use std::{
mem,
os::fd::{AsFd as _, AsRawFd as _},
ptr,
};
use aya_obj::{
btf::{Btf, BtfKind},
generated::{
BPF_F_MMAPABLE, BPF_F_NO_PREALLOC, bpf_attr, bpf_cmd, bpf_map_type, bpf_prog_info,
},
};
use libc::{E2BIG, EBADF, EINVAL};
use super::{
SyscallError, bpf_map_create, bpf_prog_load, bpf_raw_tracepoint_open, unit_sys_bpf,
with_trivial_prog,
};
use crate::{
MockableFd,
maps::MapType,
programs::{LsmAttachType, ProgramError, ProgramType},
util::page_size,
};
pub fn is_program_supported(program_type: ProgramType) -> Result<bool, ProgramError> {
if program_type == ProgramType::Unspecified {
return Ok(false);
}
let mut verifier_log = matches!(
program_type,
ProgramType::Tracing | ProgramType::Extension | ProgramType::Lsm(_)
)
.then_some([0u8; 256]);
let attach_btf_id = match program_type {
ProgramType::Tracing => Some("bpf_fentry_test1"),
ProgramType::Lsm(_) => Some("bpf_lsm_bpf"),
_ => None,
}
.map(|func_name| {
Btf::from_sys_fs()
.and_then(|btf| btf.id_by_type_name_kind(func_name, BtfKind::Func))
.unwrap_or(0)
});
with_trivial_prog(program_type, |attr| {
let u = unsafe { &mut attr.__bindgen_anon_3 };
if let Some(attach_btf_id) = attach_btf_id {
u.attach_btf_id = attach_btf_id;
}
if let Some(verifier_log) = verifier_log.as_mut() {
u.log_buf = verifier_log.as_mut_ptr() as u64;
u.log_level = 1;
u.log_size = verifier_log.len() as u32;
}
match bpf_prog_load(attr) {
Err(io_error) => match io_error.raw_os_error() {
Some(EINVAL) => {
let supported = matches!(
verifier_log,
Some(verifier_log) if verifier_log.starts_with(b"Tracing programs must provide btf_id")
);
Ok(supported)
}
Some(E2BIG) => Ok(false),
Some(524) if program_type == ProgramType::StructOps => Ok(true),
_ => Err(ProgramError::SyscallError(SyscallError {
call: "bpf_prog_load",
io_error,
})),
},
Ok(prog_fd) => {
if matches!(program_type, ProgramType::Lsm(LsmAttachType::Mac)) {
match bpf_raw_tracepoint_open(None, prog_fd.as_fd()) {
Ok(_) => Ok(true),
Err(io_error) => match io_error.raw_os_error() {
Some(524) => Ok(false),
_ => Err(ProgramError::SyscallError(SyscallError {
call: "bpf_raw_tracepoint_open",
io_error,
})),
},
}
} else {
Ok(true)
}
}
}
})
}
pub fn is_map_supported(map_type: MapType) -> Result<bool, SyscallError> {
let (key_size, value_size, max_entries) = match map_type {
MapType::Unspecified => return Ok(false),
MapType::Hash | MapType::PerCpuHash | MapType::LruHash | MapType::LruPerCpuHash => (1, 1, 1),
MapType::Array | MapType::PerCpuArray => (4, 1, 1),
MapType::ProgramArray | MapType::PerfEventArray | MapType::CgroupArray | MapType::ArrayOfMaps | MapType::DevMap | MapType::SockMap | MapType::CpuMap | MapType::XskMap | MapType::ReuseportSockArray | MapType::DevMapHash => (4, 4, 1),
MapType::StackTrace => (4, 8, 1),
MapType::LpmTrie => (8, 1, 1),
MapType::HashOfMaps | MapType::SockHash => (1, 4, 1),
MapType::CgroupStorage | MapType::PerCpuCgroupStorage => (16, 1, 0),
MapType::Queue | MapType::Stack | MapType::BloomFilter => (0, 1, 1),
MapType::SkStorage | MapType::InodeStorage | MapType::TaskStorage | MapType::CgrpStorage => (4, 1, 0),
MapType::StructOps => (4, 0, 1),
MapType::RingBuf | MapType::UserRingBuf => (0, 0, page_size() as u32),
MapType::Arena => (0, 0, 1),
};
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
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;
let inner_map_fd: MockableFd;
match map_type {
MapType::LpmTrie => u.map_flags = BPF_F_NO_PREALLOC,
MapType::SkStorage
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage => {
u.map_flags = BPF_F_NO_PREALLOC;
u.btf_fd = u32::MAX;
u.btf_key_type_id = 1;
u.btf_value_type_id = 1;
}
MapType::ArrayOfMaps | MapType::HashOfMaps => {
let mut attr_map = unsafe { mem::zeroed::<bpf_attr>() };
let u_map = unsafe { &mut attr_map.__bindgen_anon_1 };
u_map.map_type = bpf_map_type::BPF_MAP_TYPE_HASH as u32;
u_map.key_size = 1;
u_map.value_size = 1;
u_map.max_entries = 1;
inner_map_fd = bpf_map_create(&mut attr_map).map_err(|io_error| SyscallError {
call: "bpf_map_create",
io_error,
})?;
u.inner_map_fd = inner_map_fd.as_raw_fd() as u32;
}
MapType::StructOps => u.btf_vmlinux_value_type_id = 1,
MapType::Arena => u.map_flags = BPF_F_MMAPABLE,
_ => {}
}
let io_error = match bpf_map_create(&mut attr) {
Ok(_fd) => return Ok(true),
Err(io_error) => io_error,
};
match io_error.raw_os_error() {
Some(EINVAL) => Ok(false),
Some(E2BIG)
if matches!(
map_type,
MapType::SkStorage
| MapType::StructOps
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage
) =>
{
Ok(false)
}
Some(EBADF)
if matches!(
map_type,
MapType::SkStorage
| MapType::InodeStorage
| MapType::TaskStorage
| MapType::CgrpStorage
) =>
{
Ok(true)
}
Some(524) if map_type == MapType::StructOps => Ok(true),
_ => Err(SyscallError {
call: "bpf_map_create",
io_error,
}),
}
}
pub(crate) fn is_prog_info_map_ids_supported() -> Result<bool, ProgramError> {
let fd = with_trivial_prog(ProgramType::SocketFilter, |attr| {
bpf_prog_load(attr).map_err(|io_error| {
ProgramError::SyscallError(SyscallError {
call: "bpf_prog_load",
io_error,
})
})
})?;
let mut info = unsafe { mem::zeroed::<bpf_prog_info>() };
info.nr_map_ids = 1;
probe_bpf_info(fd, info).map_err(ProgramError::from)
}
pub(crate) fn is_prog_info_license_supported() -> Result<bool, ProgramError> {
let fd = with_trivial_prog(ProgramType::SocketFilter, |attr| {
bpf_prog_load(attr).map_err(|io_error| {
ProgramError::SyscallError(SyscallError {
call: "bpf_prog_load",
io_error,
})
})
})?;
let mut info = unsafe { mem::zeroed::<bpf_prog_info>() };
info.set_gpl_compatible(1);
probe_bpf_info(fd, info).map_err(ProgramError::from)
}
fn probe_bpf_info<T>(fd: MockableFd, info: T) -> Result<bool, SyscallError> {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
attr.info.bpf_fd = fd.as_raw_fd() as u32;
attr.info.info_len = size_of_val(&info) as u32;
attr.info.info = ptr::from_ref(&info) as u64;
let io_error = match unit_sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &mut attr) {
Ok(()) => return Ok(true),
Err(io_error) => io_error,
};
match io_error.raw_os_error() {
Some(E2BIG) => Ok(false),
_ => Err(SyscallError {
call: "bpf_obj_get_info_by_fd",
io_error,
}),
}
}