Add support for BPF_PROG_TYPE_CGROUP_DEVICE

Kernel 4.15 added a new eBPF program that can
be used with cgroup v2 to control & observe device
access (e.g. read, write, mknod) - `BPF_PROG_TYPE_CGROUP_DEVICE`.

We add the ability to create these programs with the `cgroup_device`
proc macro which creates the `cgroup/dev` link section. Device
details are available to the eBPF program in `DeviceContext`.

The userspace representation is provided with the `CgroupDevice`
structure.

Fixes: #212
Signed-off-by: Milan <milan@mdaverde.com>
pull/466/head
Milan 2 years ago
parent 9ce1530695
commit 8f1163a400

@ -820,6 +820,37 @@ impl SkLookup {
}
}
pub struct CgroupDevice {
item: ItemFn,
name: Option<String>,
}
impl CgroupDevice {
pub fn from_syn(mut args: Args, item: ItemFn) -> Result<Self> {
let name = name_arg(&mut args)?;
Ok(CgroupDevice { item, name })
}
pub fn expand(&self) -> Result<TokenStream> {
let section_name = if let Some(name) = &self.name {
format!("cgroup/dev/{name}")
} else {
("cgroup/dev").to_owned()
};
let fn_name = &self.item.sig.ident;
let item = &self.item;
Ok(quote! {
#[no_mangle]
#[link_section = #section_name]
fn #fn_name(ctx: *mut ::aya_bpf::bindings::bpf_cgroup_dev_ctx) -> i32 {
return #fn_name(::aya_bpf::programs::DeviceContext::new(ctx));
#item
}
})
}
}
#[cfg(test)]
mod tests {
use syn::parse_quote;
@ -893,4 +924,21 @@ mod tests {
.to_string()
.contains("[link_section = \"cgroup_skb/egress\"]"));
}
#[test]
fn cgroup_device_no_name() {
let prog = CgroupDevice::from_syn(
parse_quote!(),
parse_quote!(
fn foo(ctx: DeviceContext) -> i32 {
0
}
),
)
.unwrap();
let stream = prog.expand().unwrap();
assert!(stream
.to_string()
.contains("[link_section = \"cgroup/dev\"]"));
}
}

