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/r6
Michal R 4 days ago
parent 70919e3597
commit 817da9d01e

@ -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,10 @@ workspace = true
[dependencies]
bytes = { workspace = true }
# hashbrown uses foldhash for its `DefaultHasher` implementation. However,
# hashbrown doesn't expose any API allowing to create hashers with fixed seed,
# so we need to use `foldhash::FixedState` directly.
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 as _,
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,42 @@ fn add_type(header: &mut btf_header, types: &mut BtfTypes, btf_type: BtfType) ->
type_id as u32
}
/// [The maximum length of a symbol accepted by the Linux kernel][name-limit]
/// according to the [`KSYM_NAME_LEN` constant][ksym-name-len]. That limit got
/// increased in [kernel 6.1][ksym-name-len-bump], but we keep the old value
/// for backwards 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 +734,16 @@ impl Btf {
}
// Sanitize FUNC.
BtfType::Func(ty) => {
// Sanitize the name.
let name = self.string_at(ty.name_offset)?;
// Sanitize FUNC.
let fixed_name = sanitize_type_name(&name);
let name = if fixed_name != name {
ty.name_offset = self.add_string(&fixed_name);
fixed_name.into()
} else {
name
};
// Adjust type and linkage according to features.
if !features.btf_func {
debug!("{kind}: not supported. replacing with TYPEDEF");
*t = BtfType::Typedef(Typedef::new(ty.name_offset, ty.btf_type));
@ -805,6 +852,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 +1473,57 @@ 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);
#[cfg(any(
target_arch = "aarch64",
target_arch = "loongarch64",
target_arch = "powerpc64",
target_arch = "riscv64",
target_arch = "x86_64"
))]
let expected_hash = "f6a9dc7f4a53c91d";
#[cfg(target_arch = "arm")]
let expected_hash = "e203c1e6145df4dd";
#[cfg(target_arch = "s390x")]
let expected_hash = "6609fba3a8753dce";
assert_eq!(san.len(), 128);
assert_eq!(
san,
format!(
"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_{expected_hash}"
)
);
}
#[test]
fn test_fixup_ptr() {
let mut btf = Btf::new();

Loading…
Cancel
Save