aya-obj: Sanitize type names

Rust emits characters like `<` and `>` in name types with generics, that
are not accepted by the Linux kernel.

In theory, the correct place to fix that would be the Linux kernel, but
given that we have to support old kernels and that such change would be
controversial, it's better to fix it up on our side, at least for now.

We used to rely on bpf-linker to fix that up, but given that we want to
support binutils at some point, we need to do it in Aya during the load.
reviewable/pr1382/r1
Michal R 4 days ago
parent 17573e0e47
commit 5f2fb90f38

@ -77,6 +77,7 @@ dialoguer = { version = "0.12", default-features = false }
diff = { version = "0.1.13", default-features = false }
env_logger = { version = "0.11", default-features = false }
epoll = { version = "4.3.3", default-features = false }
foldhash = { version = "0.2", default-features = false }
futures = { version = "0.3.28", default-features = false }
glob = { version = "0.3.0", default-features = false }
hashbrown = { version = "0.16.0", default-features = false }

@ -18,6 +18,7 @@ workspace = true
[dependencies]
bytes = { workspace = true }
foldhash = { workspace = true }
hashbrown = { workspace = true, features = ["default-hasher", "equivalent"] }
log = { workspace = true }
object = { workspace = true, features = ["elf", "read_core"] }

@ -8,10 +8,13 @@ use alloc::{
use core::{
cell::OnceCell,
ffi::{CStr, FromBytesUntilNulError},
fmt::Write,
hash::{BuildHasher as _, Hasher as _},
mem, ptr,
};
use bytes::BufMut as _;
use foldhash::fast::FixedState;
use log::debug;
use object::{Endianness, SectionIndex};
@ -275,6 +278,45 @@ fn add_type(header: &mut btf_header, types: &mut BtfTypes, btf_type: BtfType) ->
type_id as u32
}
/// Maximum length of a type name accepted by the [Linux kernel][name-limit]
/// according to the [`KSYM_NAME_LEN` variable][ksym-name-len]. That variable
/// got increased in [kernel 6.1][ksym-name-len-bump], but we keep the old value
/// for backwards compatibility.
/// Ker
/// KSYM_NAME_LEN from Linux kernel intentionally set to the lowest value found
/// across versions to ensure backward compatibility.
///
/// [name-limit]: https://github.com/torvalds/linux/blob/v4.20/kernel/bpf/btf.c#L442-L449
/// [ksym-name-len]: https://github.com/torvalds/linux/blob/v4.20/include/linux/kallsyms.h#L17
/// [ksym-name-len-bump]: https://github.com/torvalds/linux/commit/b8a94bfb33952bb17fbc65f8903d242a721c533d
const MAX_NAME_LEN: usize = 128;
// Sanitize Rust type names to be valid C type names. Kernel does not accept
// other characters like `<`, `>` that Rust emits.
fn sanitize_type_name(name: &str) -> String {
let mut sanitized = String::with_capacity(name.len());
for byte in name.bytes() {
// Characters which are valid in C type names (alphanumeric and `_`).
if matches!(byte, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_') {
sanitized.push(byte as char);
} else {
write!(&mut sanitized, "_{:X}_", byte).unwrap();
}
}
if sanitized.len() > MAX_NAME_LEN {
let mut hasher = FixedState::default().build_hasher();
hasher.write(sanitized.as_bytes());
let hash = hasher.finish();
// leave space for underscore
let trim = MAX_NAME_LEN - 2 * size_of_val(&hash) - 1;
sanitized.truncate(trim);
write!(&mut sanitized, "_{:x}", hash).unwrap();
}
sanitized
}
impl Btf {
/// Creates a new empty instance with its header initialized
pub fn new() -> Self {
@ -695,8 +737,14 @@ impl Btf {
}
// Sanitize FUNC.
BtfType::Func(ty) => {
// Sanitize the name.
let name = self.string_at(ty.name_offset)?;
let fixed_name = sanitize_type_name(&name);
if fixed_name != name {
ty.name_offset = self.add_string(&fixed_name);
}
// Adjust type and linkage according to features.
let name = self.string_at(ty.name_offset)?;
// Sanitize FUNC.
if !features.btf_func {
debug!("{kind}: not supported. replacing with TYPEDEF");
*t = BtfType::Typedef(Typedef::new(ty.name_offset, ty.btf_type));
@ -805,6 +853,15 @@ impl Btf {
*t = BtfType::Union(Union::new(name_offset, size, members, Some(fallback)));
}
}
// Sanitize STRUCT.
BtfType::Struct(ty) => {
// Sanitize the name.
let name = self.string_at(ty.name_offset)?;
let fixed_name = sanitize_type_name(&name);
if fixed_name != name {
ty.name_offset = self.add_string(&fixed_name);
}
}
// The type does not need fixing up or sanitization.
_ => {}
}
@ -1417,6 +1474,42 @@ mod tests {
assert_eq!(btf.string_at(5).unwrap(), "widget");
}
#[test]
fn test_sanitize_type_name() {
let name = "MyStruct<u64>";
assert_eq!(sanitize_type_name(name), "MyStruct_3C_u64_3E_");
let name = "MyStruct<u64, u64>";
assert_eq!(sanitize_type_name(name), "MyStruct_3C_u64_2C__20_u64_3E_");
let name = "my_function<aya_bpf::BpfContext>";
assert_eq!(
sanitize_type_name(name),
"my_function_3C_aya_bpf_3A__3A_BpfContext_3E_"
);
let name = "my_function<aya_bpf::BpfContext, aya_log_ebpf::WriteToBuf>";
assert_eq!(
sanitize_type_name(name),
"my_function_3C_aya_bpf_3A__3A_BpfContext_2C__20_aya_log_ebpf_3A__3A_WriteToBuf_3E_"
);
let name = "PerfEventArray<[u8; 32]>";
assert_eq!(
sanitize_type_name(name),
"PerfEventArray_3C__5B_u8_3B__20_32_5D__3E_"
);
let name = "my_function<aya_bpf::this::is::a::very::long::namespace::BpfContext, aya_log_ebpf::this::is::a::very::long::namespace::WriteToBuf>";
let san = sanitize_type_name(name);
assert_eq!(san.len(), 128);
assert_eq!(
san,
"my_function_3C_aya_bpf_3A__3A_this_3A__3A_is_3A__3A_a_3A__3A_very_3A__3A_long_3A__3A_namespace_3A__3A_BpfContex_f6a9dc7f4a53c91d"
);
}
#[test]
fn test_fixup_ptr() {
let mut btf = Btf::new();

Loading…
Cancel
Save