aya: Implement .kconfig support

Implement support for external data symbols (kconfig) following libbpf
logic.
pull/631/head
Mary 2 years ago
parent 242d8c33c4
commit 7ea7924d24

@ -19,14 +19,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;
@ -159,6 +162,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
@ -457,6 +474,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
@ -467,6 +535,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(),
})
}
pub(crate) fn fixup_and_sanitize(
&mut self,
section_infos: &HashMap<String, (SectionIndex, u64)>,
@ -544,14 +643,13 @@ 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 = match section_infos.get(&name) {
Some((_, size)) => size,
None => {
return Err(BtfError::UnknownSectionSize { section_name: name });
}
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 });
};
debug!("{} {}: fixup size to {}", kind, name, size);
fixed_ty.size = *size as u32;
// The Vec<btf_var_secinfo> contains BTF_KIND_VAR sections
// that need to have their offsets adjusted. To do this,
@ -580,6 +678,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
@ -684,6 +790,108 @@ 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 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<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: BpfSectionKind::Rodata,
symbol_index: None,
data,
}),
);
}
Ok(())
}
/// Fixes up and sanitizes BTF data.
///
/// Mostly, it removes unsupported types and works around LLVM behaviours.

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

@ -47,6 +47,7 @@ pub struct Features {
bpf_perf_link: bool,
bpf_global_data: bool,
bpf_cookie: bool,
bpf_syscall_wrapper: bool,
btf: Option<BtfFeatures>,
}
@ -58,6 +59,7 @@ impl Features {
bpf_perf_link: bool,
bpf_global_data: bool,
bpf_cookie: bool,
bpf_syscall_wrapper: bool,
btf: Option<BtfFeatures>,
) -> Self {
Self {
@ -66,6 +68,7 @@ impl Features {
bpf_perf_link,
bpf_global_data,
bpf_cookie,
bpf_syscall_wrapper,
btf,
}
}
@ -95,6 +98,11 @@ impl Features {
self.bpf_cookie
}
/// 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()
@ -598,6 +606,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);
@ -1525,6 +1535,8 @@ mod tests {
address,
size,
is_definition: false,
is_external: false,
is_weak: false,
kind: SymbolKind::Data,
},
);
@ -2371,6 +2383,8 @@ mod tests {
address: 0,
size: 3,
is_definition: true,
is_external: false,
is_weak: false,
kind: SymbolKind::Data,
},
);

@ -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,
}
@ -226,7 +228,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;
}
@ -380,10 +384,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
@ -527,6 +532,8 @@ mod test {
address,
size,
is_definition: false,
is_external: false,
is_weak: false,
kind: SymbolKind::Data,
}
}

@ -32,6 +32,7 @@ tokio = { version = "1.24.0", features = [
"rt-multi-thread",
"net",
], optional = true }
flate2 = "1.0"
[dev-dependencies]
futures = { version = "0.3.12", default-features = false, features = ["std"] }

@ -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, warn};
use thiserror::Error;
@ -33,13 +35,13 @@ use crate::{
SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp,
},
sys::{
bpf_load_btf, bpf_map_freeze, bpf_map_update_elem_ptr, is_bpf_cookie_supported,
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,
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, POSSIBLE_CPUS},
util::{bytes_of, bytes_of_slice, possible_cpus, KernelVersion, POSSIBLE_CPUS},
};
pub(crate) const BPF_OBJ_NAME_LEN: usize = 16;
@ -89,6 +91,7 @@ fn detect_features() -> Features {
is_perf_link_supported(),
is_bpf_global_data_supported(),
is_bpf_cookie_supported(),
is_bpf_syscall_wrapper_supported(),
btf,
);
debug!("BPF Feature Detection: {:#?}", f);
@ -100,6 +103,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
@ -386,6 +505,7 @@ impl<'a> BpfLoader<'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)? {

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

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

@ -7,7 +7,7 @@ use std::{
slice,
};
use crate::util::KernelVersion;
use crate::{programs::probe::create_as_probe, util::KernelVersion};
use libc::{c_char, c_long, close, ENOENT, ENOSPC};
use obj::{
maps::{bpf_map_def, LegacyMap},
@ -752,6 +752,51 @@ pub(crate) fn is_bpf_cookie_supported() -> bool {
}
}
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");

@ -64,3 +64,26 @@ fn syscall(call: Syscall) -> SysResult {
ret => Err((ret, io::Error::last_os_error())),
}
}
#[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())
}
}

@ -20,9 +20,12 @@ use libc::{if_nametoindex, sysconf, uname, utsname, _SC_PAGESIZE};
// 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,
}
/// An error encountered while fetching the current kernel version.

@ -0,0 +1,16 @@
#include <linux/bpf.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";

@ -44,6 +44,7 @@ fn main() {
("main.bpf.c", "main.bpf.o"),
("multimap-btf.bpf.c", "multimap-btf.bpf.o"),
("text_64_64_reloc.c", "text_64_64_reloc.o"),
("kconfig.bpf.c", "kconfig.bpf.o"),
];
let c_bpf_probes = C_BPF_PROBES

@ -6,6 +6,7 @@ pub const MULTIMAP_BTF: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/multimap-btf.bpf.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"));

@ -34,3 +34,16 @@ fn extension() {
let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap();
drop_.load(pass.fd().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 = Bpf::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