pull/1017/merge
Davide Bertola 3 days ago committed by GitHub
commit d5b2441f45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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<usize, BtfError> {
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<u8> {
// 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<String, Vec<u8>>,
) -> Result<Option<(SectionIndex, Vec<u8>)>, 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<String, (String, Var)> = 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::<u8>::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<String, Vec<u8>>,
) -> 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::<u32>() 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);
}
}

@ -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<u32> {
match self {
Self::Const(t) => Some(t.btf_type),

@ -45,6 +45,7 @@ pub struct Features {
bpf_cookie: bool,
cpumap_prog_id: bool,
devmap_prog_id: bool,
bpf_syscall_wrapper: bool,
btf: Option<BtfFeatures>,
}
@ -59,6 +60,7 @@ impl Features {
bpf_cookie: bool,
cpumap_prog_id: bool,
devmap_prog_id: bool,
bpf_syscall_wrapper: bool,
btf: Option<BtfFeatures>,
) -> 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,
},
);

@ -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<Item = &'a Relocation>>(
};
// calls and relocation to .text symbols are handled in a separate step
if insn_is_call(&instructions[ins_index]) || text_sections.contains(&section_index) {
if insn_is_call(&instructions[ins_index])
|| (text_sections.contains(&section_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(&section_index))
.unwrap_or(false)
|| (!sym.is_external
&& sym
.section_index
.map(|section_index| self.text_sections.contains(&section_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,
}
}

@ -21,6 +21,7 @@ assert_matches = { workspace = true }
aya-obj = { path = "../aya-obj", version = "^0.2.2", features = ["std"] }
bitflags = { workspace = true }
bytes = { workspace = true }
flate2 = "1.0"
hashbrown = { workspace = true }
libc = { workspace = true }
log = { workspace = true }

@ -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<Features> = 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<dyn std::error::Error>>(())
/// ```
#[derive(Debug, Clone)]
pub struct KConfig {
data: HashMap<String, Vec<u8>>,
}
impl KConfig {
/// Creates a new `KConfig` by reading kernel configuration from the system.
///
/// This will attempt to read `/proc/config.gz` or `/boot/config-<release>` 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<String, Vec<u8>> {
&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<String, Vec<u8>> {
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::<u64>() {
value.to_ne_bytes().to_vec()
} else {
continue;
}
}
None => continue,
};
result.insert(key.to_string(), value);
}
}
result
}
fn read_kconfig() -> Option<String> {
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<String> {
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<KConfig>,
}
/// 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<dyn std::error::Error>>(())
/// ```
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)? {

@ -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

@ -197,6 +197,16 @@ fn create_as_probe<P: 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<crate::MockableFd, ProgramError> {
use crate::programs::KProbe;
create_as_probe::<KProbe>(ProbeKind::Entry, fn_name, offset, None)
}
fn create_as_trace_point<P: Probe>(
kind: ProbeKind,
name: &OsStr,

@ -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");

@ -237,3 +237,26 @@ impl From<Stats> for aya_obj::generated::bpf_stats_type {
pub fn enable_stats(stats_type: Stats) -> Result<OwnedFd, SyscallError> {
bpf_enable_stats(stats_type.into()).map(|fd| fd.into_inner())
}
#[cfg(test)]
pub(crate) fn kernel_release() -> Result<String, ()> {
Ok("unknown".to_string())
}
#[cfg(not(test))]
pub(crate) fn kernel_release() -> Result<String, ()> {
use std::ffi::CStr;
use libc::utsname;
unsafe {
let mut v = std::mem::zeroed::<utsname>();
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())
}
}

@ -0,0 +1,48 @@
// clang-format off
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
// 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";

@ -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"];

@ -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",

@ -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();
}

@ -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<alloc::stri
pub aya_obj::obj::Object::programs: std::collections::hash::map::HashMap<alloc::string::String, aya_obj::Program>
impl aya_obj::Object
pub fn aya_obj::Object::fixup_and_sanitize_btf(&mut self, features: &aya_obj::btf::BtfFeatures) -> core::result::Result<core::option::Option<&aya_obj::btf::Btf>, aya_obj::btf::BtfError>
pub fn aya_obj::Object::patch_extern_data(&mut self, externs: &std::collections::hash::map::HashMap<alloc::string::String, alloc::vec::Vec<u8>>) -> 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<Self, aya_obj::ParseError>
@ -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<alloc::string::S
pub aya_obj::Object::programs: std::collections::hash::map::HashMap<alloc::string::String, aya_obj::Program>
impl aya_obj::Object
pub fn aya_obj::Object::fixup_and_sanitize_btf(&mut self, features: &aya_obj::btf::BtfFeatures) -> core::result::Result<core::option::Option<&aya_obj::btf::Btf>, aya_obj::btf::BtfError>
pub fn aya_obj::Object::patch_extern_data(&mut self, externs: &std::collections::hash::map::HashMap<alloc::string::String, alloc::vec::Vec<u8>>) -> 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<Self, aya_obj::ParseError>

@ -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<P: core::convert::AsRef<std::path::Path>>(&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<aya::Ebpf, aya::EbpfError>
pub fn aya::EbpfLoader<'a>::load_file<P: core::convert::AsRef<std::path::Path>>(&mut self, path: P) -> core::result::Result<aya::Ebpf, aya::EbpfError>
pub fn aya::EbpfLoader<'a>::map_max_entries(&mut self, name: &'a str, size: u32) -> &mut Self
@ -10793,6 +10794,41 @@ impl<T> core::borrow::BorrowMut<T> for aya::GlobalData<'a> where T: ?core::marke
pub fn aya::GlobalData<'a>::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> 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<T, U> core::convert::Into<U> for aya::KConfig where U: core::convert::From<T>
pub fn aya::KConfig::into(self) -> U
impl<T, U> core::convert::TryFrom<U> for aya::KConfig where U: core::convert::Into<T>
pub type aya::KConfig::Error = core::convert::Infallible
pub fn aya::KConfig::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
impl<T, U> core::convert::TryInto<U> for aya::KConfig where U: core::convert::TryFrom<T>
pub type aya::KConfig::Error = <U as core::convert::TryFrom<T>>::Error
pub fn aya::KConfig::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
impl<T> 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<T> core::any::Any for aya::KConfig where T: 'static + ?core::marker::Sized
pub fn aya::KConfig::type_id(&self) -> core::any::TypeId
impl<T> core::borrow::Borrow<T> for aya::KConfig where T: ?core::marker::Sized
pub fn aya::KConfig::borrow(&self) -> &T
impl<T> core::borrow::BorrowMut<T> for aya::KConfig where T: ?core::marker::Sized
pub fn aya::KConfig::borrow_mut(&mut self) -> &mut T
impl<T> core::clone::CloneToUninit for aya::KConfig where T: core::clone::Clone
pub unsafe fn aya::KConfig::clone_to_uninit(&self, dest: *mut u8)
impl<T> core::convert::From<T> for aya::KConfig
pub fn aya::KConfig::from(t: T) -> T
pub struct aya::VerifierLogLevel(_)
impl aya::VerifierLogLevel
pub const aya::VerifierLogLevel::DEBUG: Self

@ -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);

Loading…
Cancel
Save