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 2691ad5ce..d9251adf2 100644 --- a/aya-ebpf-macros/src/lsm.rs +++ b/aya-ebpf-macros/src/lsm.rs @@ -1,15 +1,12 @@ use std::borrow::Cow; - use proc_macro2::TokenStream; use quote::quote; use syn::{ItemFn, Result}; - use crate::args::{err_on_unknown_args, pop_bool_arg, pop_string_arg}; pub(crate) struct Lsm { item: ItemFn, hook: Option, - cgroup: bool, sleepable: bool, } @@ -19,64 +16,43 @@ impl Lsm { let mut args = syn::parse2(attrs)?; let hook = pop_string_arg(&mut args, "hook"); let sleepable = pop_bool_arg(&mut args, "sleepable"); - let cgroup = pop_bool_arg(&mut args, "cgroup"); err_on_unknown_args(&args)?; Ok(Self { item, hook, - cgroup, sleepable, }) } pub(crate) fn expand(&self) -> TokenStream { - if self.cgroup{ - let section_name = if let Some(name) = &self.hook{ - format!("lsm_cgroup/{}", name) - } else { - ("lsm_cgroup").to_owned() - }; - - let fn_name = &self.item.sig.ident; - let item = &self.item; - - quote! { - #[no_mangle] - #[link_section = #section_name] - fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 { - return #fn_name(::aya_ebpf::programs::LsmContext::new(ctx)); - - #item - } - } + let Self { item, hook, sleepable } = self; + let ItemFn { + attrs: _, + vis, + sig, + block: _, + } = item; + let section_prefix = if *sleepable { "lsm.s" } else { "lsm" }; + let section_name: Cow<'_, _>= if let Some(name) = hook { + format!("{}/{}", section_prefix, name).into() } else { - let section_prefix = if self.sleepable { "lsm.s" } else { "lsm" }; - let section_name: Cow<'_, _> = if let Some(hook) = &self.hook { - format!("{}/{}", section_prefix, hook).into() - } else { - section_prefix.into() - }; - - let fn_vis = &self.item.vis; - let fn_name = self.item.sig.ident.clone(); - let item = &self.item; + 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)); - quote! { - #[no_mangle] - #[link_section = #section_name] - #fn_vis fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 { - return #fn_name(::aya_ebpf::programs::LsmContext::new(ctx)); - - #item - } + #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. } #[cfg(test)] @@ -141,33 +117,4 @@ mod tests { }; assert_eq!(expected.to_string(), expanded.to_string()); } - - #[test] - fn test_lsm_cgroup() { - let prog = Lsm::parse( - parse_quote! { - hook = "bprm_committed_creds", - cgroup - }, - 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-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 056381d1d..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, LsmAttachType, XdpAttachType + CgroupSockAddrAttachType, CgroupSockAttachType, CgroupSockoptAttachType, LsmAttachType, + XdpAttachType, }, relocation::*, util::HashMap, @@ -276,6 +277,9 @@ pub enum ProgramSection { sleepable: bool, attach_type: LsmAttachType, }, + LsmCgroup { + attach_type: LsmAttachType, + }, BtfTracePoint, FEntry { sleepable: bool, @@ -435,9 +439,17 @@ impl FromStr for ProgramSection { "lirc_mode2" => LircMode2, "perf_event" => PerfEvent, "raw_tp" | "raw_tracepoint" => RawTracePoint, - "lsm" => Lsm { sleepable: false, attach_type: LsmAttachType::Mac}, - "lsm.s" => Lsm { sleepable: true, attach_type: LsmAttachType::Mac }, - "lsm_cgroup" => Lsm { sleepable: false, attach_type: LsmAttachType::Cgroup }, + "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 }, @@ -2242,9 +2254,8 @@ mod tests { assert_matches!( obj.programs.get("foo"), Some(Program { - section: ProgramSection::Lsm { - sleepable: false, - attach_type: LsmAttachType::Cgroup + section: ProgramSection::LsmCgroup { + attach_type: LsmAttachType::Cgroup, }, .. }) diff --git a/aya-obj/src/programs/mod.rs b/aya-obj/src/programs/mod.rs index ee00aa8dc..600450d31 100644 --- a/aya-obj/src/programs/mod.rs +++ b/aya-obj/src/programs/mod.rs @@ -3,12 +3,12 @@ pub mod cgroup_sock; pub mod cgroup_sock_addr; pub mod cgroup_sockopt; +pub mod lsm; mod types; pub mod xdp; -pub mod lsm; pub use cgroup_sock::CgroupSockAttachType; pub use cgroup_sock_addr::CgroupSockAddrAttachType; pub use cgroup_sockopt::CgroupSockoptAttachType; -pub use xdp::XdpAttachType; pub use lsm::LsmAttachType; +pub use xdp::XdpAttachType; diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index f8d545c1b..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: _, attach_type: _ } + | ProgramSection::Lsm { + sleepable: _, + attach_type: _, + } + | ProgramSection::LsmCgroup { attach_type: _ } | ProgramSection::BtfTracePoint | ProgramSection::Iter { sleepable: _ } => { return Err(EbpfError::BtfError(err)) @@ -649,16 +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 , attach_type} => { + 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 { + 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 86de53a5d..abf3335eb 100644 --- a/aya/src/programs/lsm.rs +++ b/aya/src/programs/lsm.rs @@ -1,16 +1,14 @@ //! LSM probes. - -use std::os::fd::AsFd; - -use aya_obj::programs::LsmAttachType; - use crate::{ generated::bpf_prog_type::BPF_PROG_TYPE_LSM, - obj::btf::{Btf, BtfKind}, + obj::{ + btf::{Btf, BtfKind}, + programs::LsmAttachType, + }, programs::{ define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId, ProgramData, ProgramError, - }, sys::{bpf_link_create, LinkTarget, SyscallError}, + }, }; /// A program that attaches to Linux LSM hooks. Used to implement security policy and @@ -28,7 +26,7 @@ use crate::{ /// The minimum kernel version required to use this feature is 5.7. /// /// # Examples -/// LSM with MAC attachment type +/// ## LSM with MAC attachment type /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum LsmError { @@ -48,29 +46,6 @@ use crate::{ /// program.attach()?; /// # Ok::<(), LsmError>(()) /// ``` -/// -/// 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::Lsm, BtfError, Btf}; -/// -/// let btf = Btf::from_sys_fs()?; -/// let file = File::open("/sys/fs/cgroup/unified")?; -/// let program: &mut Lsm = bpf.program_mut("lsm_prog").unwrap().try_into()?; -/// program.load("security_bprm_exec", &btf)?; -/// program.attach(Some(file))?; -/// # Ok::<(), LsmError>(()) -/// ``` -/// /// /// [1]: https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h #[derive(Debug)] @@ -98,47 +73,11 @@ impl Lsm { /// Attaches the program. /// /// The returned value can be used to detach, see [Lsm::detach]. - pub fn attach(&mut self, cgroup: Option) -> Result { - match self.attach_type{ - LsmAttachType::Cgroup => { - if let Some(cgroup) = cgroup{ - 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; - - let link_fd = bpf_link_create( - prog_fd, - LinkTarget::Fd(cgroup_fd), - attach_type, - btf_id, - 0, - None, - ) - .map_err(|(_, io_error)| SyscallError { - call: "bpf_link_create", - io_error, - })?; - - self.data - .links - .insert(LsmLink::new(FdLink::new( - link_fd, - ))) - }else { - return Err(ProgramError::UnexpectedProgramType); - } - }, - LsmAttachType::Mac => { - attach_raw_tracepoint(&mut self.data, None) - } - } - + pub fn attach(&mut self) -> Result { + attach_raw_tracepoint(&mut self.data, None) } } - define_link_wrapper!( /// The link used by [Lsm] programs. LsmLink, diff --git a/aya/src/programs/lsm_cgroup.rs b/aya/src/programs/lsm_cgroup.rs new file mode 100644 index 000000000..64f42dffd --- /dev/null +++ b/aya/src/programs/lsm_cgroup.rs @@ -0,0 +1,109 @@ +//! 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 [Lsm::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 [Lsm] programs. + LsmLink, + /// The type returned by [Lsm::attach]. Can be passed to [Lsm::detach]. + LsmLinkId, + FdLink, + FdLinkId, + LsmCgroup, +); diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 8f9951cda..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, @@ -1022,6 +1034,7 @@ impl_try_from_program!( LircMode2, PerfEvent, Lsm, + LsmCgroup, RawTracePoint, BtfTracePoint, FEntry, @@ -1049,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 50325ca25..2a6507452 100644 --- a/test/integration-ebpf/src/test.rs +++ b/test/integration-ebpf/src/test.rs @@ -2,7 +2,9 @@ #![no_main] use aya_ebpf::{ - bindings::xdp_action, helpers::{bpf_get_current_cgroup_id, bpf_get_current_pid_tgid}, macros::{kprobe, kretprobe, lsm, tracepoint, uprobe, uretprobe, xdp}, programs::{LsmContext, ProbeContext, RetProbeContext, TracePointContext, XdpContext} + bindings::xdp_action, + macros::{kprobe, kretprobe, lsm, lsm_cgroup, tracepoint, uprobe, uretprobe, xdp}, + programs::{LsmContext, ProbeContext, RetProbeContext, TracePointContext, XdpContext}, }; #[xdp] @@ -42,11 +44,16 @@ pub fn test_uretprobe(_ctx: RetProbeContext) -> u32 { 0 } -#[lsm(hook="socket_bind", cgroup)] +#[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 8980b18c0..fdcdb63a5 100644 --- a/test/integration-test/Cargo.toml +++ b/test/integration-test/Cargo.toml @@ -28,7 +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"]} +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 5b8873ce3..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; @@ -14,4 +15,3 @@ mod strncmp; mod tcx; mod uprobe_cookie; mod xdp; -mod lsm; diff --git a/test/integration-test/src/tests/lsm.rs b/test/integration-test/src/tests/lsm.rs index e9208365c..4a18333fa 100644 --- a/test/integration-test/src/tests/lsm.rs +++ b/test/integration-test/src/tests/lsm.rs @@ -1,6 +1,15 @@ -use std::{fs::File, io::{ErrorKind, Write}, path::Path}; -use aya::{programs::Lsm, util::KernelVersion, Btf, Ebpf}; -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener}; +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}, @@ -15,40 +24,59 @@ fn lsm_cgroup() { } let mut bpf: Ebpf = Ebpf::load(crate::TEST).unwrap(); - let prog: &mut Lsm = bpf.program_mut("test_lsmcgroup").unwrap().try_into().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"); - if let Err(err) = prog.load("socket_bind", &btf) { - panic!("{err}"); - } + 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 cgroup_path = Path::new(".").join("/sys/fs/cgroup/").join("lsm_cgroup_test"); - - let _ = std::fs::create_dir_all( cgroup_path.clone()).expect("could not create the cgroup dir"); - - let p = prog.attach( - Some(File::open(cgroup_path.clone()).unwrap()), - ) - .unwrap(); - - unsafe { - match 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))) - ); - } + 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) + ); +}