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 <vadorovsky@gmail.com>
pull/439/head
Michal Rostecki 2 years ago
parent 49404367d8
commit accd9d2a2a

@ -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<CgroupLsm> {
let name = name_arg(&mut args)?.unwrap_or_else(|| item.sig.ident.to_string());
Ok(CgroupLsm { item, name })
}
pub fn expand(&self) -> Result<TokenStream> {
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)] #[cfg(test)]
mod tests { mod tests {
use syn::parse_quote; use syn::parse_quote;
@ -893,4 +924,38 @@ mod tests {
.to_string() .to_string()
.contains("[link_section = \"cgroup_skb/egress\"]")); .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\"]"));
}
} }

@ -1,9 +1,10 @@
mod expand; mod expand;
use expand::{ use expand::{
Args, BtfTracePoint, CgroupSkb, CgroupSock, CgroupSockAddr, CgroupSockopt, CgroupSysctl, Args, BtfTracePoint, CgroupLsm, CgroupSkb, CgroupSock, CgroupSockAddr, CgroupSockopt,
FEntry, FExit, Lsm, Map, PerfEvent, Probe, ProbeKind, RawTracePoint, SchedClassifier, SkLookup, CgroupSysctl, FEntry, FExit, Lsm, Map, PerfEvent, Probe, ProbeKind, RawTracePoint,
SkMsg, SkSkb, SkSkbKind, SockAddrArgs, SockOps, SocketFilter, SockoptArgs, TracePoint, Xdp, SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockAddrArgs, SockOps, SocketFilter,
SockoptArgs, TracePoint, Xdp,
}; };
use proc_macro::TokenStream; use proc_macro::TokenStream;
use syn::{parse_macro_input, ItemFn, ItemStatic}; 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()) .unwrap_or_else(|err| err.to_compile_error())
.into() .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<i32, i32> {
/// 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()
}

@ -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<CgroupLsmLink>,
}
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<T: AsRawFd>(&mut self, cgroup: T) -> Result<CgroupLsmLinkId, ProgramError> {
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<CgroupLsmLink, ProgramError> {
self.data.take_link(link_id)
}
}
#[derive(Debug, Hash, Eq, PartialEq)]
enum CgroupLsmLinkIdInner {
Fd(<FdLink as Link>::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
);

@ -15,8 +15,8 @@ use crate::{
/// access control policy and security auditing. /// access control policy and security auditing.
/// ///
/// LSM probes require a kernel compiled with `CONFIG_BPF_LSM=y` and `CONFIG_DEBUG_INFO_BTF=y`. /// 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 /// If your kernel is not built with `lsm=[...],bpf` option, BPF LSM needs to be enabled
/// kernel's boot paramters (like `lsm=lockdown,yama,bpf`). /// through the kernel's boot parameter (like `lsm=lockdown,yama,bpf`).
/// ///
/// # Minimum kernel version /// # Minimum kernel version
/// ///

@ -35,6 +35,7 @@
//! [`Bpf::program`]: crate::Bpf::program //! [`Bpf::program`]: crate::Bpf::program
//! [`Bpf::program_mut`]: crate::Bpf::program_mut //! [`Bpf::program_mut`]: crate::Bpf::program_mut
//! [`maps`]: crate::maps //! [`maps`]: crate::maps
pub mod cgroup_lsm;
pub mod cgroup_skb; pub mod cgroup_skb;
pub mod cgroup_sock; pub mod cgroup_sock;
pub mod cgroup_sock_addr; pub mod cgroup_sock_addr;
@ -72,6 +73,7 @@ use std::{
}; };
use thiserror::Error; use thiserror::Error;
pub use cgroup_lsm::CgroupLsm;
pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType}; pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType};
pub use cgroup_sock::{CgroupSock, CgroupSockAttachType}; pub use cgroup_sock::{CgroupSock, CgroupSockAttachType};
pub use cgroup_sock_addr::{CgroupSockAddr, CgroupSockAddrAttachType}; pub use cgroup_sock_addr::{CgroupSockAddr, CgroupSockAddrAttachType};

Loading…
Cancel
Save