Compare commits

..

2 Commits

Author SHA1 Message Date
Tyrone Wu dd5f2c1fb7
aya,aya-obj: cache feat probed info fields
Cached probed for ProgramInfo fields instead of exposing it through
global FEATURE. Probing occurs on cache miss, which happens when first
accessing the field, *and* if the field is 0.
Tyrone Wu e2da6f8eb7
aya: add feature probing for map type
Add API that probes whether kernel supports a map type.

@ -162,52 +162,86 @@ pub fn is_program_supported(program_type: ProgramType) -> Result<bool, ProgramEr
/// Note that certain errors are expected and handled internally; only
/// unanticipated failures during probing will result in this error.
pub fn is_map_supported(map_type: MapType) -> Result<bool, SyscallError> {
if map_type == MapType::Unspecified {
return Ok(false);
}
// Each `bpf_map_ops` struct contains their own `.map_alloc()` & `.map_alloc_check()` that does
// field validation on map_create.
let (key_size, value_size, max_entries) = match map_type {
MapType::Unspecified => return Ok(false),
MapType::Hash // https://elixir.bootlin.com/linux/v3.19/source/kernel/bpf/hashtab.c#L349
| MapType::PerCpuHash // https://elixir.bootlin.com/linux/v4.6/source/kernel/bpf/hashtab.c#L726
| MapType::LruHash // https://elixir.bootlin.com/linux/v4.10/source/kernel/bpf/hashtab.c#L1032
| MapType::LruPerCpuHash // https://elixir.bootlin.com/linux/v4.10/source/kernel/bpf/hashtab.c#L1133
=> (1, 1, 1),
MapType::Array // https://elixir.bootlin.com/linux/v3.19/source/kernel/bpf/arraymap.c#L138
| MapType::PerCpuArray // https://elixir.bootlin.com/linux/v4.6/source/kernel/bpf/arraymap.c#L283
=> (4, 1, 1),
MapType::ProgramArray // https://elixir.bootlin.com/linux/v4.2/source/kernel/bpf/arraymap.c#L239
| MapType::PerfEventArray // https://elixir.bootlin.com/linux/v4.3/source/kernel/bpf/arraymap.c#L312
| MapType::CgroupArray // https://elixir.bootlin.com/linux/v4.8/source/kernel/bpf/arraymap.c#L562
| MapType::ArrayOfMaps // https://elixir.bootlin.com/linux/v4.12/source/kernel/bpf/arraymap.c#L595
| MapType::DevMap // https://elixir.bootlin.com/linux/v4.14/source/kernel/bpf/devmap.c#L360
| MapType::SockMap // https://elixir.bootlin.com/linux/v4.14/source/kernel/bpf/sockmap.c#L874
| MapType::CpuMap // https://elixir.bootlin.com/linux/v4.15/source/kernel/bpf/cpumap.c#L589
| MapType::XskMap // https://elixir.bootlin.com/linux/v4.18/source/kernel/bpf/xskmap.c#L224
| MapType::ReuseportSockArray // https://elixir.bootlin.com/linux/v4.20/source/kernel/bpf/reuseport_array.c#L357
| MapType::DevMapHash // https://elixir.bootlin.com/linux/v5.4/source/kernel/bpf/devmap.c#L713
=> (4, 4, 1),
MapType::StackTrace // https://elixir.bootlin.com/linux/v4.6/source/kernel/bpf/stackmap.c#L272
=> (4, 8, 1),
MapType::LpmTrie // https://elixir.bootlin.com/linux/v4.11/source/kernel/bpf/lpm_trie.c#L509
=> (8, 1, 1),
MapType::HashOfMaps // https://elixir.bootlin.com/linux/v4.12/source/kernel/bpf/hashtab.c#L1301
| MapType::SockHash // https://elixir.bootlin.com/linux/v4.18/source/kernel/bpf/sockmap.c#L2507
=> (1, 4, 1),
MapType::CgroupStorage // https://elixir.bootlin.com/linux/v4.19/source/kernel/bpf/local_storage.c#L246
| MapType::PerCpuCgroupStorage // https://elixir.bootlin.com/linux/v4.20/source/kernel/bpf/local_storage.c#L313
=> (16, 1, 0),
MapType::Queue // https://elixir.bootlin.com/linux/v4.20/source/kernel/bpf/queue_stack_maps.c#L267
| MapType::Stack // https://elixir.bootlin.com/linux/v4.20/source/kernel/bpf/queue_stack_maps.c#L280
| MapType::BloomFilter // https://elixir.bootlin.com/linux/v5.16/source/kernel/bpf/bloom_filter.c#L193
=> (0, 1, 1),
MapType::SkStorage // https://elixir.bootlin.com/linux/v5.2/source/net/core/bpf_sk_storage.c#L779
| MapType::InodeStorage // https://elixir.bootlin.com/linux/v5.10/source/kernel/bpf/bpf_inode_storage.c#L239
| MapType::TaskStorage // https://elixir.bootlin.com/linux/v5.11/source/kernel/bpf/bpf_task_storage.c#L285
| MapType::CgrpStorage // https://elixir.bootlin.com/linux/v6.2/source/kernel/bpf/bpf_cgrp_storage.c#L216
=> (4, 1, 0),
MapType::StructOps // https://elixir.bootlin.com/linux/v5.6/source/kernel/bpf/bpf_struct_ops.c#L607
=> (4, 0, 1),
MapType::RingBuf // https://elixir.bootlin.com/linux/v5.8/source/kernel/bpf/ringbuf.c#L296
| MapType::UserRingBuf // https://elixir.bootlin.com/linux/v6.1/source/kernel/bpf/ringbuf.c#L356
// `max_entries` required to be multiple of kernel page size & power of 2: https://elixir.bootlin.com/linux/v5.8/source/kernel/bpf/ringbuf.c#L160
=> (0, 0, page_size() as u32),
MapType::Arena // https://elixir.bootlin.com/linux/v6.9/source/kernel/bpf/arena.c#L380
=> (0, 0, 1),
};
// SAFETY: all-zero byte-pattern valid for `bpf_attr`
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
// SAFETY: union access
let u = unsafe { &mut attr.__bindgen_anon_1 };
u.map_type = map_type as u32;
u.key_size = key_size;
u.value_size = value_size;
u.max_entries = max_entries;
// To pass `map_alloc_check`/`map_alloc`
let key_size = match map_type {
MapType::LpmTrie | MapType::CgroupStorage | MapType::PerCpuCgroupStorage => 16,
MapType::Queue
| MapType::Stack
| MapType::RingBuf
| MapType::BloomFilter
| MapType::UserRingBuf
| MapType::Arena => 0,
_ => 4,
};
let value_size = match map_type {
MapType::StackTrace | MapType::LpmTrie => 8,
MapType::RingBuf | MapType::UserRingBuf | MapType::Arena => 0,
_ => 4,
};
let max_entries = match map_type {
MapType::CgroupStorage
| MapType::PerCpuCgroupStorage
| MapType::SkStorage
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage => 0,
MapType::RingBuf | MapType::UserRingBuf => page_size() as u32,
_ => 1,
};
// Ensure that fd doesn't get dropped due to scoping.
let inner_map_fd;
// Ensure that fd doesn't get dropped due to scoping for for *_of_maps type.
let inner_map_fd: MockableFd;
match map_type {
// lpm_trie required to not be pre-alloced: https://elixir.bootlin.com/linux/v4.11/source/kernel/bpf/lpm_trie.c#L419
MapType::LpmTrie => u.map_flags = BPF_F_NO_PREALLOC,
// For these types, we aim to intentionally trigger `EBADF` by supplying invalid btf attach
// data to verify the map type's existance. Otherwise, negative support will produce
// `EINVAL` instead.
MapType::SkStorage
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage => {
// These types required to not be pre-alloced:
// - sk_storage: https://elixir.bootlin.com/linux/v5.2/source/net/core/bpf_sk_storage.c#L604
// - inode_storage: https://elixir.bootlin.com/linux/v5.10/source/kernel/bpf/bpf_local_storage.c#L525
// - task_storage: https://elixir.bootlin.com/linux/v5.11/source/kernel/bpf/bpf_local_storage.c#L527
// - cgrp_storage: https://elixir.bootlin.com/linux/v6.2/source/kernel/bpf/bpf_local_storage.c#L539
u.map_flags = BPF_F_NO_PREALLOC;
// Intentionally trigger `EBADF` from `btf_get_by_fd()`.
// Will trigger `EBADF` from `btf_get_by_fd()` https://elixir.bootlin.com/linux/v5.2/source/kernel/bpf/btf.c#L3428
u.btf_fd = u32::MAX;
u.btf_key_type_id = 1;
u.btf_value_type_id = 1;
@ -231,23 +265,27 @@ pub fn is_map_supported(map_type: MapType) -> Result<bool, SyscallError> {
u.inner_map_fd = inner_map_fd.as_raw_fd() as u32;
}
// We aim to intentionally trigger `ENOTSUPP` by setting an invalid, non-zero
// `btf_vmlinux_value_type_id`. Negative support produce `EINVAL` instead.
MapType::StructOps => u.btf_vmlinux_value_type_id = 1,
// arena required to be mmapable: https://elixir.bootlin.com/linux/v6.9/source/kernel/bpf/arena.c#L103
MapType::Arena => u.map_flags = BPF_F_MMAPABLE,
_ => {}
}
u.map_type = map_type as u32;
u.key_size = key_size;
u.value_size = value_size;
u.max_entries = max_entries;
// SAFETY: BPF_MAP_CREATE returns a new file descriptor.
let io_error = match unsafe { fd_sys_bpf(bpf_cmd::BPF_MAP_CREATE, &mut attr) } {
Ok(_) => return Ok(true),
Err(io_error) => io_error,
};
// sk_storage, struct_ops, inode_storage, task_storage, & cgrp_storage requires further
// examination to verify support.
match io_error.raw_os_error() {
Some(EINVAL) => Ok(false),
// These types use fields that may not exist at the kernel's current version.
// Supplying `bpf_attr` fields unknown to the kernel triggers `E2BIG` from
// `bpf_check_uarg_tail_zero()` https://elixir.bootlin.com/linux/v4.18/source/kernel/bpf/syscall.c#L71.
Some(E2BIG)
if matches!(
map_type,
@ -260,6 +298,10 @@ pub fn is_map_supported(map_type: MapType) -> Result<bool, SyscallError> {
{
Ok(false)
}
// For these types, `EBADF` from `btf_get_by_fd()` https://elixir.bootlin.com/linux/v5.2/source/kernel/bpf/btf.c#L3428
// indicates that map_create advanced far enough in the validation to recognize the type
// before being rejected.
// Otherwise, negative support produces `EINVAL`, meaning it was immediately rejected.
Some(EBADF)
if matches!(
map_type,
@ -271,7 +313,10 @@ pub fn is_map_supported(map_type: MapType) -> Result<bool, SyscallError> {
{
Ok(true)
}
// `ENOTSUPP` from `bpf_struct_ops_map_alloc()` for struct_ops.
// `ENOTSUPP` from `bpf_struct_ops_map_alloc()` https://elixir.bootlin.com/linux/v5.6/source/kernel/bpf/bpf_struct_ops.c#L557
// indicates that map_create advanced far enough in the validation to recognize the type
// before being rejected.
// Otherwise, negative support produces `EINVAL`, meaning it was immediately rejected.
Some(524) if map_type == MapType::StructOps => Ok(true),
_ => Err(SyscallError {
call: "bpf_map_create",

@ -138,89 +138,88 @@ fn probe_supported_programs() {
#[test]
fn probe_supported_maps() {
let mut kern_version: KernelVersion;
macro_rules! is_supported {
($map_type:expr) => {
is_map_supported($map_type).unwrap()
};
}
kern_version = KernelVersion::new(3, 19, 0);
let kern_version = KernelVersion::new(3, 19, 0);
kernel_assert!(is_supported!(MapType::Hash), kern_version);
kernel_assert!(is_supported!(MapType::Array), kern_version);
kern_version = KernelVersion::new(4, 2, 0);
let kern_version = KernelVersion::new(4, 2, 0);
kernel_assert!(is_supported!(MapType::ProgramArray), kern_version);
kern_version = KernelVersion::new(4, 3, 0);
let kern_version = KernelVersion::new(4, 3, 0);
kernel_assert!(is_supported!(MapType::PerfEventArray), kern_version);
kern_version = KernelVersion::new(4, 6, 0);
let kern_version = KernelVersion::new(4, 6, 0);
kernel_assert!(is_supported!(MapType::PerCpuHash), kern_version);
kernel_assert!(is_supported!(MapType::PerCpuArray), kern_version);
kernel_assert!(is_supported!(MapType::StackTrace), kern_version);
kern_version = KernelVersion::new(4, 8, 0);
let kern_version = KernelVersion::new(4, 8, 0);
kernel_assert!(is_supported!(MapType::CgroupArray), kern_version);
kern_version = KernelVersion::new(4, 10, 0);
let kern_version = KernelVersion::new(4, 10, 0);
kernel_assert!(is_supported!(MapType::LruHash), kern_version);
kernel_assert!(is_supported!(MapType::LruPerCpuHash), kern_version);
kern_version = KernelVersion::new(4, 11, 0);
let kern_version = KernelVersion::new(4, 11, 0);
kernel_assert!(is_supported!(MapType::LpmTrie), kern_version);
kern_version = KernelVersion::new(4, 12, 0);
let kern_version = KernelVersion::new(4, 12, 0);
kernel_assert!(is_supported!(MapType::ArrayOfMaps), kern_version);
kernel_assert!(is_supported!(MapType::HashOfMaps), kern_version);
kern_version = KernelVersion::new(4, 14, 0);
let kern_version = KernelVersion::new(4, 14, 0);
kernel_assert!(is_supported!(MapType::DevMap), kern_version);
kernel_assert!(is_supported!(MapType::SockMap), kern_version);
kern_version = KernelVersion::new(4, 15, 0);
let kern_version = KernelVersion::new(4, 15, 0);
kernel_assert!(is_supported!(MapType::CpuMap), kern_version);
kern_version = KernelVersion::new(4, 18, 0);
let kern_version = KernelVersion::new(4, 18, 0);
kernel_assert!(is_supported!(MapType::XskMap), kern_version);
kernel_assert!(is_supported!(MapType::SockHash), kern_version);
kern_version = KernelVersion::new(4, 19, 0);
let kern_version = KernelVersion::new(4, 19, 0);
kernel_assert!(is_supported!(MapType::CgroupStorage), kern_version);
kernel_assert!(is_supported!(MapType::ReuseportSockArray), kern_version);
kern_version = KernelVersion::new(4, 20, 0);
let kern_version = KernelVersion::new(4, 20, 0);
kernel_assert!(is_supported!(MapType::PerCpuCgroupStorage), kern_version);
kernel_assert!(is_supported!(MapType::Queue), kern_version);
kernel_assert!(is_supported!(MapType::Stack), kern_version);
kern_version = KernelVersion::new(5, 2, 0);
let kern_version = KernelVersion::new(5, 2, 0);
kernel_assert!(is_supported!(MapType::SkStorage), kern_version);
kern_version = KernelVersion::new(5, 4, 0);
let kern_version = KernelVersion::new(5, 4, 0);
kernel_assert!(is_supported!(MapType::DevMapHash), kern_version);
kern_version = KernelVersion::new(5, 6, 0);
let kern_version = KernelVersion::new(5, 6, 0);
kernel_assert!(is_supported!(MapType::StructOps), kern_version);
kern_version = KernelVersion::new(5, 8, 0);
let kern_version = KernelVersion::new(5, 8, 0);
kernel_assert!(is_supported!(MapType::RingBuf), kern_version);
kern_version = KernelVersion::new(5, 10, 0);
let kern_version = KernelVersion::new(5, 10, 0);
kernel_assert!(is_supported!(MapType::InodeStorage), kern_version); // Requires `CONFIG_BPF_LSM=y`
kern_version = KernelVersion::new(5, 11, 0);
let kern_version = KernelVersion::new(5, 11, 0);
kernel_assert!(is_supported!(MapType::TaskStorage), kern_version);
kern_version = KernelVersion::new(5, 16, 0);
let kern_version = KernelVersion::new(5, 16, 0);
kernel_assert!(is_supported!(MapType::BloomFilter), kern_version);
kern_version = KernelVersion::new(6, 1, 0);
let kern_version = KernelVersion::new(6, 1, 0);
kernel_assert!(is_supported!(MapType::UserRingBuf), kern_version);
kern_version = KernelVersion::new(6, 2, 0);
let kern_version = KernelVersion::new(6, 2, 0);
kernel_assert!(is_supported!(MapType::CgrpStorage), kern_version);
kern_version = KernelVersion::new(6, 9, 0);
let kern_version = KernelVersion::new(6, 9, 0);
kernel_assert!(is_supported!(MapType::Arena), kern_version);
}

Loading…
Cancel
Save