diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs index ff28b164..38934fc0 100644 --- a/aya-obj/src/btf/btf.rs +++ b/aya-obj/src/btf/btf.rs @@ -16,7 +16,7 @@ use log::debug; use object::{Endianness, SectionIndex}; use crate::{ - Object, + EbpfSectionKind, Map, Object, btf::{ Array, BtfEnum, BtfEnum64, BtfKind, BtfMember, BtfType, Const, DataSec, DataSecEntry, Enum, Enum64, Enum64Fallback, Enum64VariantFallback, FuncInfo, FuncLinkage, Int, IntEncoding, @@ -24,7 +24,8 @@ use crate::{ info::{FuncSecInfo, LineSecInfo}, relocation::Relocation, }, - generated::{btf_ext_header, btf_header}, + generated::{BPF_F_RDONLY_PROG, bpf_map_type, btf_ext_header, btf_header}, + maps::{LegacyMap, bpf_map_def}, util::{HashMap, bytes_of}, }; @@ -161,6 +162,20 @@ pub enum BtfError { #[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, + }, + /// BTF map wrapper's layout is unexpected #[error("BTF map wrapper's layout is unexpected: {0:?}")] UnexpectedBtfMapWrapperLayout(Struct), @@ -478,6 +493,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 @@ -653,7 +719,19 @@ impl Btf { } }; e.offset = *offset as u32; - debug!("{kind} {name}: VAR {var_name}: fixup offset {offset}"); + + // For kconfig support + 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 + ); } else { return Err(BtfError::InvalidDatasec); } @@ -821,6 +899,139 @@ impl Default for Btf { } impl Object { + fn patch_extern_data_internal( + &mut self, + externs: &HashMap>, + ) -> Result)>, BtfError> { + if let Some(obj_btf) = &mut self.btf { + if obj_btf.is_empty() { + return Ok(None); + } + + // Build a single-pass index of all VARs contained in DATASEC types: + // var_name -> (datasec_name, var) + let mut datasec_var_index: HashMap = HashMap::new(); + for t in &obj_btf.types.types { + let BtfType::DataSec(d) = t else { + continue; + }; + let sec_name = obj_btf.string_at(d.name_offset)?.into_owned(); + for entry in &d.entries { + let BtfType::Var(var) = obj_btf.types.type_by_id(entry.btf_type)? else { + continue; + }; + let var_name = obj_btf.string_at(var.name_offset)?.into_owned(); + datasec_var_index.insert(var_name, (sec_name.clone(), var.clone())); + } + } + + 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) = match datasec_var_index.get(&name) { + Some((sec_name, var)) => { + if var.linkage == VarLinkage::Extern { + (sec_name.as_str(), var) + } else { + return Err(BtfError::InvalidExternalSymbol { symbol_name: name }); + } + } + None => { + return Err(BtfError::ExternalSymbolNotFound { symbol_name: 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(section_data.len() + (symbol.address - offset) as usize, 0); + + self.symbol_offset_by_name.insert(name, symbol.address); + section_data.extend(data); + offset = section_data.len() as u64; + } 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 the BPF object with external data symbol values. + /// + /// External data symbols (e.g., kernel config values marked with `__kconfig`) + /// are resolved and embedded into the object as a `.kconfig` map section. + /// + /// # Arguments + /// * `externs` - A map of external symbol names to their byte values. + /// + /// # Errors + /// Returns an error if: + /// - External symbols are found but not defined in `externs` (unless weak). + /// - BTF type information is invalid for external symbols. + 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. @@ -1191,8 +1402,8 @@ mod tests { use super::*; use crate::btf::{ - BtfEnum64, BtfParam, DataSec, DataSecEntry, DeclTag, Enum64, Float, Func, FuncProto, Ptr, - TypeTag, Var, + BtfEnum64, BtfMember, BtfParam, DataSec, DataSecEntry, DeclTag, Enum64, Float, Func, + FuncProto, Int, IntEncoding, Ptr, Struct, TypeTag, Var, }; #[test] @@ -2017,4 +2228,60 @@ mod tests { let raw = btf.to_bytes(); Btf::parse(&raw, Endianness::default()).unwrap(); } + + #[test] + fn type_align_struct_naturally_aligned() { + // Build a struct: + // struct S { u64 a; u32 b; }; + // a @ 0 bits (8-byte aligned), b @ 64 bits (8 bytes), total size 16 bytes. + // On 32-bit architectures, u64 alignment is capped at ptr_size() (4 bytes). + let mut btf = Btf::new(); + let u32_ty = btf.add_type(BtfType::Int(Int::new(0, 4, IntEncoding::None, 0))); + let u64_ty = btf.add_type(BtfType::Int(Int::new(0, 8, IntEncoding::None, 0))); + + let members = vec![ + BtfMember { + name_offset: 0, + btf_type: u64_ty, + offset: 0, // bits + }, + BtfMember { + name_offset: 0, + btf_type: u32_ty, + offset: 64, // bits + }, + ]; + let s_id = btf.add_type(BtfType::Struct(Struct::new(0, members, 16))); + + let align = btf.type_align(s_id).unwrap(); + // Alignment is capped at ptr_size(), so on 32-bit archs it's 4, on 64-bit it's 8 + let expected_align = usize::min(BtfType::ptr_size() as usize, 8); + assert_eq!(align, expected_align); + } + + #[test] + fn type_align_struct_misaligned_member_is_packed() { + // Build a struct with a misaligned non-bitfield member: + // struct P { u64 a; u32 b; }; where b is at 1-byte offset (misaligned) + let mut btf = Btf::new(); + let u32_ty = btf.add_type(BtfType::Int(Int::new(0, 4, IntEncoding::None, 0))); + let u64_ty = btf.add_type(BtfType::Int(Int::new(0, 8, IntEncoding::None, 0))); + + let members = vec![ + BtfMember { + name_offset: 0, + btf_type: u64_ty, + offset: 0, // bits + }, + BtfMember { + name_offset: 0, + btf_type: u32_ty, + offset: 8, // bits (1 byte) -> not aligned to 4 bytes + }, + ]; + let p_id = btf.add_type(BtfType::Struct(Struct::new(0, members, 16))); + + let align = btf.type_align(p_id).unwrap(); + assert_eq!(align, 1); + } } diff --git a/aya-obj/src/btf/types.rs b/aya-obj/src/btf/types.rs index 13226bc0..51ada754 100644 --- a/aya-obj/src/btf/types.rs +++ b/aya-obj/src/btf/types.rs @@ -1316,11 +1316,15 @@ impl BtfType { Self::Struct(t) => Some(t.size), Self::Union(t) => Some(t.size), Self::DataSec(t) => Some(t.size), - Self::Ptr(_) => Some(mem::size_of::<&()>() as u32), + Self::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 { Self::Const(t) => Some(t.btf_type), diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index c3d0d107..1d4ee78b 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -45,6 +45,7 @@ pub struct Features { bpf_cookie: bool, cpumap_prog_id: bool, devmap_prog_id: bool, + bpf_syscall_wrapper: bool, btf: Option, } @@ -59,6 +60,7 @@ impl Features { bpf_cookie: bool, cpumap_prog_id: bool, devmap_prog_id: bool, + bpf_syscall_wrapper: bool, btf: Option, ) -> Self { Self { @@ -69,6 +71,7 @@ impl Features { bpf_cookie, cpumap_prog_id, devmap_prog_id, + bpf_syscall_wrapper, btf, } } @@ -111,6 +114,11 @@ impl Features { self.devmap_prog_id } + /// Returns whether BPF syscall wrapper hooking is supported. + pub fn bpf_syscall_wrapper(&self) -> bool { + self.bpf_syscall_wrapper + } + /// If BTF is supported, returns which BTF features are supported. pub fn btf(&self) -> Option<&BtfFeatures> { self.btf.as_ref() @@ -472,6 +480,8 @@ impl Object { address: symbol.address(), size: symbol.size(), is_definition: symbol.is_definition(), + is_external: symbol.is_undefined() && symbol.is_global(), + is_weak: symbol.is_weak(), kind: symbol.kind(), }; bpf_obj.symbol_table.insert(symbol.index().0, sym); @@ -1509,6 +1519,8 @@ mod tests { size, is_definition: false, kind: SymbolKind::Text, + is_external: false, + is_weak: false, }, ); obj.symbols_by_section @@ -2665,6 +2677,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 d035339b..a45d7f9a 100644 --- a/aya-obj/src/relocation.rs +++ b/aya-obj/src/relocation.rs @@ -104,6 +104,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, } @@ -223,7 +225,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; } @@ -372,10 +376,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 @@ -515,6 +520,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 ad431440..ddd8e261 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -21,6 +21,7 @@ assert_matches = { workspace = true } aya-obj = { path = "../aya-obj", version = "^0.2.1", features = ["std"] } bitflags = { workspace = true } bytes = { workspace = true } +flate2 = "1.0" hashbrown = { workspace = true } libc = { workspace = true } log = { workspace = true } diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 3e1d4404..be575cb8 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 as _}, os::fd::{AsFd as _, AsRawFd as _}, path::{Path, PathBuf}, sync::{Arc, LazyLock}, @@ -16,6 +17,7 @@ use aya_obj::{ }, relocation::EbpfRelocationError, }; +use flate2::read::GzDecoder; use log::{debug, warn}; use thiserror::Error; @@ -29,14 +31,14 @@ use crate::{ TracePoint, UProbe, Xdp, }, sys::{ - bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, - is_btf_datasec_supported, is_btf_datasec_zero_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_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_datasec_zero_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_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, nr_cpus, page_size}, + util::{KernelVersion, bytes_of, bytes_of_slice, nr_cpus, page_size}, }; /// Marker trait for types that can safely be converted to and from byte slices. @@ -60,6 +62,46 @@ pub use aya_obj::maps::{PinningType, bpf_map_def}; pub(crate) static FEATURES: LazyLock = LazyLock::new(detect_features); +/// Kernel configuration data for eBPF extern variables. +/// +/// This type holds kernel configuration values that can be used to patch extern variables +/// in eBPF programs. It includes both kernel version information and CONFIG_* values from +/// the kernel configuration. +/// +/// # Examples +/// +/// ```no_run +/// use aya::{EbpfLoader, KConfig}; +/// +/// // Load kconfig from the system +/// let kconfig = KConfig::current(); +/// let bpf = EbpfLoader::new() +/// .kconfig(kconfig) +/// .load_file("file.o")?; +/// # Ok::<(), Box>(()) +/// ``` +#[derive(Debug, Clone)] +pub struct KConfig { + data: HashMap>, +} + +impl KConfig { + /// Creates a new `KConfig` by reading kernel configuration from the system. + /// + /// This will attempt to read `/proc/config.gz` or `/boot/config-` and + /// populate the configuration with kernel version and feature detection information. + pub fn current() -> Self { + Self { + data: compute_kconfig_definition(&FEATURES), + } + } + + /// Returns a reference to the underlying configuration data. + pub(crate) fn as_map(&self) -> &HashMap> { + &self.data + } +} + fn detect_features() -> Features { let btf = if is_btf_supported() { Some(BtfFeatures::new( @@ -83,6 +125,7 @@ fn detect_features() -> Features { is_bpf_cookie_supported(), is_prog_id_supported(BPF_MAP_TYPE_CPUMAP), is_prog_id_supported(BPF_MAP_TYPE_DEVMAP), + is_bpf_syscall_wrapper_supported(), btf, ); debug!("BPF Feature Detection: {f:#?}"); @@ -94,6 +137,133 @@ pub fn features() -> &'static Features { &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 + }; + + // Mirror libbpf's virtual __kconfig externs, see + // https://github.com/libbpf/libbpf/blob/libbpf-1.6.2/src/libbpf.c#L8465-L8468 + 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 parts = line.split_once('='); + let (key, raw_value) = match parts { + Some((key, value)) => (key, value), + _ => continue, + }; + + let value = match raw_value.chars().next() { + Some('n') => 0u64.to_ne_bytes().to_vec(), + Some('y') => 1u64.to_ne_bytes().to_vec(), + Some('m') => 2u64.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() + .iter() + .chain([0].iter()) + .copied() + .collect() + } + 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 @@ -130,6 +300,7 @@ pub struct EbpfLoader<'a> { extensions: HashSet<&'a str>, verifier_log_level: VerifierLogLevel, allow_unsupported_maps: bool, + kconfig: Option, } /// Builder style API for advanced loading of eBPF programs. @@ -169,9 +340,31 @@ impl<'a> EbpfLoader<'a> { extensions: HashSet::new(), verifier_log_level: VerifierLogLevel::default(), allow_unsupported_maps: false, + kconfig: None, } } + /// Sets the kernel configuration data for extern variables. + /// + /// This allows you to provide kernel configuration values that will be used to patch + /// extern variables in eBPF programs. If not set, extern variables will not be patched. + /// + /// # Examples + /// + /// ```no_run + /// use aya::{EbpfLoader, KConfig}; + /// + /// let kconfig = KConfig::current(); + /// let bpf = EbpfLoader::new() + /// .kconfig(kconfig) + /// .load_file("file.o")?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn kconfig(mut self, kconfig: KConfig) -> Self { + self.kconfig = Some(kconfig); + self + } + /// Sets the target [BTF](Btf) info. /// /// The loader defaults to loading `BTF` info using [Btf::from_sys_fs]. @@ -440,9 +633,13 @@ impl<'a> EbpfLoader<'a> { verifier_log_level, allow_unsupported_maps, map_pin_path_by_name, + kconfig, } = self; let mut obj = Object::parse(data)?; obj.patch_map_data(globals.clone())?; + if let Some(kconfig) = &kconfig { + obj.patch_extern_data(kconfig.as_map())?; + } 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 9e151ad8..ded95a44 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 0eecfc2c..4b2c756e 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -197,6 +197,16 @@ fn create_as_probe( .map_err(Into::into) } +/// Test helper to check if a kprobe can be created for the given function name. +/// This is used for capability detection. +pub(crate) fn test_kprobe_creation( + fn_name: &OsStr, + offset: u64, +) -> Result { + use crate::programs::KProbe; + create_as_probe::(ProbeKind::Entry, fn_name, offset, None) +} + fn create_as_trace_point( kind: ProbeKind, name: &OsStr, diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 7a6a0ef2..8105d7d5 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -1,6 +1,6 @@ use std::{ cmp, - ffi::{CStr, CString, c_char}, + ffi::{CStr, CString, OsStr, c_char}, fmt, io, iter, mem::{self, MaybeUninit}, os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, FromRawFd as _, RawFd}, @@ -32,7 +32,7 @@ use log::warn; use crate::{ Btf, Pod, VerifierLogLevel, maps::{MapData, PerCpuValues}, - programs::{LsmAttachType, ProgramType, links::LinkRef}, + programs::{LsmAttachType, ProgramType, links::LinkRef, probe::test_kprobe_creation}, sys::{Syscall, SyscallError, syscall}, util::KernelVersion, }; @@ -1014,6 +1014,48 @@ pub(crate) fn is_prog_id_supported(map_type: bpf_map_type) -> bool { bpf_map_create(&mut attr).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()); + + if cfg!(miri) { + // Miri runs with isolation; avoid filesystem/probe checks. + return true; + } + + return test_kprobe_creation(syscall_name, 0).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 ee6a3af9..fdd9b633 100644 --- a/aya/src/sys/mod.rs +++ b/aya/src/sys/mod.rs @@ -237,3 +237,26 @@ impl From for aya_obj::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 = std::mem::zeroed::(); + if libc::uname(std::ptr::from_mut(&mut v)) != 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 00000000..4d5ca497 --- /dev/null +++ b/test/integration-test/bpf/kconfig.bpf.c @@ -0,0 +1,48 @@ +// clang-format off +#include +#include +// clang-format on + +// Test char (1 byte alignment) +// CONFIG_BPF=y => 1 +extern unsigned char CONFIG_BPF __kconfig; +// Test short (2 byte alignment) +// CONFIG_PANIC_TIMEOUT=0 => 0 +extern unsigned short CONFIG_PANIC_TIMEOUT __kconfig; +// Test int (4 byte alignment) +// CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 +extern unsigned int CONFIG_DEFAULT_HUNG_TASK_TIMEOUT __kconfig; +// Test long (8 byte alignment) +// CONFIG_BPF_JIT=y => 1 +extern unsigned long CONFIG_BPF_JIT __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; + } + + if (CONFIG_BPF_JIT != 1) { + 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 881cdd57..14d74a74 100644 --- a/test/integration-test/build.rs +++ b/test/integration-test/build.rs @@ -84,6 +84,7 @@ fn main() -> Result<()> { ("struct_flavors_reloc.bpf.c", true), ("text_64_64_reloc.c", false), ("variables_reloc.bpf.c", false), + ("kconfig.bpf.c", false), ]; const C_BPF_HEADERS: &[&str] = &["reloc.h", "struct_with_scalars.h"]; diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index 01e26f03..cade0830 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -11,6 +11,7 @@ bpf_file!( ITER_TASK => "iter.bpf.o", MAIN => "main.bpf.o", MULTIMAP_BTF => "multimap-btf.bpf.o", + KCONFIG => "kconfig.bpf.o", ENUM_SIGNED_32_RELOC_BPF => "enum_signed_32_reloc.bpf.o", ENUM_SIGNED_32_RELOC_BTF => "enum_signed_32_reloc.bpf.target.o", diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs index 8f6200c4..0cad66b8 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/src/tests/smoke.rs @@ -1,5 +1,5 @@ use aya::{ - Ebpf, EbpfLoader, + Ebpf, EbpfLoader, KConfig, programs::{Extension, TracePoint, Xdp, XdpFlags, tc}, util::KernelVersion, }; @@ -83,3 +83,22 @@ 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:?}, kconfig support for BPF requires 5.9.0" + ); + return; + } + let kconfig = KConfig::current(); + let mut bpf = EbpfLoader::new() + .kconfig(kconfig) + .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 5e59c5ea..b69849f8 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 @@ -8695,6 +8699,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 @@ -8801,6 +8806,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::has_btf_relocations(&self) -> bool pub fn aya_obj::Object::parse(data: &[u8]) -> core::result::Result @@ -9558,6 +9564,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 @@ -9664,6 +9671,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::has_btf_relocations(&self) -> bool pub fn aya_obj::Object::parse(data: &[u8]) -> core::result::Result diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 3b6618be..72d071e9 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -10731,6 +10731,7 @@ pub fn aya::EbpfLoader<'a>::allow_unsupported_maps(&mut self) -> &mut Self pub fn aya::EbpfLoader<'a>::btf(&mut self, btf: core::option::Option<&'a aya_obj::btf::btf::Btf>) -> &mut Self pub fn aya::EbpfLoader<'a>::default_map_pin_directory>(&mut self, path: P) -> &mut Self pub fn aya::EbpfLoader<'a>::extension(&mut self, name: &'a str) -> &mut Self +pub fn aya::EbpfLoader<'a>::kconfig(self, kconfig: aya::KConfig) -> Self pub fn aya::EbpfLoader<'a>::load(&mut self, data: &[u8]) -> core::result::Result pub fn aya::EbpfLoader<'a>::load_file>(&mut self, path: P) -> core::result::Result pub fn aya::EbpfLoader<'a>::map_max_entries(&mut self, name: &'a str, size: u32) -> &mut Self @@ -10793,6 +10794,41 @@ impl core::borrow::BorrowMut for aya::GlobalData<'a> where T: ?core::marke pub fn aya::GlobalData<'a>::borrow_mut(&mut self) -> &mut T impl core::convert::From for aya::GlobalData<'a> pub fn aya::GlobalData<'a>::from(t: T) -> T +pub struct aya::KConfig +impl aya::KConfig +pub fn aya::KConfig::current() -> Self +impl core::clone::Clone for aya::KConfig +pub fn aya::KConfig::clone(&self) -> aya::KConfig +impl core::fmt::Debug for aya::KConfig +pub fn aya::KConfig::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for aya::KConfig +impl core::marker::Send for aya::KConfig +impl core::marker::Sync for aya::KConfig +impl core::marker::Unpin for aya::KConfig +impl core::panic::unwind_safe::RefUnwindSafe for aya::KConfig +impl core::panic::unwind_safe::UnwindSafe for aya::KConfig +impl core::convert::Into for aya::KConfig where U: core::convert::From +pub fn aya::KConfig::into(self) -> U +impl core::convert::TryFrom for aya::KConfig where U: core::convert::Into +pub type aya::KConfig::Error = core::convert::Infallible +pub fn aya::KConfig::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::KConfig where U: core::convert::TryFrom +pub type aya::KConfig::Error = >::Error +pub fn aya::KConfig::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for aya::KConfig where T: core::clone::Clone +pub type aya::KConfig::Owned = T +pub fn aya::KConfig::clone_into(&self, target: &mut T) +pub fn aya::KConfig::to_owned(&self) -> T +impl core::any::Any for aya::KConfig where T: 'static + ?core::marker::Sized +pub fn aya::KConfig::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::KConfig where T: ?core::marker::Sized +pub fn aya::KConfig::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::KConfig where T: ?core::marker::Sized +pub fn aya::KConfig::borrow_mut(&mut self) -> &mut T +impl core::clone::CloneToUninit for aya::KConfig where T: core::clone::Clone +pub unsafe fn aya::KConfig::clone_to_uninit(&self, dest: *mut u8) +impl core::convert::From for aya::KConfig +pub fn aya::KConfig::from(t: T) -> T pub struct aya::VerifierLogLevel(_) impl aya::VerifierLogLevel pub const aya::VerifierLogLevel::DEBUG: Self diff --git a/xtask/src/run.rs b/xtask/src/run.rs index fe1ed92d..e15f1e16 100644 --- a/xtask/src/run.rs +++ b/xtask/src/run.rs @@ -735,6 +735,27 @@ pub(crate) 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(), + " 0644 0 0\n".as_bytes(), + ] { + stdin.deref().write_all(bytes).expect("write"); + } + } + // Must explicitly close to signal EOF. drop(stdin);