From 8f1163a400b13010acf5353ddc43e9b81ca61d7a Mon Sep 17 00:00:00 2001 From: Milan Date: Wed, 14 Dec 2022 14:10:09 -0500 Subject: [PATCH] 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 --- aya-bpf-macros/src/expand.rs | 48 ++++++++++ aya-bpf-macros/src/lib.rs | 49 ++++++++++- aya/src/bpf.rs | 13 ++- aya/src/obj/mod.rs | 6 ++ aya/src/programs/cgroup_device.rs | 135 +++++++++++++++++++++++++++++ aya/src/programs/mod.rs | 12 +++ bpf/aya-bpf/src/programs/device.rs | 19 ++++ bpf/aya-bpf/src/programs/mod.rs | 2 + 8 files changed, 276 insertions(+), 8 deletions(-) create mode 100644 aya/src/programs/cgroup_device.rs create mode 100644 bpf/aya-bpf/src/programs/device.rs diff --git a/aya-bpf-macros/src/expand.rs b/aya-bpf-macros/src/expand.rs index 4fd81db4..b793a92d 100644 --- a/aya-bpf-macros/src/expand.rs +++ b/aya-bpf-macros/src/expand.rs @@ -820,6 +820,37 @@ impl SkLookup { } } +pub struct CgroupDevice { + item: ItemFn, + name: Option, +} + +impl CgroupDevice { + pub fn from_syn(mut args: Args, item: ItemFn) -> Result { + let name = name_arg(&mut args)?; + + Ok(CgroupDevice { item, name }) + } + + pub fn expand(&self) -> Result { + 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\"]")); + } } diff --git a/aya-bpf-macros/src/lib.rs b/aya-bpf-macros/src/lib.rs index be255952..7cd40dc5 100644 --- a/aya-bpf-macros/src/lib.rs +++ b/aya-bpf-macros/src/lib.rs @@ -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 { +/// 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() +} diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index f5307f83..d81ff9fd 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -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) diff --git a/aya/src/obj/mod.rs b/aya/src/obj/mod.rs index 4cf11415..7dd90d53 100644 --- a/aya/src/obj/mod.rs +++ b/aya/src/obj/mod.rs @@ -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 } diff --git a/aya/src/programs/cgroup_device.rs b/aya/src/programs/cgroup_device.rs new file mode 100644 index 00000000..194c2df1 --- /dev/null +++ b/aya/src/programs/cgroup_device.rs @@ -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, +} + +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(&mut self, cgroup: T) -> Result { + 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 { + 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(::Id), + ProgAttach(::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 +); diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 7f392f7a..a2a45969 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -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 diff --git a/bpf/aya-bpf/src/programs/device.rs b/bpf/aya-bpf/src/programs/device.rs new file mode 100644 index 00000000..876756d5 --- /dev/null +++ b/bpf/aya-bpf/src/programs/device.rs @@ -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 _ + } +} diff --git a/bpf/aya-bpf/src/programs/mod.rs b/bpf/aya-bpf/src/programs/mod.rs index edd69397..498b0d1c 100644 --- a/bpf/aya-bpf/src/programs/mod.rs +++ b/bpf/aya-bpf/src/programs/mod.rs @@ -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;