diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7704179b9..b897086da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -299,7 +299,7 @@ jobs: run: | set -euxo pipefail find test/.tmp -name '*.deb' -print0 | xargs -t -0 -I {} \ - sh -c "dpkg --fsys-tarfile {} | tar -C test/.tmp --wildcards --extract '*vmlinuz*' --file -" + sh -c "dpkg --fsys-tarfile {} | tar -C test/.tmp --wildcards --extract '*vmlinuz*' --wildcards --extract '*config*' --file -" - name: Run local integration tests if: runner.os == 'Linux' diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs index 0d160a7d8..46e2b8ed2 100644 --- a/aya-obj/src/btf/btf.rs +++ b/aya-obj/src/btf/btf.rs @@ -18,11 +18,12 @@ use crate::{ info::{FuncSecInfo, LineSecInfo}, relocation::Relocation, Array, BtfEnum, BtfKind, BtfMember, BtfType, Const, Enum, FuncInfo, FuncLinkage, Int, - IntEncoding, LineInfo, Struct, Typedef, Union, VarLinkage, + IntEncoding, LineInfo, Struct, Typedef, Union, Var, VarLinkage, }, - generated::{btf_ext_header, btf_header}, + generated::{bpf_map_type, btf_ext_header, btf_header, BPF_F_RDONLY_PROG}, + maps::{bpf_map_def, LegacyMap}, util::{bytes_of, HashMap}, - Object, + EbpfSectionKind, Map, Object, }; pub(crate) const MAX_RESOLVE_DEPTH: u8 = 32; @@ -157,6 +158,20 @@ pub enum BtfError { /// unable to get symbol name #[error("Unable to get symbol name")] InvalidSymbolName, + + /// external symbol is invalid + #[error("Invalid extern symbol `{symbol_name}`")] + InvalidExternalSymbol { + /// name of the symbol + symbol_name: String, + }, + + /// external symbol not found + #[error("Extern symbol not found `{symbol_name}`")] + ExternalSymbolNotFound { + /// name of the symbol + symbol_name: String, + }, } /// Available BTF features @@ -463,6 +478,57 @@ impl Btf { }) } + pub(crate) fn type_align(&self, root_type_id: u32) -> Result { + let mut type_id = root_type_id; + for _ in 0..MAX_RESOLVE_DEPTH { + let ty = self.types.type_by_id(type_id)?; + let size = match ty { + BtfType::Array(Array { array, .. }) => { + type_id = array.element_type; + continue; + } + BtfType::Struct(Struct { size, members, .. }) + | BtfType::Union(Union { size, members, .. }) => { + let mut max_align = 1; + + for m in members { + let align = self.type_align(m.btf_type)?; + max_align = usize::max(align, max_align); + + if ty.member_bit_field_size(m).unwrap() == 0 + || m.offset % (8 * align as u32) != 0 + { + return Ok(1); + } + } + + if size % max_align as u32 != 0 { + return Ok(1); + } + + return Ok(max_align); + } + + other => { + if let Some(size) = other.size() { + u32::min(BtfType::ptr_size(), size) + } else if let Some(next) = other.btf_type() { + type_id = next; + continue; + } else { + return Err(BtfError::UnexpectedBtfType { type_id }); + } + } + }; + + return Ok(size as usize); + } + + Err(BtfError::MaximumTypeDepthReached { + type_id: root_type_id, + }) + } + /// Encodes the metadata as BTF format pub fn to_bytes(&self) -> Vec { // Safety: btf_header is POD @@ -473,6 +539,38 @@ impl Btf { buf } + pub(crate) fn get_extern_data_sec_entry_info( + &self, + target_var_name: &str, + ) -> Result<(String, Var), BtfError> { + for t in &self.types.types { + let BtfType::DataSec(d) = t else { + continue; + }; + let sec_name = self.string_at(d.name_offset)?; + + for d in &d.entries { + let BtfType::Var(var) = self.types.type_by_id(d.btf_type)? else { + continue; + }; + + if target_var_name == self.string_at(var.name_offset)? { + if var.linkage == VarLinkage::Extern { + return Ok((sec_name.into(), var.clone())); + } else { + return Err(BtfError::InvalidExternalSymbol { + symbol_name: target_var_name.into(), + }); + } + } + } + } + + Err(BtfError::ExternalSymbolNotFound { + symbol_name: target_var_name.into(), + }) + } + // This follows the same logic as libbpf's bpf_object__sanitize_btf() function. // https://github.com/libbpf/libbpf/blob/05f94ddbb837f5f4b3161e341eed21be307eaa04/src/libbpf.c#L2701 // @@ -610,6 +708,14 @@ impl Btf { } }; e.offset = *offset as u32; + + if var.linkage == VarLinkage::Extern { + let mut var = var.clone(); + var.linkage = VarLinkage::Global; + + types.types[e.btf_type as usize] = BtfType::Var(var); + } + debug!( "{} {}: VAR {}: fixup offset {}", kind, name, var_name, offset @@ -730,6 +836,107 @@ impl Default for Btf { } impl Object { + fn patch_extern_data_internal( + &mut self, + externs: &HashMap>, + ) -> Result)>, BtfError> { + if let Some(ref mut obj_btf) = &mut self.btf { + if obj_btf.is_empty() { + return Ok(None); + } + + let mut kconfig_map_index = 0; + + for map in self.maps.values() { + if map.section_index() >= kconfig_map_index { + kconfig_map_index = map.section_index() + 1; + } + } + + let kconfig_map_index = self.maps.len(); + + let symbols = self + .symbol_table + .iter_mut() + .filter(|(_, s)| s.name.is_some() && s.section_index.is_none() && s.is_external) + .map(|(_, s)| (s.name.as_ref().unwrap().clone(), s)); + + let mut section_data = Vec::::new(); + let mut offset = 0u64; + let mut has_extern_data = false; + + for (name, symbol) in symbols { + let (datasec_name, var) = obj_btf.get_extern_data_sec_entry_info(&name)?; + + if datasec_name == ".kconfig" { + has_extern_data = true; + + let type_size = obj_btf.type_size(var.btf_type)?; + let type_align = obj_btf.type_align(var.btf_type)? as u64; + + let mut external_value_opt = externs.get(&name); + let empty_data = vec![0; type_size]; + + if external_value_opt.is_none() && symbol.is_weak { + external_value_opt = Some(&empty_data); + } + + if let Some(data) = external_value_opt { + symbol.address = (offset + (type_align - 1)) & !(type_align - 1); + symbol.size = type_size as u64; + symbol.section_index = Some(kconfig_map_index); + + section_data.resize((symbol.address - offset) as usize, 0); + + self.symbol_offset_by_name.insert(name, symbol.address); + offset = symbol.address + section_data.len() as u64; + section_data.extend(data); + } else { + return Err(BtfError::ExternalSymbolNotFound { symbol_name: name }); + } + } + } + + if has_extern_data { + self.section_infos.insert( + ".kconfig".into(), + (SectionIndex(kconfig_map_index), section_data.len() as u64), + ); + + return Ok(Some((SectionIndex(kconfig_map_index), section_data))); + } + } + Ok(None) + } + + /// Patches extern data + pub fn patch_extern_data( + &mut self, + externs: &HashMap>, + ) -> Result<(), BtfError> { + if let Some((section_index, data)) = self.patch_extern_data_internal(externs)? { + self.maps.insert( + ".kconfig".into(), + Map::Legacy(LegacyMap { + def: bpf_map_def { + map_type: bpf_map_type::BPF_MAP_TYPE_ARRAY as u32, + key_size: mem::size_of::() as u32, + value_size: data.len() as u32, + max_entries: 1, + map_flags: BPF_F_RDONLY_PROG, + ..Default::default() + }, + section_index: section_index.0, + section_kind: EbpfSectionKind::Rodata, + symbol_index: None, + data, + }), + ); + } + + Ok(()) + } + /// Fixes up and sanitizes BTF data. /// /// Mostly, it removes unsupported types and works around LLVM behaviours. diff --git a/aya-obj/src/btf/types.rs b/aya-obj/src/btf/types.rs index 9e68b71c1..3b75af76d 100644 --- a/aya-obj/src/btf/types.rs +++ b/aya-obj/src/btf/types.rs @@ -1292,11 +1292,15 @@ impl BtfType { BtfType::Struct(t) => Some(t.size), BtfType::Union(t) => Some(t.size), BtfType::DataSec(t) => Some(t.size), - BtfType::Ptr(_) => Some(mem::size_of::<&()>() as u32), + BtfType::Ptr(_) => Some(Self::ptr_size()), _ => None, } } + pub(crate) fn ptr_size() -> u32 { + mem::size_of::<&()>() as u32 + } + pub(crate) fn btf_type(&self) -> Option { match self { BtfType::Const(t) => Some(t.btf_type), diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index e4be87da3..4eb9f58e5 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -49,6 +49,7 @@ pub struct Features { devmap_prog_id: bool, prog_info_map_ids: bool, prog_info_gpl_compatible: bool, + bpf_syscall_wrapper: bool, btf: Option, } @@ -65,6 +66,7 @@ impl Features { devmap_prog_id: bool, prog_info_map_ids: bool, prog_info_gpl_compatible: bool, + bpf_syscall_wrapper: bool, btf: Option, ) -> Self { Self { @@ -77,6 +79,7 @@ impl Features { devmap_prog_id, prog_info_map_ids, prog_info_gpl_compatible, + bpf_syscall_wrapper, btf, } } @@ -118,6 +121,10 @@ impl Features { pub fn devmap_prog_id(&self) -> bool { self.devmap_prog_id } + /// Returns whether BPF syscall wrapper hooking is supported. + pub fn bpf_syscall_wrapper(&self) -> bool { + self.bpf_syscall_wrapper + } /// Returns whether `bpf_prog_info` supports `nr_map_ids` & `map_ids` fields. pub fn prog_info_map_ids(&self) -> bool { @@ -483,6 +490,8 @@ impl Object { address: symbol.address(), size: symbol.size(), is_definition: symbol.is_definition(), + is_external: symbol.is_undefined() && (symbol.is_global() || symbol.is_weak()), + is_weak: symbol.is_weak(), kind: symbol.kind(), }; bpf_obj.symbol_table.insert(symbol.index().0, sym); @@ -1465,6 +1474,8 @@ mod tests { size, is_definition: false, kind: SymbolKind::Text, + is_external: false, + is_weak: false, }, ); obj.symbols_by_section @@ -2601,6 +2612,8 @@ mod tests { address: 0, size: 3, is_definition: true, + is_external: false, + is_weak: false, kind: SymbolKind::Data, }, ); diff --git a/aya-obj/src/relocation.rs b/aya-obj/src/relocation.rs index b05648ba4..0c4e66433 100644 --- a/aya-obj/src/relocation.rs +++ b/aya-obj/src/relocation.rs @@ -99,6 +99,8 @@ pub(crate) struct Symbol { pub(crate) address: u64, pub(crate) size: u64, pub(crate) is_definition: bool, + pub(crate) is_external: bool, + pub(crate) is_weak: bool, pub(crate) kind: SymbolKind, } @@ -218,7 +220,9 @@ fn relocate_maps<'a, I: Iterator>( }; // calls and relocation to .text symbols are handled in a separate step - if insn_is_call(&instructions[ins_index]) || text_sections.contains(§ion_index) { + if insn_is_call(&instructions[ins_index]) + || (text_sections.contains(§ion_index) && !sym.is_external) + { continue; } @@ -367,10 +371,11 @@ impl<'a> FunctionLinker<'a> { // only consider text relocations, data relocations are // relocated in relocate_maps() sym.kind == SymbolKind::Text - || sym - .section_index - .map(|section_index| self.text_sections.contains(§ion_index)) - .unwrap_or(false) + || (!sym.is_external + && sym + .section_index + .map(|section_index| self.text_sections.contains(§ion_index)) + .unwrap_or(false)) }); // not a call and not a text relocation, we don't need to do anything @@ -510,6 +515,8 @@ mod test { address, size, is_definition: false, + is_external: false, + is_weak: false, kind: SymbolKind::Data, } } diff --git a/aya/Cargo.toml b/aya/Cargo.toml index 57e80c777..797fcbac7 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -24,6 +24,7 @@ log = { workspace = true } object = { workspace = true, features = ["elf", "read_core", "std", "write"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["rt"], optional = true } +flate2 = "1.0" [dev-dependencies] tempfile = { workspace = true } diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 8a1894763..342fc1608 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -1,7 +1,8 @@ use std::{ borrow::Cow, collections::{HashMap, HashSet}, - fs, io, + fs::{self, File}, + io::{self, Read}, os::{ fd::{AsFd as _, AsRawFd as _}, raw::c_int, @@ -16,6 +17,8 @@ use aya_obj::{ relocation::EbpfRelocationError, EbpfSectionKind, Features, }; +use flate2::read::GzDecoder; +use lazy_static::lazy_static; use log::{debug, warn}; use thiserror::Error; @@ -36,14 +39,15 @@ use crate::{ SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, }, sys::{ - bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, - is_btf_datasec_supported, is_btf_decl_tag_supported, is_btf_enum64_supported, - is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported, - is_btf_supported, is_btf_type_tag_supported, is_info_gpl_compatible_supported, - is_info_map_ids_supported, is_perf_link_supported, is_probe_read_kernel_supported, - is_prog_id_supported, is_prog_name_supported, retry_with_verifier_logs, + self, bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, + is_bpf_syscall_wrapper_supported, is_btf_datasec_supported, is_btf_decl_tag_supported, + is_btf_enum64_supported, is_btf_float_supported, is_btf_func_global_supported, + is_btf_func_supported, is_btf_supported, is_btf_type_tag_supported, + is_info_gpl_compatible_supported, is_info_map_ids_supported, is_perf_link_supported, + is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported, + retry_with_verifier_logs, }, - util::{bytes_of, bytes_of_slice, page_size, possible_cpus, POSSIBLE_CPUS}, + util::{bytes_of, bytes_of_slice, page_size, possible_cpus, KernelVersion, POSSIBLE_CPUS}, }; pub(crate) const BPF_OBJ_NAME_LEN: usize = 16; @@ -98,6 +102,7 @@ fn detect_features() -> Features { is_prog_id_supported(BPF_MAP_TYPE_DEVMAP), is_info_map_ids_supported(), is_info_gpl_compatible_supported(), + is_bpf_syscall_wrapper_supported(), btf, ); debug!("BPF Feature Detection: {:#?}", f); @@ -109,6 +114,131 @@ pub fn features() -> &'static Features { &FEATURES } +lazy_static! { + static ref KCONFIG_DEFINITION: HashMap> = compute_kconfig_definition(&FEATURES); +} + +fn compute_kconfig_definition(features: &Features) -> HashMap> { + let mut result = HashMap::new(); + + if let Ok(KernelVersion { + major, + minor, + patch, + }) = KernelVersion::current() + { + result.insert( + "LINUX_KERNEL_VERSION".to_string(), + { + let value = (u64::from(major) << 16) + (u64::from(minor) << 8) + u64::from(patch); + value.to_ne_bytes() + } + .to_vec(), + ); + } + + let bpf_cookie = if features.bpf_cookie() { 1u64 } else { 0u64 }; + let bpf_syscall_wrapper = if features.bpf_syscall_wrapper() { + 1u64 + } else { + 0u64 + }; + + result.insert( + "LINUX_HAS_BPF_COOKIE".to_string(), + bpf_cookie.to_ne_bytes().to_vec(), + ); + + result.insert( + "LINUX_HAS_SYSCALL_WRAPPER".to_string(), + bpf_syscall_wrapper.to_ne_bytes().to_vec(), + ); + + if let Some(raw_config) = read_kconfig() { + for line in raw_config.lines() { + if !line.starts_with("CONFIG_") { + continue; + } + + let mut parts = line.split('='); + let (key, raw_value) = match (parts.next(), parts.next(), parts.count()) { + (Some(key), Some(value), 0) => (key, value), + _ => continue, + }; + + let value = match raw_value.chars().next() { + Some('n') => 0_u64.to_ne_bytes().to_vec(), + Some('y') => 1_u64.to_ne_bytes().to_vec(), + Some('m') => 2_u64.to_ne_bytes().to_vec(), + Some('"') => { + if raw_value.len() < 2 || !raw_value.ends_with('"') { + continue; + } + + let raw_value = &raw_value[1..raw_value.len() - 1]; + raw_value.as_bytes().to_vec() + } + Some(_) => { + if let Ok(value) = raw_value.parse::() { + value.to_ne_bytes().to_vec() + } else { + continue; + } + } + None => continue, + }; + + result.insert(key.to_string(), value); + } + } + + result +} + +fn read_kconfig() -> Option { + let config_path = PathBuf::from("/proc/config.gz"); + if config_path.exists() { + debug!("Found kernel config at {}", config_path.to_string_lossy()); + return read_kconfig_file(&config_path, true); + } + + let Ok(release) = sys::kernel_release() else { + return None; + }; + + let config_path = PathBuf::from("/boot").join(format!("config-{}", release)); + if config_path.exists() { + debug!("Found kernel config at {}", config_path.to_string_lossy()); + return read_kconfig_file(&config_path, false); + } + + None +} + +fn read_kconfig_file(path: &PathBuf, gzip: bool) -> Option { + let mut output = String::new(); + + let res = if gzip { + File::open(path) + .map(GzDecoder::new) + .and_then(|mut file| file.read_to_string(&mut output)) + } else { + File::open(path).and_then(|mut file| file.read_to_string(&mut output)) + }; + + match res { + Ok(_) => Some(output), + Err(e) => { + warn!( + "Unable to read kernel config {}: {:?}", + path.to_string_lossy(), + e + ); + None + } + } +} + /// Builder style API for advanced loading of eBPF programs. /// /// Loading eBPF code involves a few steps, including loading maps and applying @@ -399,6 +529,7 @@ impl<'a> EbpfLoader<'a> { } = self; let mut obj = Object::parse(data)?; obj.patch_map_data(globals.clone())?; + obj.patch_extern_data(&KCONFIG_DEFINITION)?; let btf_fd = if let Some(features) = &FEATURES.btf() { if let Some(btf) = obj.fixup_and_sanitize_btf(features)? { diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index fcca69fa8..1eaa913c8 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -38,7 +38,7 @@ // modules we don't export mod info; -mod probe; +pub(crate) mod probe; mod utils; // modules we explicitly export so their pub items (Links etc) get exported too diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs index 4d737e346..0e15f750f 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -145,7 +145,7 @@ pub(crate) fn detach_debug_fs(event: ProbeEvent) -> Result<(), ProgramError> { }) } -fn create_as_probe( +pub(crate) fn create_as_probe( kind: ProbeKind, fn_name: &OsStr, offset: u64, diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 2d3052460..42f16881b 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -1,6 +1,6 @@ use std::{ cmp, - ffi::{c_char, CStr, CString}, + ffi::{c_char, CStr, CString, OsStr}, io, iter, mem::{self, MaybeUninit}, os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, FromRawFd as _, RawFd}, @@ -30,6 +30,7 @@ use crate::{ }, copy_instructions, }, + programs::probe::create_as_probe, sys::{syscall, SysResult, Syscall, SyscallError}, util::KernelVersion, Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN, FEATURES, @@ -950,6 +951,43 @@ pub(crate) fn is_prog_id_supported(map_type: bpf_map_type) -> bool { fd.is_ok() } +fn arch_specific_syscall_prefix() -> Option<&'static str> { + if cfg!(target_arch = "aarch64") { + Some("arm64") + } else if cfg!(target_arch = "arm") { + Some("arm") + } else if cfg!(target_arch = "powerpc") { + Some("powerpc") + } else if cfg!(target_arch = "powerpc64") { + Some("powerpc64") + } else if cfg!(target_arch = "riscv32") || cfg!(target_arch = "riscv64") { + Some("riscv") + } else if cfg!(target_arch = "x86") { + Some("ia32") + } else if cfg!(target_arch = "x86_64") { + Some("x64") + } else if cfg!(target_arch = "s390x") { + Some("s390x") + } else if cfg!(target_arch = "mips") || cfg!(target_arch = "mips64") { + Some("mips") + } else { + None + } +} + +pub(crate) fn is_bpf_syscall_wrapper_supported() -> bool { + let syscall_prefix_opt = arch_specific_syscall_prefix(); + + if let Some(syscall_prefix) = syscall_prefix_opt { + let syscall_name = format!("__{}_sys_bpf", syscall_prefix); + let syscall_name = OsStr::new(syscall_name.as_str()); + + return create_as_probe(crate::programs::ProbeKind::KProbe, syscall_name, 0, None).is_ok(); + } + + false +} + pub(crate) fn is_btf_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("int"); diff --git a/aya/src/sys/mod.rs b/aya/src/sys/mod.rs index 05f8b4dd7..7dc4f6d06 100644 --- a/aya/src/sys/mod.rs +++ b/aya/src/sys/mod.rs @@ -191,3 +191,26 @@ impl From for crate::generated::bpf_stats_type { pub fn enable_stats(stats_type: Stats) -> Result { bpf_enable_stats(stats_type.into()).map(|fd| fd.into_inner()) } + +#[cfg(test)] +pub(crate) fn kernel_release() -> Result { + Ok("unknown".to_string()) +} + +#[cfg(not(test))] +pub(crate) fn kernel_release() -> Result { + use std::ffi::CStr; + + use libc::utsname; + + unsafe { + let mut v = mem::zeroed::(); + if libc::uname(&mut v as *mut _) != 0 { + return Err(()); + } + + let release = CStr::from_ptr(v.release.as_ptr()); + + Ok(release.to_string_lossy().into_owned()) + } +} diff --git a/test/integration-test/bpf/kconfig.bpf.c b/test/integration-test/bpf/kconfig.bpf.c new file mode 100644 index 000000000..2a7859518 --- /dev/null +++ b/test/integration-test/bpf/kconfig.bpf.c @@ -0,0 +1,38 @@ +// clang-format off +#include +#include +// clang-format on + +// CONFIG_BPF=y => 1 +extern unsigned int CONFIG_BPF __kconfig; +// CONFIG_PANIC_TIMEOUT=0 => 0 +extern unsigned int CONFIG_PANIC_TIMEOUT __kconfig; +// CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 +extern unsigned int CONFIG_DEFAULT_HUNG_TASK_TIMEOUT __kconfig; +// CONFIG_DEFAULT_HOSTNAME +extern char CONFIG_DEFAULT_HOSTNAME[] __kconfig; + +SEC("xdp") +int kconfig(struct xdp_md *ctx) { + if (CONFIG_BPF != 1) { + return XDP_DROP; + } + + if (CONFIG_PANIC_TIMEOUT != 0) { + return XDP_DROP; + } + + if (CONFIG_DEFAULT_HUNG_TASK_TIMEOUT != 120) { + return XDP_DROP; + } + + for (int i = 0; i < 7; i++) { + if ("(none)"[i] != CONFIG_DEFAULT_HOSTNAME[i]) { + return XDP_DROP; + } + } + + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; diff --git a/test/integration-test/build.rs b/test/integration-test/build.rs index 8e53b5a97..efbf430d6 100644 --- a/test/integration-test/build.rs +++ b/test/integration-test/build.rs @@ -71,6 +71,7 @@ fn main() { ("multimap-btf.bpf.c", false), ("reloc.bpf.c", true), ("text_64_64_reloc.c", false), + ("kconfig.bpf.c", false), ]; if build_integration_bpf { diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index fc31e6c28..e32585365 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -9,6 +9,7 @@ pub const RELOC_BTF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.target.o")); pub const TEXT_64_64_RELOC: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/text_64_64_reloc.o")); +pub const KCONFIG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/kconfig.bpf.o")); pub const LOG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/log")); pub const MAP_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/map_test")); diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs index 0d57aea9f..03bbef2ae 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/src/tests/smoke.rs @@ -69,3 +69,16 @@ fn extension() { .load(pass.fd().unwrap().try_clone().unwrap(), "xdp_pass") .unwrap(); } + +#[test] +fn kconfig() { + let kernel_version = KernelVersion::current().unwrap(); + if kernel_version < KernelVersion::new(5, 9, 0) { + eprintln!("skipping test on kernel {kernel_version:?}, XDP uses netlink"); + return; + } + let mut bpf = Ebpf::load(crate::KCONFIG).unwrap(); + let pass: &mut Xdp = bpf.program_mut("kconfig").unwrap().try_into().unwrap(); + pass.load().unwrap(); + pass.attach("lo", XdpFlags::default()).unwrap(); +} diff --git a/xtask/public-api/aya-obj.txt b/xtask/public-api/aya-obj.txt index f46d106f2..31f941b3d 100644 --- a/xtask/public-api/aya-obj.txt +++ b/xtask/public-api/aya-obj.txt @@ -1,10 +1,14 @@ pub mod aya_obj pub mod aya_obj::btf pub enum aya_obj::btf::BtfError +pub aya_obj::btf::BtfError::ExternalSymbolNotFound +pub aya_obj::btf::BtfError::ExternalSymbolNotFound::symbol_name: alloc::string::String pub aya_obj::btf::BtfError::FileError pub aya_obj::btf::BtfError::FileError::error: std::io::error::Error pub aya_obj::btf::BtfError::FileError::path: std::path::PathBuf pub aya_obj::btf::BtfError::InvalidDatasec +pub aya_obj::btf::BtfError::InvalidExternalSymbol +pub aya_obj::btf::BtfError::InvalidExternalSymbol::symbol_name: alloc::string::String pub aya_obj::btf::BtfError::InvalidHeader pub aya_obj::btf::BtfError::InvalidInfo pub aya_obj::btf::BtfError::InvalidInfo::len: usize @@ -7106,6 +7110,7 @@ pub fn aya_obj::Features::bpf_global_data(&self) -> bool pub fn aya_obj::Features::bpf_name(&self) -> bool pub fn aya_obj::Features::bpf_perf_link(&self) -> bool pub fn aya_obj::Features::bpf_probe_read_kernel(&self) -> bool +pub fn aya_obj::Features::bpf_syscall_wrapper(&self) -> bool pub fn aya_obj::Features::btf(&self) -> core::option::Option<&aya_obj::btf::BtfFeatures> pub fn aya_obj::Features::cpumap_prog_id(&self) -> bool pub fn aya_obj::Features::devmap_prog_id(&self) -> bool @@ -7214,6 +7219,7 @@ pub aya_obj::obj::Object::maps: std::collections::hash::map::HashMap impl aya_obj::Object pub fn aya_obj::Object::fixup_and_sanitize_btf(&mut self, features: &aya_obj::btf::BtfFeatures) -> core::result::Result, aya_obj::btf::BtfError> +pub fn aya_obj::Object::patch_extern_data(&mut self, externs: &std::collections::hash::map::HashMap>) -> core::result::Result<(), aya_obj::btf::BtfError> impl aya_obj::Object pub fn aya_obj::Object::parse(data: &[u8]) -> core::result::Result pub fn aya_obj::Object::patch_map_data(&mut self, globals: std::collections::hash::map::HashMap<&str, (&[u8], bool)>) -> core::result::Result<(), aya_obj::ParseError> @@ -7966,6 +7972,7 @@ pub fn aya_obj::Features::bpf_global_data(&self) -> bool pub fn aya_obj::Features::bpf_name(&self) -> bool pub fn aya_obj::Features::bpf_perf_link(&self) -> bool pub fn aya_obj::Features::bpf_probe_read_kernel(&self) -> bool +pub fn aya_obj::Features::bpf_syscall_wrapper(&self) -> bool pub fn aya_obj::Features::btf(&self) -> core::option::Option<&aya_obj::btf::BtfFeatures> pub fn aya_obj::Features::cpumap_prog_id(&self) -> bool pub fn aya_obj::Features::devmap_prog_id(&self) -> bool @@ -8074,6 +8081,7 @@ pub aya_obj::Object::maps: std::collections::hash::map::HashMap impl aya_obj::Object pub fn aya_obj::Object::fixup_and_sanitize_btf(&mut self, features: &aya_obj::btf::BtfFeatures) -> core::result::Result, aya_obj::btf::BtfError> +pub fn aya_obj::Object::patch_extern_data(&mut self, externs: &std::collections::hash::map::HashMap>) -> core::result::Result<(), aya_obj::btf::BtfError> impl aya_obj::Object pub fn aya_obj::Object::parse(data: &[u8]) -> core::result::Result pub fn aya_obj::Object::patch_map_data(&mut self, globals: std::collections::hash::map::HashMap<&str, (&[u8], bool)>) -> core::result::Result<(), aya_obj::ParseError> diff --git a/xtask/src/run.rs b/xtask/src/run.rs index 8bba7a532..652e4d844 100644 --- a/xtask/src/run.rs +++ b/xtask/src/run.rs @@ -340,6 +340,28 @@ pub fn run(opts: Options) -> Result<()> { } } } + + // Copy kernel configs as well (based on Debian path conventions) + let config_path = PathBuf::from( + kernel_image + .to_string_lossy() + .replace("vmlinuz-", "config-"), + ); + if config_path.exists() { + let mut destination = PathBuf::from("/boot"); + destination.push(config_path.file_name().expect("filename")); + for bytes in [ + "dir /boot 0755 0 0\n".as_bytes(), + "file ".as_bytes(), + destination.as_os_str().as_bytes(), + " ".as_bytes(), + config_path.as_os_str().as_bytes(), + " 0755 0 0\n".as_bytes(), + ] { + stdin.write_all(bytes).expect("write"); + } + } + // Must explicitly close to signal EOF. drop(stdin);