mirror of https://github.com/aya-rs/aya
lsm :: cgroup attachment type support
parent
29b821376e
commit
97a52dd30f
@ -0,0 +1,87 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{ItemFn, Result};
|
||||||
|
|
||||||
|
use crate::args::{err_on_unknown_args, pop_string_arg};
|
||||||
|
|
||||||
|
pub(crate) struct LsmCgroup {
|
||||||
|
item: ItemFn,
|
||||||
|
hook: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LsmCgroup {
|
||||||
|
pub(crate) fn parse(attrs: TokenStream, item: TokenStream) -> Result<Self> {
|
||||||
|
let item = syn::parse2(item)?;
|
||||||
|
let mut args = syn::parse2(attrs)?;
|
||||||
|
let hook = pop_string_arg(&mut args, "hook");
|
||||||
|
err_on_unknown_args(&args)?;
|
||||||
|
|
||||||
|
Ok(Self { item, hook })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expand(&self) -> TokenStream {
|
||||||
|
let Self { item, hook } = self;
|
||||||
|
let ItemFn {
|
||||||
|
attrs: _,
|
||||||
|
vis,
|
||||||
|
sig,
|
||||||
|
block: _,
|
||||||
|
} = item;
|
||||||
|
let section_prefix = "lsm_cgroup";
|
||||||
|
let section_name: Cow<'_, _> = if let Some(name) = hook {
|
||||||
|
format!("{}/{}", section_prefix, name).into()
|
||||||
|
} else {
|
||||||
|
section_prefix.into()
|
||||||
|
};
|
||||||
|
let fn_name = &sig.ident;
|
||||||
|
// 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.
|
||||||
|
quote! {
|
||||||
|
#[no_mangle]
|
||||||
|
#[link_section = #section_name]
|
||||||
|
#vis fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 {
|
||||||
|
return #fn_name(::aya_ebpf::programs::LsmContext::new(ctx));
|
||||||
|
|
||||||
|
#item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use syn::parse_quote;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lsm_cgroup() {
|
||||||
|
let prog = LsmCgroup::parse(
|
||||||
|
parse_quote! {
|
||||||
|
hook = "bprm_committed_creds",
|
||||||
|
},
|
||||||
|
parse_quote! {
|
||||||
|
fn bprm_committed_creds(ctx: &mut ::aya_ebpf::programs::LsmContext) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let expanded = prog.expand();
|
||||||
|
let expected = quote! {
|
||||||
|
#[no_mangle]
|
||||||
|
#[link_section = "lsm_cgroup/bprm_committed_creds"]
|
||||||
|
fn bprm_committed_creds(ctx: *mut ::core::ffi::c_void) -> i32 {
|
||||||
|
return bprm_committed_creds(::aya_ebpf::programs::LsmContext::new(ctx));
|
||||||
|
|
||||||
|
fn bprm_committed_creds(ctx: &mut ::aya_ebpf::programs::LsmContext) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert_eq!(expected.to_string(), expanded.to_string());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
//! LSM probes.
|
||||||
|
|
||||||
|
use std::os::fd::AsFd;
|
||||||
|
|
||||||
|
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, FdLinkId, ProgramData, ProgramError},
|
||||||
|
sys::{bpf_link_create, BpfLinkCreateArgs, LinkTarget, SyscallError},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A program that attaches to Linux LSM hooks with per-cgroup attachment type. 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 boot paramters (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)]
|
||||||
|
/// # Ebpf(#[from] aya::EbpfError),
|
||||||
|
/// # }
|
||||||
|
/// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?;
|
||||||
|
/// use aya::{Ebpf, programs::LsmCgroup, BtfError, Btf};
|
||||||
|
/// use std::fs::File;
|
||||||
|
///
|
||||||
|
/// let btf = Btf::from_sys_fs()?;
|
||||||
|
/// let file = File::open("/sys/fs/cgroup/unified").unwrap();
|
||||||
|
/// let program: &mut LsmCgroup = bpf.program_mut("lsm_prog").unwrap().try_into()?;
|
||||||
|
/// program.load("security_bprm_exec", &btf)?;
|
||||||
|
/// program.attach(file)?;
|
||||||
|
/// # Ok::<(), LsmError>(())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [1]: https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[doc(alias = "BPF_PROG_TYPE_LSM")]
|
||||||
|
pub struct LsmCgroup {
|
||||||
|
pub(crate) data: ProgramData<LsmLink>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LsmCgroup {
|
||||||
|
/// Loads the program inside the kernel.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `lsm_hook_name` - full name of the LSM hook that the program should
|
||||||
|
/// be attached to
|
||||||
|
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 [LsmCgroup::detach].
|
||||||
|
pub fn attach<T: AsFd>(&mut self, cgroup: T) -> Result<LsmLinkId, ProgramError> {
|
||||||
|
let prog_fd = self.fd()?;
|
||||||
|
let prog_fd = prog_fd.as_fd();
|
||||||
|
let cgroup_fd = cgroup.as_fd();
|
||||||
|
let attach_type = self.data.expected_attach_type.unwrap();
|
||||||
|
let btf_id = self.data.attach_btf_id.ok_or(ProgramError::NotLoaded)?;
|
||||||
|
let link_fd = bpf_link_create(
|
||||||
|
prog_fd,
|
||||||
|
LinkTarget::Fd(cgroup_fd),
|
||||||
|
attach_type,
|
||||||
|
0,
|
||||||
|
Some(BpfLinkCreateArgs::TargetBtfId(btf_id)),
|
||||||
|
)
|
||||||
|
.map_err(|(_, io_error)| SyscallError {
|
||||||
|
call: "bpf_link_create",
|
||||||
|
io_error,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.data.links.insert(LsmLink::new(FdLink::new(link_fd)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define_link_wrapper!(
|
||||||
|
/// The link used by [LsmCgroup] programs.
|
||||||
|
LsmLink,
|
||||||
|
/// The type returned by [LsmCgroup::attach]. Can be passed to [LsmCgroup::detach].
|
||||||
|
LsmLinkId,
|
||||||
|
FdLink,
|
||||||
|
FdLinkId,
|
||||||
|
LsmCgroup,
|
||||||
|
);
|
@ -0,0 +1,83 @@
|
|||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{ErrorKind, Write},
|
||||||
|
net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use aya::{
|
||||||
|
programs::{lsm_cgroup::LsmCgroup, Lsm},
|
||||||
|
util::KernelVersion,
|
||||||
|
Btf, Ebpf,
|
||||||
|
};
|
||||||
|
use nix::{
|
||||||
|
sys::wait::waitpid,
|
||||||
|
unistd::{fork, getpid, ForkResult},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsm_cgroup() {
|
||||||
|
let kernel_version = KernelVersion::current().unwrap();
|
||||||
|
if kernel_version < KernelVersion::new(6, 0, 0) {
|
||||||
|
eprintln!("skipping lsm_cgroup test on kernel {kernel_version:?}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bpf: Ebpf = Ebpf::load(crate::TEST).unwrap();
|
||||||
|
let prog: &mut LsmCgroup = bpf
|
||||||
|
.program_mut("test_lsmcgroup")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let btf = Btf::from_sys_fs().expect("could not get btf from sys");
|
||||||
|
prog.load("socket_bind", &btf).unwrap();
|
||||||
|
|
||||||
|
let cgroup_path = Path::new("/sys/fs/cgroup/lsm_cgroup_test");
|
||||||
|
if !cgroup_path.exists() {
|
||||||
|
std::fs::create_dir_all(cgroup_path).expect("could not create the cgroup dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = prog.attach(File::open(cgroup_path).unwrap()).unwrap();
|
||||||
|
|
||||||
|
match unsafe { fork().expect("Failed to fork process") } {
|
||||||
|
ForkResult::Parent { child } => {
|
||||||
|
waitpid(Some(child), None).unwrap();
|
||||||
|
|
||||||
|
let pid = getpid();
|
||||||
|
|
||||||
|
let mut f = File::create(cgroup_path.join("cgroup.procs"))
|
||||||
|
.expect("could not open cgroup procs");
|
||||||
|
f.write_fmt(format_args!("{}", pid.as_raw() as u64))
|
||||||
|
.expect("could not write into procs file");
|
||||||
|
|
||||||
|
assert_matches::assert_matches!(TcpListener::bind("127.0.0.1:12345"), Err(e) => assert_eq!(
|
||||||
|
e.kind(), ErrorKind::PermissionDenied)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ForkResult::Child => {
|
||||||
|
assert_matches::assert_matches!(TcpListener::bind("127.0.0.1:12345"), Ok(listener) => assert_eq!(
|
||||||
|
listener.local_addr().unwrap(), SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 12345)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsm() {
|
||||||
|
let kernel_version = KernelVersion::current().unwrap();
|
||||||
|
if kernel_version < KernelVersion::new(5, 7, 0) {
|
||||||
|
eprintln!("skipping lsm test on kernel {kernel_version:?}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bpf: Ebpf = Ebpf::load(crate::TEST).unwrap();
|
||||||
|
let prog: &mut Lsm = bpf.program_mut("test_lsm").unwrap().try_into().unwrap();
|
||||||
|
let btf = Btf::from_sys_fs().expect("could not get btf from sys");
|
||||||
|
prog.load("socket_bind", &btf).unwrap();
|
||||||
|
|
||||||
|
prog.attach().unwrap();
|
||||||
|
|
||||||
|
assert_matches::assert_matches!(TcpListener::bind("127.0.0.1:12345"), Err(e) => assert_eq!(
|
||||||
|
e.kind(), ErrorKind::PermissionDenied)
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue