diff --git a/Cargo.toml b/Cargo.toml index b49d26528..baa6b9205 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ indoc = { version = "2.0", default-features = false } libc = { version = "0.2.105", default-features = false } log = { version = "0.4", default-features = false } netns-rs = { version = "0.1", default-features = false } -nix = { version = "0.29.0", default-features = false } +nix = { version = "0.29.0", default-features = true } num_enum = { version = "0.7", default-features = false } object = { version = "0.36", default-features = false } octorust = { version = "0.9.0", default-features = false } diff --git a/aya-ebpf-macros/src/lib.rs b/aya-ebpf-macros/src/lib.rs index acb6fbd93..598a410cc 100644 --- a/aya-ebpf-macros/src/lib.rs +++ b/aya-ebpf-macros/src/lib.rs @@ -10,6 +10,7 @@ mod fentry; mod fexit; mod kprobe; mod lsm; +mod lsm_cgroup; mod map; mod perf_event; mod raw_tracepoint; @@ -34,6 +35,7 @@ use fentry::FEntry; use fexit::FExit; use kprobe::{KProbe, KProbeKind}; use lsm::Lsm; +use lsm_cgroup::LsmCgroup; use map::Map; use perf_event::PerfEvent; use proc_macro::TokenStream; @@ -326,6 +328,49 @@ pub fn lsm(attrs: TokenStream, item: TokenStream) -> TokenStream { .into() } +/// Marks a function as an LSM program that can be attached to cgroups. +/// This program will only trigger for workloads in the attached cgroups. +/// Used to implement security policy and audit logging. +/// +/// The hook name is the first and only argument to the macro. +/// +/// 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 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 +/// use aya_ebpf::{macros::lsm_cgroup, programs::LsmContext}; +/// +/// #[lsm_cgroup(hook = "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 { +/// Err(0) +/// } +/// ``` +#[proc_macro_attribute] +pub fn lsm_cgroup(attrs: TokenStream, item: TokenStream) -> TokenStream { + match LsmCgroup::parse(attrs.into(), item.into()) { + Ok(prog) => prog.expand(), + Err(err) => err.into_compile_error(), + } + .into() +} + /// Marks a function as a [BTF-enabled raw tracepoint][1] eBPF program that can be attached at /// a pre-defined kernel trace point. /// diff --git a/aya-ebpf-macros/src/lsm.rs b/aya-ebpf-macros/src/lsm.rs index 2eb53dcd2..a4d19b9a0 100644 --- a/aya-ebpf-macros/src/lsm.rs +++ b/aya-ebpf-macros/src/lsm.rs @@ -19,6 +19,7 @@ impl Lsm { let hook = pop_string_arg(&mut args, "hook"); let sleepable = pop_bool_arg(&mut args, "sleepable"); err_on_unknown_args(&args)?; + Ok(Self { item, hook, @@ -39,15 +40,15 @@ impl Lsm { block: _, } = item; let section_prefix = if *sleepable { "lsm.s" } else { "lsm" }; - let section_name: Cow<'_, _> = if let Some(hook) = hook { - format!("{}/{}", section_prefix, hook).into() + 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. - let fn_name = &sig.ident; quote! { #[no_mangle] #[link_section = #section_name] diff --git a/aya-ebpf-macros/src/lsm_cgroup.rs b/aya-ebpf-macros/src/lsm_cgroup.rs new file mode 100644 index 000000000..96040e5e8 --- /dev/null +++ b/aya-ebpf-macros/src/lsm_cgroup.rs @@ -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, +} + +impl LsmCgroup { + pub(crate) fn parse(attrs: TokenStream, item: TokenStream) -> Result { + 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()); + } +} diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index 4f115024b..59047aa2a 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -27,7 +27,8 @@ use crate::{ }, maps::{bpf_map_def, BtfMap, BtfMapDef, LegacyMap, Map, PinningType, MINIMUM_MAP_SIZE}, programs::{ - CgroupSockAddrAttachType, CgroupSockAttachType, CgroupSockoptAttachType, XdpAttachType, + CgroupSockAddrAttachType, CgroupSockAttachType, CgroupSockoptAttachType, LsmAttachType, + XdpAttachType, }, relocation::*, util::HashMap, @@ -274,6 +275,10 @@ pub enum ProgramSection { RawTracePoint, Lsm { sleepable: bool, + attach_type: LsmAttachType, + }, + LsmCgroup { + attach_type: LsmAttachType, }, BtfTracePoint, FEntry { @@ -434,8 +439,17 @@ impl FromStr for ProgramSection { "lirc_mode2" => LircMode2, "perf_event" => PerfEvent, "raw_tp" | "raw_tracepoint" => RawTracePoint, - "lsm" => Lsm { sleepable: false }, - "lsm.s" => Lsm { sleepable: true }, + "lsm" => Lsm { + sleepable: false, + attach_type: LsmAttachType::Mac, + }, + "lsm.s" => Lsm { + sleepable: true, + attach_type: LsmAttachType::Mac, + }, + "lsm_cgroup" => LsmCgroup { + attach_type: LsmAttachType::Cgroup, + }, "fentry" => FEntry { sleepable: false }, "fentry.s" => FEntry { sleepable: true }, "fexit" => FExit { sleepable: false }, @@ -2190,7 +2204,7 @@ mod tests { Some(Program { section: ProgramSection::Lsm { sleepable: false, - .. + attach_type: LsmAttachType::Mac }, .. }) @@ -2223,6 +2237,31 @@ mod tests { ); } + #[test] + fn test_parse_section_lsm_cgroup() { + let mut obj = fake_obj(); + fake_sym(&mut obj, 0, 0, "foo", FAKE_INS_LEN); + + assert_matches!( + obj.parse_section(fake_section( + EbpfSectionKind::Program, + "lsm_cgroup/foo", + bytes_of(&fake_ins()), + None + )), + Ok(()) + ); + assert_matches!( + obj.programs.get("foo"), + Some(Program { + section: ProgramSection::LsmCgroup { + attach_type: LsmAttachType::Cgroup, + }, + .. + }) + ); + } + #[test] fn test_parse_section_btf_tracepoint() { let mut obj = fake_obj(); diff --git a/aya-obj/src/programs/lsm.rs b/aya-obj/src/programs/lsm.rs new file mode 100644 index 000000000..9379375ce --- /dev/null +++ b/aya-obj/src/programs/lsm.rs @@ -0,0 +1,21 @@ +//! XDP programs. + +use crate::generated::bpf_attach_type; + +/// Defines where to attach an `XDP` program. +#[derive(Copy, Clone, Debug)] +pub enum LsmAttachType { + /// Cgroup based LSM program + Cgroup, + /// MAC based LSM program + Mac, +} + +impl From for bpf_attach_type { + fn from(value: LsmAttachType) -> Self { + match value { + LsmAttachType::Cgroup => bpf_attach_type::BPF_LSM_CGROUP, + LsmAttachType::Mac => bpf_attach_type::BPF_LSM_MAC, + } + } +} diff --git a/aya-obj/src/programs/mod.rs b/aya-obj/src/programs/mod.rs index b6e91e7a0..600450d31 100644 --- a/aya-obj/src/programs/mod.rs +++ b/aya-obj/src/programs/mod.rs @@ -3,10 +3,12 @@ pub mod cgroup_sock; pub mod cgroup_sock_addr; pub mod cgroup_sockopt; +pub mod lsm; mod types; pub mod xdp; pub use cgroup_sock::CgroupSockAttachType; pub use cgroup_sock_addr::CgroupSockAddrAttachType; pub use cgroup_sockopt::CgroupSockoptAttachType; +pub use lsm::LsmAttachType; pub use xdp::XdpAttachType; diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index acce6c16c..8aacb1e16 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -30,8 +30,9 @@ use crate::{ programs::{ BtfTracePoint, CgroupDevice, CgroupSkb, CgroupSkbAttachType, CgroupSock, CgroupSockAddr, CgroupSockopt, CgroupSysctl, Extension, FEntry, FExit, Iter, KProbe, LircMode2, Lsm, - PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier, - SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, + LsmCgroup, PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, + SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, + UProbe, Xdp, }, sys::{ bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, @@ -411,7 +412,11 @@ impl<'a> EbpfLoader<'a> { ProgramSection::Extension | ProgramSection::FEntry { sleepable: _ } | ProgramSection::FExit { sleepable: _ } - | ProgramSection::Lsm { sleepable: _ } + | ProgramSection::Lsm { + sleepable: _, + attach_type: _, + } + | ProgramSection::LsmCgroup { attach_type: _ } | ProgramSection::BtfTracePoint | ProgramSection::Iter { sleepable: _ } => { return Err(EbpfError::BtfError(err)) @@ -649,13 +654,25 @@ impl<'a> EbpfLoader<'a> { ProgramSection::RawTracePoint => Program::RawTracePoint(RawTracePoint { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), - ProgramSection::Lsm { sleepable } => { + ProgramSection::Lsm { + sleepable, + attach_type, + } => { let mut data = ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level); if *sleepable { data.flags = BPF_F_SLEEPABLE; } - Program::Lsm(Lsm { data }) + Program::Lsm(Lsm { + data, + attach_type: *attach_type, + }) + } + ProgramSection::LsmCgroup { attach_type } => { + Program::LsmCgroup(LsmCgroup { + data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), + attach_type: *attach_type, + }) } ProgramSection::BtfTracePoint => Program::BtfTracePoint(BtfTracePoint { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), diff --git a/aya/src/programs/lsm.rs b/aya/src/programs/lsm.rs index bcf9d052e..5f9299824 100644 --- a/aya/src/programs/lsm.rs +++ b/aya/src/programs/lsm.rs @@ -1,8 +1,10 @@ //! LSM probes. - use crate::{ - generated::{bpf_attach_type::BPF_LSM_MAC, bpf_prog_type::BPF_PROG_TYPE_LSM}, - obj::btf::{Btf, BtfKind}, + generated::bpf_prog_type::BPF_PROG_TYPE_LSM, + obj::{ + btf::{Btf, BtfKind}, + programs::LsmAttachType, + }, programs::{ define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId, ProgramData, ProgramError, @@ -24,7 +26,7 @@ use crate::{ /// The minimum kernel version required to use this feature is 5.7. /// /// # Examples -/// +/// ## LSM with MAC attachment type /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum LsmError { @@ -44,12 +46,12 @@ use crate::{ /// program.attach()?; /// # 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 Lsm { pub(crate) data: ProgramData, + pub(crate) attach_type: LsmAttachType, } impl Lsm { @@ -60,7 +62,7 @@ impl Lsm { /// * `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_MAC); + self.data.expected_attach_type = Some(self.attach_type.into()); 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)?); diff --git a/aya/src/programs/lsm_cgroup.rs b/aya/src/programs/lsm_cgroup.rs new file mode 100644 index 000000000..9f0b8630b --- /dev/null +++ b/aya/src/programs/lsm_cgroup.rs @@ -0,0 +1,107 @@ +//! LSM probes. + +use std::os::fd::AsFd; + +use crate::{ + generated::bpf_prog_type::BPF_PROG_TYPE_LSM, + obj::{ + btf::{Btf, BtfKind}, + programs::LsmAttachType, + }, + 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. 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 +/// ## LSM with cgroup attachment type +/// ```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}; +/// +/// let btf = Btf::from_sys_fs()?; +/// let file = File::open("/sys/fs/cgroup/unified")?; +/// 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, + pub(crate) attach_type: LsmAttachType, +} + +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(self.attach_type.into()); + 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(&mut self, cgroup: T) -> Result { + 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, +); diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 223723390..40feed610 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -56,6 +56,7 @@ pub mod kprobe; pub mod links; pub mod lirc_mode2; pub mod lsm; +pub mod lsm_cgroup; pub mod perf_attach; pub mod perf_event; pub mod raw_trace_point; @@ -100,6 +101,7 @@ pub use crate::programs::{ links::{CgroupAttachMode, Link, LinkOrder}, lirc_mode2::LircMode2, lsm::Lsm, + lsm_cgroup::LsmCgroup, perf_event::{PerfEvent, PerfEventScope, PerfTypeId, SamplePolicy}, probe::ProbeKind, raw_trace_point::RawTracePoint, @@ -295,6 +297,8 @@ pub enum Program { RawTracePoint(RawTracePoint), /// A [`Lsm`] program Lsm(Lsm), + /// A [`LsmCgroup`] program + LsmCgroup(LsmCgroup), /// A [`BtfTracePoint`] program BtfTracePoint(BtfTracePoint), /// A [`FEntry`] program @@ -332,6 +336,7 @@ impl Program { Self::PerfEvent(_) => ProgramType::PerfEvent, Self::RawTracePoint(_) => ProgramType::RawTracePoint, Self::Lsm(_) => ProgramType::Lsm, + Self::LsmCgroup(_) => ProgramType::Lsm, // The following program types are a subset of `TRACING` programs: // // - `BPF_TRACE_RAW_TP` (`BtfTracePoint`) @@ -371,6 +376,7 @@ impl Program { Self::PerfEvent(p) => p.pin(path), Self::RawTracePoint(p) => p.pin(path), Self::Lsm(p) => p.pin(path), + Self::LsmCgroup(p) => p.pin(path), Self::BtfTracePoint(p) => p.pin(path), Self::FEntry(p) => p.pin(path), Self::FExit(p) => p.pin(path), @@ -402,6 +408,7 @@ impl Program { Self::PerfEvent(mut p) => p.unload(), Self::RawTracePoint(mut p) => p.unload(), Self::Lsm(mut p) => p.unload(), + Self::LsmCgroup(mut p) => p.unload(), Self::BtfTracePoint(mut p) => p.unload(), Self::FEntry(mut p) => p.unload(), Self::FExit(mut p) => p.unload(), @@ -435,6 +442,7 @@ impl Program { Self::PerfEvent(p) => p.fd(), Self::RawTracePoint(p) => p.fd(), Self::Lsm(p) => p.fd(), + Self::LsmCgroup(p) => p.fd(), Self::BtfTracePoint(p) => p.fd(), Self::FEntry(p) => p.fd(), Self::FExit(p) => p.fd(), @@ -469,6 +477,7 @@ impl Program { Self::PerfEvent(p) => p.info(), Self::RawTracePoint(p) => p.info(), Self::Lsm(p) => p.info(), + Self::LsmCgroup(p) => p.info(), Self::BtfTracePoint(p) => p.info(), Self::FEntry(p) => p.info(), Self::FExit(p) => p.info(), @@ -780,6 +789,7 @@ impl_program_unload!( LircMode2, PerfEvent, Lsm, + LsmCgroup, RawTracePoint, BtfTracePoint, FEntry, @@ -821,6 +831,7 @@ impl_fd!( LircMode2, PerfEvent, Lsm, + LsmCgroup, RawTracePoint, BtfTracePoint, FEntry, @@ -927,6 +938,7 @@ impl_program_pin!( LircMode2, PerfEvent, Lsm, + LsmCgroup, RawTracePoint, BtfTracePoint, FEntry, @@ -967,7 +979,6 @@ impl_from_pin!( CgroupSysctl, LircMode2, PerfEvent, - Lsm, RawTracePoint, BtfTracePoint, FEntry, @@ -1023,6 +1034,7 @@ impl_try_from_program!( LircMode2, PerfEvent, Lsm, + LsmCgroup, RawTracePoint, BtfTracePoint, FEntry, @@ -1050,6 +1062,7 @@ impl_info!( LircMode2, PerfEvent, Lsm, + LsmCgroup, RawTracePoint, BtfTracePoint, FEntry, diff --git a/test/integration-ebpf/src/test.rs b/test/integration-ebpf/src/test.rs index 88f01e891..2a6507452 100644 --- a/test/integration-ebpf/src/test.rs +++ b/test/integration-ebpf/src/test.rs @@ -3,8 +3,8 @@ use aya_ebpf::{ bindings::xdp_action, - macros::{kprobe, kretprobe, tracepoint, uprobe, uretprobe, xdp}, - programs::{ProbeContext, RetProbeContext, TracePointContext, XdpContext}, + macros::{kprobe, kretprobe, lsm, lsm_cgroup, tracepoint, uprobe, uretprobe, xdp}, + programs::{LsmContext, ProbeContext, RetProbeContext, TracePointContext, XdpContext}, }; #[xdp] @@ -44,6 +44,16 @@ pub fn test_uretprobe(_ctx: RetProbeContext) -> u32 { 0 } +#[lsm_cgroup(hook = "socket_bind")] +pub fn test_lsmcgroup(_ctx: LsmContext) -> i32 { + 0 +} + +#[lsm(hook = "socket_bind")] +pub fn test_lsm(_ctx: LsmContext) -> i32 { + -1 +} + #[cfg(not(test))] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { diff --git a/test/integration-test/Cargo.toml b/test/integration-test/Cargo.toml index 4cdeaa284..fdcdb63a5 100644 --- a/test/integration-test/Cargo.toml +++ b/test/integration-test/Cargo.toml @@ -28,6 +28,7 @@ test-case = { workspace = true } test-log = { workspace = true, features = ["log"] } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] } xdpilone = { workspace = true } +nix = { workspace = true, features = ["process"] } [build-dependencies] anyhow = { workspace = true } diff --git a/test/integration-test/src/tests.rs b/test/integration-test/src/tests.rs index 9ca83669f..13d4ed9c3 100644 --- a/test/integration-test/src/tests.rs +++ b/test/integration-test/src/tests.rs @@ -5,6 +5,7 @@ mod info; mod iter; mod load; mod log; +mod lsm; mod raw_tracepoint; mod rbpf; mod relocations; diff --git a/test/integration-test/src/tests/lsm.rs b/test/integration-test/src/tests/lsm.rs new file mode 100644 index 000000000..4a18333fa --- /dev/null +++ b/test/integration-test/src/tests/lsm.rs @@ -0,0 +1,82 @@ +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"); + + 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) + ); +}