Selaa lähdekoodia

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>
Milan 2 vuotta sitten
vanhempi
commit
8f1163a400

+ 48 - 0
aya-bpf-macros/src/expand.rs

@@ -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\"]"));
+    }
 }

+ 45 - 4
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<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()
+}

+ 9 - 4
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)

+ 6 - 0
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 }

+ 135 - 0
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<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
+);

+ 12 - 0
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

+ 19 - 0
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 _
+    }
+}

+ 2 - 0
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;