@ -1,12 +1,13 @@
mod expand;
use expand::{
Args, BtfTracePoint, CgroupSkb, CgroupSock, CgroupSockAddr, CgroupSockopt, CgroupSysctl,
FEntry, FExit, Lsm, Map, PerfEvent, Probe, ProbeKind, RawTracePoint, SchedClassifier, SkLookup,
SkMsg, SkSkb, SkSkbKind, SockAddrArgs, SockOps, SocketFilter, SockoptArgs, TracePoint, Xdp,
Args, BtfTracePoint, CgroupDevice, CgroupSkb, CgroupSock, CgroupSockAddr, CgroupSockopt,
CgroupSysctl, FEntry, FExit, Lsm, Map, PerfEvent, Probe, ProbeKind, RawTracePoint,
SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockAddrArgs, SockOps, SocketFilter,
SockoptArgs, TracePoint, Xdp,
};
use proc_macro::TokenStream;
use syn::{parse_macro_input, ItemFn, ItemStatic};
use syn::{parse_macro_input, token::Token, ItemFn, ItemStatic};
#[proc_macro_attribute]
pub fn map(attrs: TokenStream, item: TokenStream) -> TokenStream {
@ -507,3 +508,43 @@ pub fn sk_lookup(attrs: TokenStream, item: TokenStream) -> TokenStream {
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
/// Marks a function as a cgroup device eBPF program that can be attached to a
/// cgroup.
///
/// # Minimum kernel version
///
/// The minimum kernel version required to use this feature is 4.15.
///
/// # Examples
///
/// ```no_run
/// use aya_bpf::{
/// macros::cgroup_device,
/// programs::DeviceContext,
/// };
/// use aya_log_ebpf::info;
///
/// #[cgroup_device(name="cgroup_dev")]
/// pub fn cgroup_dev(ctx: DeviceContext) -> i32 {
/// match try_cgroup_dev(ctx) {
/// Ok(ret) => ret,
/// Err(ret) => ret,
/// }
/// }
///
/// fn try_cgroup_dev(ctx: DeviceContext) -> Result<i32, i32> {
/// info!(&ctx, "device operation called");
/// Ok(0)
/// }
/// ```
#[proc_macro_attribute]
pub fn cgroup_device(attrs: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attrs as Args);
let item = parse_macro_input!(item as ItemFn);
CgroupDevice::from_syn(args, item)
.and_then(|u| u.expand())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

@ -22,10 +22,10 @@ use crate::{
MapKind, Object, ParseError, ProgramSection,
},
programs::{
BtfTracePoint, CgroupSkb, CgroupSkbAttachType, CgroupSock, CgroupSockAddr, CgroupSockopt,
CgroupSysctl, Extension, FEntry, FExit, KProbe, LircMode2, Lsm, PerfEvent, ProbeKind,
Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier, SkLookup, SkMsg, SkSkb,
SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp,
BtfTracePoint, CgroupDevice, CgroupSkb, CgroupSkbAttachType, CgroupSock, CgroupSockAddr,
CgroupSockopt, CgroupSysctl, Extension, FEntry, FExit, KProbe, LircMode2, Lsm, PerfEvent,
ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier, SkLookup,
SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp,
},
sys::{
bpf_load_btf, bpf_map_freeze, bpf_map_update_elem_ptr, is_btf_datasec_supported,
@ -633,6 +633,11 @@ impl<'a> BpfLoader<'a> {
attach_type: *attach_type,
})
}
ProgramSection::CgroupDevice { .. } => {
Program::CgroupDevice(CgroupDevice {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
})
}
}
};
(name, program)

