From accd9d2a2a8dc97576de2f6b002efe7b4a6d0811 Mon Sep 17 00:00:00 2001 From: Michal Rostecki Date: Fri, 11 Nov 2022 14:06:34 -0800 Subject: [PATCH] Add support for LSM programs attached to cgroups Kernel 6.0 provides a new attachment type - `BPF_LSM_CGROUP`. When using it, a program attaches to LSM hooks, but only within a cgroup. `BPF_LSM_CGROUP` requires programs to we present in the `lsm_cgroup` section, therefore we provide a new `cgroup_lsm` macro for defining such programs in aya-bpf. We also provide a new `CgroupLsm` structure in userspace aya, which uses the new attachment type and stores the information about cgroup in links. Fixes: #423 Signed-off-by: Michal Rostecki --- aya-bpf-macros/src/expand.rs | 65 ++++++++++++++++ aya-bpf-macros/src/lib.rs | 51 ++++++++++++- aya/src/programs/cgroup_lsm.rs | 134 +++++++++++++++++++++++++++++++++ aya/src/programs/lsm.rs | 4 +- aya/src/programs/mod.rs | 2 + 5 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 aya/src/programs/cgroup_lsm.rs diff --git a/aya-bpf-macros/src/expand.rs b/aya-bpf-macros/src/expand.rs index 42088969..42b5b17c 100644 --- a/aya-bpf-macros/src/expand.rs +++ b/aya-bpf-macros/src/expand.rs @@ -820,6 +820,37 @@ impl SkLookup { } } +pub struct CgroupLsm { + item: ItemFn, + name: String, +} + +impl CgroupLsm { + pub fn from_syn(mut args: Args, item: ItemFn) -> Result { + let name = name_arg(&mut args)?.unwrap_or_else(|| item.sig.ident.to_string()); + + Ok(CgroupLsm { item, name }) + } + + pub fn expand(&self) -> Result { + let section_name = format!("lsm_cgroup/{}", self.name); + let fn_name = &self.item.sig.ident; + let item = &self.item; + // LSM probes need to return an integer corresponding to the correct + // policy decision. Therefore we do not simply default to a return value + // of 0 as in other program types. + Ok(quote! { + #[no_mangle] + #[link_section = #section_name] + fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 { + return #fn_name(::aya_bpf::programs::CgroupLsmContext::new(ctx)); + + #item + } + }) + } +} + #[cfg(test)] mod tests { use syn::parse_quote; @@ -893,4 +924,38 @@ mod tests { .to_string() .contains("[link_section = \"cgroup_skb/egress\"]")); } + + #[test] + fn cgroup_lsm_with_name() { + let prog = CgroupLsm::from_syn( + parse_quote!(name = "foo"), + parse_quote!( + fn bar(ctx: LsmContext) -> i32 { + 0 + } + ), + ) + .unwrap(); + let stream = prog.expand().unwrap(); + assert!(stream + .to_string() + .contains("[link_section = \"lsm_cgroup/foo\"]")); + } + + #[test] + fn cgroup_lsm_no_name() { + let prog = CgroupLsm::from_syn( + parse_quote!(), + parse_quote!( + fn foo(ct: LsmContext) -> i32 { + 0 + } + ), + ) + .unwrap(); + let stream = prog.expand().unwrap(); + assert!(stream + .to_string() + .contains("[link_section = \"lsm_cgroup/foo\"]")); + } } diff --git a/aya-bpf-macros/src/lib.rs b/aya-bpf-macros/src/lib.rs index be255952..09ee1163 100644 --- a/aya-bpf-macros/src/lib.rs +++ b/aya-bpf-macros/src/lib.rs @@ -1,9 +1,10 @@ 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, CgroupLsm, 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}; @@ -507,3 +508,47 @@ pub fn sk_lookup(attrs: TokenStream, item: TokenStream) -> TokenStream { .unwrap_or_else(|err| err.to_compile_error()) .into() } + +/// Marks a function as an LSM program that can be attached to Linux LSM hooks +/// within a [cgroup]. Used to implement security policy and audit logging. +/// +/// LSM probes can be attached to the kernel's security hooks to implement mandatory +/// access control policy and security auditing. +/// +/// LSM probes require a kernel compiled with `CONFIG_BPF_LSM=y` and +/// `CONFIG_DEBUG_INFO_BTF=y`. In order for the probes to fire, you also need +/// the BPF LSM to be enabled through your kernel's `lsm` option. If your kernel +/// is not built with `lsm=[...],bpf` option, BPF LSM needs to be enabled +/// through the kernel's boot parameter (like `lsm=lockdown,yama,bpf`). +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 6.0. +/// +/// # Examples +/// +/// ```no_run +/// use aya_bpf::{macros::lsm, programs::LsmContext}; +/// +/// #[lsm(name = "file_open")] +/// pub fn file_open(ctx: LsmContext) -> i32 { +/// match unsafe { try_file_open(ctx) } { +/// Ok(ret) => ret, +/// Err(ret) => ret, +/// } +/// } +/// +/// unsafe fn try_file_open(_ctx: LsmContext) -> Result { +/// Ok(0) +/// } +/// ``` +#[proc_macro_attribute] +pub fn cgroup_lsm(attrs: TokenStream, item: TokenStream) -> TokenStream { + let args = parse_macro_input!(attrs as Args); + let item = parse_macro_input!(item as ItemFn); + + CgroupLsm::from_syn(args, item) + .and_then(|u| u.expand()) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/aya/src/programs/cgroup_lsm.rs b/aya/src/programs/cgroup_lsm.rs new file mode 100644 index 00000000..402ecaa1 --- /dev/null +++ b/aya/src/programs/cgroup_lsm.rs @@ -0,0 +1,134 @@ +//! LSM probes attached to cgroups. +use std::os::unix::prelude::{AsRawFd, RawFd}; + +use crate::{ + generated::{bpf_attach_type::BPF_LSM_CGROUP, bpf_prog_type::BPF_PROG_TYPE_LSM}, + obj::btf::{Btf, BtfKind}, + programs::{define_link_wrapper, load_program, FdLink, Link, ProgramData, ProgramError}, + sys::bpf_link_create, +}; + +/// A program that attaches to Linux LSM hooks within a [cgroup]. Used to +/// implement security policy and audit logging. +/// +/// LSM probes can be attached to the kernel's [security hooks][1] to implement +/// mandatory access control policy and security auditing. +/// +/// LSM probes require a kernel compiled with `CONFIG_BPF_LSM=y` and +/// `CONFIG_DEBUG_INFO_BTF=y`. In order for the probes to fire, you also need +/// the BPF LSM to be enabled through your kernel's `lsm` option. If your kernel +/// is not built with `lsm=[...],bpf` option, BPF LSM needs to be enabled +/// through the kernel's boot parameter (like `lsm=lockdown,yama,bpf`). +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 6.0. +/// +/// # Examples +/// +/// ```no_run +/// # #[derive(thiserror::Error, Debug)] +/// # enum LsmError { +/// # #[error(transparent)] +/// # BtfError(#[from] aya::BtfError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Bpf(#[from] aya::BpfError), +/// # } +/// # let mut bpf = Bpf::load_file("ebpf_programs.o")?; +/// use aya::{Bpf, programs::LsmCgroup, BtfError, Btf}; +/// use std::{fs::File, os::unix::prelude::AsRawFd, path::Path}; +/// +/// let btf = Btf::from_sys_fs()?; +/// let program: &mut LsmCgroup = bpf.program_mut("lsm_prog").unwrap().try_into()?; +/// program.load("security_bprm_exec", &btf)?; +/// let cgroup = File::open(Path::new("/sys/fs/cgroup/unified/aya"))?; +/// program.attach(cgroup.as_raw_fd())?; +/// # Ok::<(), LsmError>(()) +/// ``` +#[derive(Debug)] +pub struct CgroupLsm { + pub(crate) data: ProgramData, +} + +impl CgroupLsm { + /// Loads the program inside the kernel. + pub fn load(&mut self, lsm_hook_name: &str, btf: &Btf) -> Result<(), ProgramError> { + self.data.expected_attach_type = Some(BPF_LSM_CGROUP); + let type_name = format!("bpf_lsm_{}", lsm_hook_name); + self.data.attach_btf_id = + Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Func)?); + load_program(BPF_PROG_TYPE_LSM, &mut self.data) + } + + /// Attaches the program. + /// + /// The returned value can be used to detach, see [CgroupLsm::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 attach_type = self.data.expected_attach_type.unwrap(); + let btf_id = self.data.attach_btf_id; + + let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, btf_id, 0).map_err( + |(_, io_error)| ProgramError::SyscallError { + call: "bpf_link_create".to_owned(), + io_error, + }, + )? as RawFd; + self.data + .links + .insert(CgroupLsmLink(CgroupLsmLinkInner::Fd(FdLink::new(link_fd)))) + } + + /// Detaches the program. + /// + /// See [CgroupLsm::attach]. + pub fn detach(&mut self, link_id: CgroupLsmLinkId) -> Result<(), ProgramError> { + self.data.links.remove(link_id) + } + + /// 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: CgroupLsmLinkId) -> Result { + self.data.take_link(link_id) + } +} + +#[derive(Debug, Hash, Eq, PartialEq)] +enum CgroupLsmLinkIdInner { + Fd(::Id), +} + +#[derive(Debug)] +enum CgroupLsmLinkInner { + Fd(FdLink), +} + +impl Link for CgroupLsmLinkInner { + type Id = CgroupLsmLinkIdInner; + + fn id(&self) -> Self::Id { + match self { + CgroupLsmLinkInner::Fd(fd) => CgroupLsmLinkIdInner::Fd(fd.id()), + } + } + + fn detach(self) -> Result<(), ProgramError> { + match self { + CgroupLsmLinkInner::Fd(fd) => fd.detach(), + } + } +} + +define_link_wrapper!( + /// The link used by [CgroupLsm] programs. + CgroupLsmLink, + /// The type returned by [CgroupLsm::attach]. Can be passed to [CgroupLsm::detach]. + CgroupLsmLinkId, + CgroupLsmLinkInner, + CgroupLsmLinkIdInner +); diff --git a/aya/src/programs/lsm.rs b/aya/src/programs/lsm.rs index 92122b55..f39b37dd 100644 --- a/aya/src/programs/lsm.rs +++ b/aya/src/programs/lsm.rs @@ -15,8 +15,8 @@ use crate::{ /// access control policy and security auditing. /// /// LSM probes require a kernel compiled with `CONFIG_BPF_LSM=y` and `CONFIG_DEBUG_INFO_BTF=y`. -/// In order for the probes to fire, you also need the BPF LSM to be enabled through your -/// kernel's boot paramters (like `lsm=lockdown,yama,bpf`). +/// If your kernel is not built with `lsm=[...],bpf` option, BPF LSM needs to be enabled +/// through the kernel's boot parameter (like `lsm=lockdown,yama,bpf`). /// /// # Minimum kernel version /// diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 7f392f7a..a66ae078 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_lsm; 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_lsm::CgroupLsm; pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType}; pub use cgroup_sock::{CgroupSock, CgroupSockAttachType}; pub use cgroup_sock_addr::{CgroupSockAddr, CgroupSockAddrAttachType};