From 5f2fb90f38332c2bab289d518f49cf13107485a5 Mon Sep 17 00:00:00 2001 From: Michal R Date: Wed, 5 Nov 2025 10:36:42 +0100 Subject: [PATCH] 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. --- Cargo.toml | 1 + aya-obj/Cargo.toml | 1 + aya-obj/src/btf/btf.rs | 95 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 73f71433..08f9b069 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/aya-obj/Cargo.toml b/aya-obj/Cargo.toml index c3056fe5..069c87b2 100644 --- a/aya-obj/Cargo.toml +++ b/aya-obj/Cargo.toml @@ -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"] } diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs index ff28b164..6b5b202e 100644 --- a/aya-obj/src/btf/btf.rs +++ b/aya-obj/src/btf/btf.rs @@ -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"; + assert_eq!(sanitize_type_name(name), "MyStruct_3C_u64_3E_"); + + let name = "MyStruct"; + assert_eq!(sanitize_type_name(name), "MyStruct_3C_u64_2C__20_u64_3E_"); + + let name = "my_function"; + assert_eq!( + sanitize_type_name(name), + "my_function_3C_aya_bpf_3A__3A_BpfContext_3E_" + ); + + let name = "my_function"; + 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"; + 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();