@ -293,6 +293,9 @@ pub enum ProgramSection {
name: String,
attach_type: CgroupSockAttachType,
},
CgroupDevice {
name: String,
},
}
impl ProgramSection {
@ -326,6 +329,7 @@ impl ProgramSection {
ProgramSection::Extension { name } => name,
ProgramSection::SkLookup { name } => name,
ProgramSection::CgroupSock { name, .. } => name,
ProgramSection::CgroupDevice { name } => name,
}
}
}
@ -390,6 +394,7 @@ impl FromStr for ProgramSection {
attach_type: CgroupSockAttachType::default(),
},
"cgroup/sysctl" => CgroupSysctl { name },
"cgroup/dev" => CgroupDevice { name },
"cgroup/getsockopt" => CgroupSockopt {
name,
attach_type: CgroupSockoptAttachType::Get,
@ -401,6 +406,7 @@ impl FromStr for ProgramSection {
"cgroup" => match &*name {
"skb" => CgroupSkb { name },
"sysctl" => CgroupSysctl { name },
"dev" => CgroupDevice { name },
"getsockopt" | "setsockopt" => {
if let Ok(attach_type) = CgroupSockoptAttachType::try_from(name.as_str()) {
CgroupSockopt { name, attach_type }

@ -0,0 +1,135 @@
//! Cgroup device programs.
use std::os::unix::prelude::{AsRawFd, RawFd};
use crate::{
generated::{bpf_attach_type::BPF_CGROUP_DEVICE, bpf_prog_type::BPF_PROG_TYPE_CGROUP_DEVICE},
programs::{
define_link_wrapper, load_program, FdLink, Link, ProgAttachLink, ProgramData, ProgramError,
},
sys::{bpf_link_create, bpf_prog_attach, kernel_version},
};
/// A program used to watch or prevent device interaction from a cgroup
///
/// [`CgroupDevice`] programs can be attached to a cgroup and will be called every
/// time a process inside that cgroup tries to access (e.g. read, write, mknod)
/// a device (identified through its major and minor number). See
/// [mknod](https://man7.org/linux/man-pages/man2/mknod.2.html) as a starting point.
///
/// # Minimum kernel version
///
/// The minimum kernel version required to use this feature is [4.15](https://github.com/torvalds/linux/commit/ebc614f687369f9df99828572b1d85a7c2de3d92).
///
/// # Examples
///
/// ```no_run
/// use aya::programs::CgroupDevice;
///
/// let cgroup = std::fs::File::open("/sys/fs/cgroup/unified")?;
/// let program: &mut CgroupDevice = bpf.program_mut("cgroup_dev").unwrap().try_into()?;
/// program.load()?;
/// program.attach(cgroup)?;
/// ```
#[derive(Debug)]
#[doc(alias = "BPF_PROG_TYPE_CGROUP_DEVICE")]
pub struct CgroupDevice {
pub(crate) data: ProgramData<CgroupDeviceLink>,
}
impl CgroupDevice {
/// Loads the program inside the kernel
pub fn load(&mut self) -> Result<(), ProgramError> {
load_program(BPF_PROG_TYPE_CGROUP_DEVICE, &mut self.data)
}
/// Attaches the program to the given cgroup.
///
/// The returned value can be used to detach, see [CgroupDevice::detach]
pub fn attach<T: AsRawFd>(&mut self, cgroup: T) -> Result<CgroupDeviceLinkId, ProgramError> {
let prog_fd = self.data.fd_or_err()?;
let cgroup_fd = cgroup.as_raw_fd();
let k_ver = kernel_version().unwrap();
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,
},
)? as RawFd;
self.data
.links
.insert(CgroupDeviceLink(CgroupDeviceLinkInner::Fd(FdLink::new(
link_fd,
))))
} else {
bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE).map_err(|(_, io_error)| {
ProgramError::SyscallError {
call: "bpf_prog_attach".to_owned(),
io_error,
}
})?;
self.data
.links
.insert(CgroupDeviceLink(CgroupDeviceLinkInner::ProgAttach(
ProgAttachLink::new(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE),
)))
}
}
/// Takes ownership of the link referenced by the provided link_id.
///
/// The link will be detached on `Drop` and the caller is now responsible
/// for managing its lifetime.
pub fn take_link(
&mut self,
link_id: CgroupDeviceLinkId,
) -> Result<CgroupDeviceLink, ProgramError> {
self.data.take_link(link_id)
}
/// Detaches the program
///
/// See [CgroupDevice::attach].
pub fn detach(&mut self, link_id: CgroupDeviceLinkId) -> Result<(), ProgramError> {
self.data.links.remove(link_id)
}
}
#[derive(Debug, Hash, Eq, PartialEq)]
enum CgroupDeviceLinkIdInner {
Fd(<FdLink as Link>::Id),
ProgAttach(<ProgAttachLink as Link>::Id),
}
#[derive(Debug)]
enum CgroupDeviceLinkInner {
Fd(FdLink),
ProgAttach(ProgAttachLink),
}
impl Link for CgroupDeviceLinkInner {
type Id = CgroupDeviceLinkIdInner;
fn id(&self) -> Self::Id {
match self {
CgroupDeviceLinkInner::Fd(fd) => CgroupDeviceLinkIdInner::Fd(fd.id()),
CgroupDeviceLinkInner::ProgAttach(p) => CgroupDeviceLinkIdInner::ProgAttach(p.id()),
}
}
fn detach(self) -> Result<(), ProgramError> {
match self {
CgroupDeviceLinkInner::Fd(fd) => fd.detach(),
CgroupDeviceLinkInner::ProgAttach(p) => p.detach(),
}
}
}
define_link_wrapper!(
/// The link used by [CgroupDevice] programs.
CgroupDeviceLink,
/// The type returned by [CgroupDevice::attach]. Can be passed to [CgroupDevice::detach].
CgroupDeviceLinkId,
CgroupDeviceLinkInner,
CgroupDeviceLinkIdInner
);

@ -35,6 +35,7 @@
//! [`Bpf::program`]: crate::Bpf::program
//! [`Bpf::program_mut`]: crate::Bpf::program_mut
//! [`maps`]: crate::maps
pub mod cgroup_device;
pub mod cgroup_skb;
pub mod cgroup_sock;
pub mod cgroup_sock_addr;
@ -72,6 +73,7 @@ use std::{
};
use thiserror::Error;
pub use cgroup_device::CgroupDevice;
pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType};
pub use cgroup_sock::{CgroupSock, CgroupSockAttachType};
pub use cgroup_sock_addr::{CgroupSockAddr, CgroupSockAddrAttachType};
@ -265,6 +267,8 @@ pub enum Program {
SkLookup(SkLookup),
/// A [`CgroupSock`] program
CgroupSock(CgroupSock),
/// A [`CgroupDevice`] program
CgroupDevice(CgroupDevice),
}
impl Program {
@ -295,6 +299,7 @@ impl Program {
Program::CgroupSockAddr(_) => BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
Program::SkLookup(_) => BPF_PROG_TYPE_SK_LOOKUP,
Program::CgroupSock(_) => BPF_PROG_TYPE_CGROUP_SOCK,
Program::CgroupDevice(_) => BPF_PROG_TYPE_CGROUP_DEVICE,
}
}
@ -324,6 +329,7 @@ impl Program {
Program::CgroupSockAddr(p) => p.pin(path),
Program::SkLookup(p) => p.pin(path),
Program::CgroupSock(p) => p.pin(path),
Program::CgroupDevice(p) => p.pin(path),
}
}
@ -353,6 +359,7 @@ impl Program {
Program::CgroupSockAddr(p) => p.unload(),
Program::SkLookup(p) => p.unload(),
Program::CgroupSock(p) => p.unload(),
Program::CgroupDevice(p) => p.unload(),
}
}
@ -385,6 +392,7 @@ impl Program {
Program::CgroupSockAddr(p) => p.fd(),
Program::SkLookup(p) => p.fd(),
Program::CgroupSock(p) => p.fd(),
Program::CgroupDevice(p) => p.fd(),
}
}
}
@ -637,6 +645,7 @@ impl_program_unload!(
SkLookup,
SockOps,
CgroupSock,
CgroupDevice,
);
macro_rules! impl_fd {
@ -676,6 +685,7 @@ impl_fd!(
SkLookup,
SockOps,
CgroupSock,
CgroupDevice,
);
macro_rules! impl_program_pin{
@ -720,6 +730,7 @@ impl_program_pin!(
SkLookup,
SockOps,
CgroupSock,
CgroupDevice,
);
macro_rules! impl_try_from_program {
@ -774,6 +785,7 @@ impl_try_from_program!(
CgroupSockAddr,
SkLookup,
CgroupSock,
CgroupDevice,
);
/// Provides information about a loaded program, like name, id and statistics

@ -0,0 +1,19 @@
use core::ffi::c_void;
use crate::{bindings::bpf_cgroup_dev_ctx, BpfContext};
pub struct DeviceContext {
pub device: *mut bpf_cgroup_dev_ctx,
}
impl DeviceContext {
pub fn new(device: *mut bpf_cgroup_dev_ctx) -> DeviceContext {
DeviceContext { device }
}
}
impl BpfContext for DeviceContext {
fn as_ptr(&self) -> *mut c_void {
self.device as *mut _
}
}

@ -1,3 +1,4 @@
pub mod device;
pub mod fentry;
pub mod fexit;
pub mod lsm;
@ -17,6 +18,7 @@ pub mod tp_btf;
pub mod tracepoint;
pub mod xdp;
pub use device::DeviceContext;
pub use fentry::FEntryContext;
pub use fexit::FExitContext;
pub use lsm::LsmContext;

Loading…
Cancel
Save