aya: Implement .kconfig support

Implement support for external data symbols (kconfig) following libbpf
logic.
pull/631/merge^2
Mary 1 year ago committed by Davide Bertola
parent fbb09304a2
commit a1fb9704a8

@ -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<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
@ -473,6 +539,37 @@ 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)?;
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)?;
if target_var_name == var_name {
if var.linkage != VarLinkage::Extern {
return Err(BtfError::InvalidExternalSymbol {
symbol_name: var_name.into(),
});
}
return Ok((sec_name.into(), var.clone()));
}
}
}
}
}
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 +707,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 +835,107 @@ 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(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::<u8>::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);
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<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.

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

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

@ -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<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;
}
@ -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(&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
@ -510,6 +515,8 @@ mod test {
address,
size,
is_definition: false,
is_external: false,
is_weak: false,
kind: SymbolKind::Data,
}
}

@ -23,7 +23,13 @@ libc = { workspace = true }
log = { workspace = true }
object = { workspace = true, features = ["elf", "read_core", "std", "write"] }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt"], optional = true }
tokio = { workspace = true, features = [
"rt",
"macros",
"rt-multi-thread",
"net",
], optional = true }
flate2 = "1.0"
[dev-dependencies]
tempfile = { workspace = true }

@ -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,122 @@ pub fn features() -> &'static Features {
&FEATURES
}
lazy_static! {
static ref KCONFIG_DEFINITION: HashMap<String, Vec<u8>> = 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<String, Vec<u8>> {
let mut result = HashMap::new();
if let Ok(KernelVersion {
major,
minor,
patch,
}) = KernelVersion::current()
{
result.insert(
"LINUX_KERNEL_VERSION".to_string(),
to_bytes((u64::from(major) << 16) + (u64::from(minor) << 8) + u64::from(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::<u64>() {
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
@ -399,6 +520,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)? {

@ -38,7 +38,6 @@
// modules we don't export
mod info;
mod probe;
mod utils;
// modules we explicitly export so their pub items (Links etc) get exported too
@ -57,6 +56,7 @@ pub mod lirc_mode2;
pub mod lsm;
pub mod perf_attach;
pub mod perf_event;
pub mod probe;
pub mod raw_trace_point;
pub mod sk_lookup;
pub mod sk_msg;

@ -1,3 +1,5 @@
//! Probes.
use std::{
ffi::{OsStr, OsString},
fmt::Write as _,
@ -145,7 +147,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,

@ -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,
@ -948,6 +949,49 @@ 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());
if let Ok(fd) = create_as_probe(crate::programs::ProbeKind::KProbe, syscall_name, 0, None) {
unsafe {
libc::close(fd.as_raw_fd());
}
return true;
}
}
false
}
pub(crate) fn is_btf_supported() -> bool {
let mut btf = Btf::new();
let name_offset = btf.add_string("int");

@ -191,3 +191,26 @@ impl From<Stats> for crate::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 = mem::zeroed::<utsname>();
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())
}
}

@ -23,9 +23,12 @@ use crate::{
// Adapted from https://docs.rs/procfs/latest/procfs/sys/kernel/struct.Version.html.
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd)]
pub struct KernelVersion {
pub(crate) major: u8,
pub(crate) minor: u8,
pub(crate) patch: u16,
/// Major version.
pub major: u8,
/// Minor version.
pub minor: u8,
/// Patch version.
pub patch: u16,
}
#[derive(thiserror::Error, Debug)]

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

@ -72,6 +72,7 @@ fn main() {
("multimap-btf.bpf.c", false),
("reloc.bpf.c", true),
("text_64_64_reloc.c", false),
("kconfig.bpf.c", false), // TODO: or true ?
];
if build_integration_bpf {

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

@ -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("pass").unwrap().try_into().unwrap();
pass.load().unwrap();
pass.attach("lo", XdpFlags::default()).unwrap();
}

Loading…
Cancel
Save