diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs index 360079f01..e4fcb24e6 100644 --- a/aya-obj/src/btf/btf.rs +++ b/aya-obj/src/btf/btf.rs @@ -20,14 +20,17 @@ use crate::{ Array, BtfEnum, BtfKind, BtfMember, BtfType, Const, Enum, FuncInfo, FuncLinkage, Int, IntEncoding, LineInfo, Struct, Typedef, 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, + BpfSectionKind, Map, Object, }; #[cfg(not(feature = "std"))] use crate::std; +use super::{Union, Var}; + pub(crate) const MAX_RESOLVE_DEPTH: u8 = 32; pub(crate) const MAX_SPEC_LEN: usize = 64; @@ -160,6 +163,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 @@ -402,6 +419,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 @@ -412,6 +480,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 { + if let BtfType::DataSec(d) = t { + let sec_name = self.string_at(d.name_offset)?; + let name = sec_name.to_string(); + + for d in &d.entries { + if let BtfType::Var(var) = self.types.type_by_id(d.btf_type)? { + let var_name = self.string_at(var.name_offset)?.to_string(); + + if target_var_name == var_name { + if var.linkage != VarLinkage::Extern { + return Err(BtfError::InvalidExternalSymbol { + symbol_name: var_name, + }); + } + + return Ok((name, var.clone())); + } + } + } + } + } + + Err(BtfError::ExternalSymbolNotFound { + symbol_name: target_var_name.into(), + }) + } + pub(crate) fn fixup_and_sanitize( &mut self, section_infos: &HashMap, @@ -490,13 +590,15 @@ impl Btf { // We need to get the size of the section from the ELF file // Fortunately, we cached these when parsing it initially // and we can this up by name in section_infos - let (_, size) = section_infos.get(&name).ok_or_else(|| { - BtfError::UnknownSectionSize { + + if let Some((_, size)) = section_infos.get(&name) { + debug!("{} {}: fixup size to {}", kind, name, size); + fixed_ty.size = *size as u32; + } else if name != ".kconfig" && name != ".ksyms" { + return Err(BtfError::UnknownSectionSize { section_name: name.clone(), - } - })?; - debug!("{} {}: fixup size to {}", kind, name, size); - fixed_ty.size = *size as u32; + }); + }; // The Vec contains BTF_KIND_VAR sections // that need to have their offsets adjusted. To do this, @@ -522,6 +624,14 @@ impl Btf { }, )?; d.offset = *offset as u32; + + if var.linkage == VarLinkage::Extern { + let mut var = var.clone(); + var.linkage = VarLinkage::Global; + + types.types[d.btf_type as usize] = BtfType::Var(var); + } + debug!( "{} {}: {} {}: fixup offset {}", kind, name, var_kind, var_name, offset @@ -626,6 +736,108 @@ 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 mut empty_data = Vec::new(); + empty_data.resize(type_size, 0u8); + + 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); + section_data.extend(data); + offset = symbol.address + 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 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: BpfSectionKind::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 dcd869fe8..091f97131 100644 --- a/aya-obj/src/btf/types.rs +++ b/aya-obj/src/btf/types.rs @@ -1112,11 +1112,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 0a7235fdf..07f4c3f48 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -46,6 +46,8 @@ pub struct Features { pub bpf_probe_read_kernel: bool, pub bpf_perf_link: bool, pub bpf_global_data: bool, + pub bpf_cookie: bool, + pub bpf_syscall_wrapper: bool, pub btf: Option, } @@ -546,6 +548,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); @@ -1487,6 +1491,8 @@ mod tests { address, size, is_definition: false, + is_external: false, + is_weak: false, kind: SymbolKind::Data, }, ); @@ -2349,6 +2355,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 7fb3bc0dd..f2ac6947e 100644 --- a/aya-obj/src/relocation.rs +++ b/aya-obj/src/relocation.rs @@ -109,6 +109,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, } @@ -530,6 +532,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 f12ebf278..bd6f82c1c 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -22,6 +22,7 @@ parking_lot = { version = "0.12.0", features = ["send_guard"] } tokio = { version = "1.24.0", features = ["macros", "rt", "rt-multi-thread", "net"], optional = true } async-io = { version = "1.3", optional = true } log = "0.4" +flate2 = "1.0" [dev-dependencies] matches = "0.1.8" diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 2b499c123..6a9baa64f 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -2,7 +2,8 @@ use std::{ borrow::Cow, collections::{HashMap, HashSet}, ffi::CString, - fs, io, + fs::{self, File}, + io::{self, Read}, os::{raw::c_int, unix::io::RawFd}, path::{Path, PathBuf}, }; @@ -13,6 +14,7 @@ use aya_obj::{ relocation::BpfRelocationError, BpfSectionKind, Features, }; +use flate2::read::GzDecoder; use log::debug; use thiserror::Error; @@ -33,11 +35,11 @@ use crate::{ SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, }, sys::{ - bpf_load_btf, bpf_map_freeze, bpf_map_update_elem_ptr, is_bpf_global_data_supported, - is_btf_datasec_supported, is_btf_decl_tag_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_name_supported, retry_with_verifier_logs, + self, bpf_load_btf, bpf_map_freeze, bpf_map_update_elem_ptr, 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_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_name_supported, retry_with_verifier_logs, }, util::{bytes_of, bytes_of_slice, possible_cpus, VerifierLog, POSSIBLE_CPUS}, }; @@ -88,12 +90,124 @@ fn detect_features() -> Features { bpf_probe_read_kernel: is_probe_read_kernel_supported(), bpf_perf_link: is_perf_link_supported(), bpf_global_data: is_bpf_global_data_supported(), + bpf_cookie: is_bpf_cookie_supported(), + bpf_syscall_wrapper: is_bpf_syscall_wrapper_supported(), btf, }; debug!("BPF Feature Detection: {:#?}", f); f } +lazy_static! { + static ref KCONFIG_DEFINITION: HashMap> = compute_kconfig_definition(&FEATURES); +} + +fn to_bytes(value: u64) -> [u8; 8] { + if cfg!(target_endian = "big") { + value.to_be_bytes() + } else { + value.to_le_bytes() + } +} + +fn compute_kconfig_definition(features: &Features) -> HashMap> { + let mut result = HashMap::new(); + + if let Ok((major, minor, patch)) = sys::kernel_version() { + result.insert( + "LINUX_KERNEL_VERSION".to_string(), + to_bytes(u64::from((major << 16) + (minor << 8) + patch)).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(), + to_bytes(bpf_cookie).to_vec(), + ); + + result.insert( + "LINUX_HAS_SYSCALL_WRAPPER".to_string(), + to_bytes(bpf_syscall_wrapper).to_vec(), + ); + + let mut raw_config_opt = None; + + let proc_config_path = PathBuf::from("/proc/config.gz"); + + if proc_config_path.exists() { + if let Ok(file) = File::open(proc_config_path) { + let mut file = GzDecoder::new(file); + let mut output = String::new(); + if file.read_to_string(&mut output).is_ok() { + raw_config_opt = Some(output); + } + } + } + + if raw_config_opt.is_none() { + if let Ok(release) = sys::kernel_release() { + let config_path = PathBuf::from("/boot").join(format!("config-{}", release)); + + if config_path.exists() { + if let Ok(mut file) = File::open(config_path) { + let mut output = String::new(); + if file.read_to_string(&mut output).is_ok() { + raw_config_opt = Some(output); + } + } + } + } + } + + if let Some(raw_config) = raw_config_opt { + for line in raw_config.split('\n') { + 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') => to_bytes(0).to_vec(), + Some('y') => to_bytes(1).to_vec(), + Some('m') => to_bytes(2).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::() { + to_bytes(value).to_vec() + } else { + continue; + } + } + None => continue, + }; + + result.insert(key.to_string(), value); + } + } + + result +} + /// Builder style API for advanced loading of eBPF programs. /// /// Loading eBPF code involves a few steps, including loading maps and applying @@ -343,6 +457,7 @@ impl<'a> BpfLoader<'a> { let verifier_log_level = self.verifier_log_level.bits(); let mut obj = Object::parse(data)?; obj.patch_map_data(self.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 d903cca76..c24933470 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -50,7 +50,7 @@ pub mod lirc_mode2; pub mod lsm; pub mod perf_attach; pub mod perf_event; -mod probe; +pub(crate) mod probe; mod raw_trace_point; mod sk_lookup; mod sk_msg; diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs index 4ecf80a5c..728f04f90 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -81,7 +81,7 @@ pub(crate) fn detach_debug_fs(kind: ProbeKind, event_alias: &str) -> Result<(), Ok(()) } -fn create_as_probe( +pub(crate) fn create_as_probe( kind: ProbeKind, fn_name: &str, offset: u64, diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index aac2e1b94..946c24c49 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -27,6 +27,7 @@ use crate::{ }, copy_instructions, }, + programs::probe::create_as_probe, sys::{kernel_version, syscall, SysResult, Syscall}, util::VerifierLog, Btf, Pod, BPF_OBJ_NAME_LEN, @@ -719,6 +720,78 @@ pub(crate) fn is_bpf_global_data_supported() -> bool { false } +pub(crate) fn is_bpf_cookie_supported() -> bool { + let mut attr = unsafe { mem::zeroed::() }; + let u = unsafe { &mut attr.__bindgen_anon_3 }; + + let prog: &[u8] = &[ + 0x85, 0x00, 0x00, 0x00, 0xAE, 0x00, 0x00, 0x00, // call 174 + 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit + ]; + + let gpl = b"GPL\0"; + u.license = gpl.as_ptr() as u64; + + let insns = copy_instructions(prog).unwrap(); + u.insn_cnt = insns.len() as u32; + u.insns = insns.as_ptr() as u64; + u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32; + + match sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr) { + Ok(v) => { + let fd = v as RawFd; + unsafe { close(fd) }; + true + } + Err(_) => false, + } +} + +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 = "s390") { + Some("s390") + } 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); + + if let Ok(fd) = create_as_probe(crate::programs::ProbeKind::KProbe, &syscall_name, 0, None) + { + unsafe { + libc::close(fd); + } + + return true; + } + } + + false +} + pub(crate) fn is_btf_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("int".to_string()); diff --git a/aya/src/sys/mod.rs b/aya/src/sys/mod.rs index 294ff3860..02f10bc7a 100644 --- a/aya/src/sys/mod.rs +++ b/aya/src/sys/mod.rs @@ -7,7 +7,10 @@ mod fake; use std::io; #[cfg(not(test))] -use std::{ffi::CString, mem}; +use std::{ + ffi::{CStr, CString}, + mem, +}; #[cfg(not(test))] use std::{fs::File, io::Read}; @@ -169,3 +172,22 @@ pub(crate) fn kernel_version() -> Result<(u32, u32, u32), ()> { Ok((major, minor, patch)) } } + +#[cfg(test)] +pub(crate) fn kernel_release() -> Result { + Ok("unknown".to_string()) +} + +#[cfg(not(test))] +pub(crate) fn kernel_release() -> Result { + 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-ebpf/src/bpf/kconfig.bpf.c b/test/integration-ebpf/src/bpf/kconfig.bpf.c new file mode 100644 index 000000000..4526d3fbb --- /dev/null +++ b/test/integration-ebpf/src/bpf/kconfig.bpf.c @@ -0,0 +1,16 @@ +#include +#include + +extern unsigned int CONFIG_BPF __kconfig; + +SEC("xdp/pass") +int xdp_pass(struct xdp_md *ctx) +{ + if (!CONFIG_BPF) { + return XDP_DROP; + } + + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs index 17abd79bf..4ffd34a55 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/src/tests/smoke.rs @@ -40,3 +40,22 @@ fn extension() { let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap(); drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap(); } + +#[integration_test] +fn kconfig() { + let (major, minor, _) = kernel_version().unwrap(); + if major < 5 || (minor == 5 && minor < 9) { + info!( + "skipping as {}.{} does not meet version requirement of 5.9", + major, minor + ); + return; + } + // TODO: Check kernel version == 5.9 or later + let main_bytes = + include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/kconfig.bpf.o"); + let mut bpf = Bpf::load(main_bytes).unwrap(); + let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); + pass.load().unwrap(); + pass.attach("lo", XdpFlags::default()).unwrap(); +}