lsm-cgroup: attachment type support

reviewable/pr1131/r1
Altug Bozkurt 3 months ago committed by altug bozkurt
parent f34d355d7d
commit 3787b4be44

@ -78,7 +78,7 @@ indoc = { version = "2.0", default-features = false }
libc = { version = "0.2.105", default-features = false } libc = { version = "0.2.105", default-features = false }
log = { version = "0.4", default-features = false } log = { version = "0.4", default-features = false }
netns-rs = { version = "0.1", 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 } num_enum = { version = "0.7", default-features = false }
object = { version = "0.36", default-features = false } object = { version = "0.36", default-features = false }
octorust = { version = "0.9.0", default-features = false } octorust = { version = "0.9.0", default-features = false }

@ -9,6 +9,7 @@ use crate::args::{err_on_unknown_args, pop_bool_arg, pop_string_arg};
pub(crate) struct Lsm { pub(crate) struct Lsm {
item: ItemFn, item: ItemFn,
hook: Option<String>, hook: Option<String>,
cgroup: bool,
sleepable: bool, sleepable: bool,
} }
@ -18,46 +19,64 @@ impl Lsm {
let mut args = syn::parse2(attrs)?; let mut args = syn::parse2(attrs)?;
let hook = pop_string_arg(&mut args, "hook"); let hook = pop_string_arg(&mut args, "hook");
let sleepable = pop_bool_arg(&mut args, "sleepable"); let sleepable = pop_bool_arg(&mut args, "sleepable");
let cgroup = pop_bool_arg(&mut args, "cgroup");
err_on_unknown_args(&args)?; err_on_unknown_args(&args)?;
Ok(Self { Ok(Self {
item, item,
hook, hook,
cgroup,
sleepable, sleepable,
}) })
} }
pub(crate) fn expand(&self) -> TokenStream { pub(crate) fn expand(&self) -> TokenStream {
let Self { if self.cgroup{
item, let section_name = if let Some(name) = &self.hook{
hook, format!("lsm_cgroup/{}", name)
sleepable, } else {
} = self; ("lsm_cgroup").to_owned()
let ItemFn { };
attrs: _,
vis, let fn_name = &self.item.sig.ident;
sig, let item = &self.item;
block: _,
} = item; quote! {
let section_prefix = if *sleepable { "lsm.s" } else { "lsm" }; #[no_mangle]
let section_name: Cow<'_, _> = if let Some(hook) = hook { #[link_section = #section_name]
format!("{}/{}", section_prefix, hook).into() fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 {
return #fn_name(::aya_ebpf::programs::LsmContext::new(ctx));
#item
}
}
} else { } else {
section_prefix.into() let section_prefix = if self.sleepable { "lsm.s" } else { "lsm" };
}; let section_name: Cow<'_, _> = if let Some(hook) = &self.hook {
// LSM probes need to return an integer corresponding to the correct format!("{}/{}", section_prefix, hook).into()
// policy decision. Therefore we do not simply default to a return value } else {
// of 0 as in other program types. section_prefix.into()
let fn_name = &sig.ident; };
quote! {
#[no_mangle] let fn_vis = &self.item.vis;
#[link_section = #section_name] let fn_name = self.item.sig.ident.clone();
#vis fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 { let item = &self.item;
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)] #[cfg(test)]
@ -122,4 +141,33 @@ mod tests {
}; };
assert_eq!(expected.to_string(), expanded.to_string()); 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());
}
} }

@ -27,7 +27,7 @@ use crate::{
}, },
maps::{bpf_map_def, BtfMap, BtfMapDef, LegacyMap, Map, PinningType, MINIMUM_MAP_SIZE}, maps::{bpf_map_def, BtfMap, BtfMapDef, LegacyMap, Map, PinningType, MINIMUM_MAP_SIZE},
programs::{ programs::{
CgroupSockAddrAttachType, CgroupSockAttachType, CgroupSockoptAttachType, XdpAttachType, CgroupSockAddrAttachType, CgroupSockAttachType, CgroupSockoptAttachType, LsmAttachType, XdpAttachType
}, },
relocation::*, relocation::*,
util::HashMap, util::HashMap,
@ -274,6 +274,7 @@ pub enum ProgramSection {
RawTracePoint, RawTracePoint,
Lsm { Lsm {
sleepable: bool, sleepable: bool,
attach_type: LsmAttachType,
}, },
BtfTracePoint, BtfTracePoint,
FEntry { FEntry {
@ -434,8 +435,9 @@ impl FromStr for ProgramSection {
"lirc_mode2" => LircMode2, "lirc_mode2" => LircMode2,
"perf_event" => PerfEvent, "perf_event" => PerfEvent,
"raw_tp" | "raw_tracepoint" => RawTracePoint, "raw_tp" | "raw_tracepoint" => RawTracePoint,
"lsm" => Lsm { sleepable: false }, "lsm" => Lsm { sleepable: false, attach_type: LsmAttachType::Mac},
"lsm.s" => Lsm { sleepable: true }, "lsm.s" => Lsm { sleepable: true, attach_type: LsmAttachType::Mac },
"lsm_cgroup" => Lsm { sleepable: false, attach_type: LsmAttachType::Cgroup },
"fentry" => FEntry { sleepable: false }, "fentry" => FEntry { sleepable: false },
"fentry.s" => FEntry { sleepable: true }, "fentry.s" => FEntry { sleepable: true },
"fexit" => FExit { sleepable: false }, "fexit" => FExit { sleepable: false },
@ -2190,7 +2192,7 @@ mod tests {
Some(Program { Some(Program {
section: ProgramSection::Lsm { section: ProgramSection::Lsm {
sleepable: false, sleepable: false,
.. attach_type: LsmAttachType::Mac
}, },
.. ..
}) })
@ -2223,6 +2225,32 @@ 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::Lsm {
sleepable: false,
attach_type: LsmAttachType::Cgroup
},
..
})
);
}
#[test] #[test]
fn test_parse_section_btf_tracepoint() { fn test_parse_section_btf_tracepoint() {
let mut obj = fake_obj(); let mut obj = fake_obj();

@ -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<LsmAttachType> 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,
}
}
}

@ -5,8 +5,10 @@ pub mod cgroup_sock_addr;
pub mod cgroup_sockopt; pub mod cgroup_sockopt;
mod types; mod types;
pub mod xdp; pub mod xdp;
pub mod lsm;
pub use cgroup_sock::CgroupSockAttachType; pub use cgroup_sock::CgroupSockAttachType;
pub use cgroup_sock_addr::CgroupSockAddrAttachType; pub use cgroup_sock_addr::CgroupSockAddrAttachType;
pub use cgroup_sockopt::CgroupSockoptAttachType; pub use cgroup_sockopt::CgroupSockoptAttachType;
pub use xdp::XdpAttachType; pub use xdp::XdpAttachType;
pub use lsm::LsmAttachType;

@ -411,7 +411,7 @@ impl<'a> EbpfLoader<'a> {
ProgramSection::Extension ProgramSection::Extension
| ProgramSection::FEntry { sleepable: _ } | ProgramSection::FEntry { sleepable: _ }
| ProgramSection::FExit { sleepable: _ } | ProgramSection::FExit { sleepable: _ }
| ProgramSection::Lsm { sleepable: _ } | ProgramSection::Lsm { sleepable: _, attach_type: _ }
| ProgramSection::BtfTracePoint | ProgramSection::BtfTracePoint
| ProgramSection::Iter { sleepable: _ } => { | ProgramSection::Iter { sleepable: _ } => {
return Err(EbpfError::BtfError(err)) return Err(EbpfError::BtfError(err))
@ -649,13 +649,16 @@ impl<'a> EbpfLoader<'a> {
ProgramSection::RawTracePoint => Program::RawTracePoint(RawTracePoint { ProgramSection::RawTracePoint => Program::RawTracePoint(RawTracePoint {
data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
}), }),
ProgramSection::Lsm { sleepable } => { ProgramSection::Lsm { sleepable , attach_type} => {
let mut data = let mut data =
ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level); ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level);
if *sleepable { if *sleepable {
data.flags = BPF_F_SLEEPABLE; data.flags = BPF_F_SLEEPABLE;
} }
Program::Lsm(Lsm { data }) Program::Lsm(Lsm {
data,
attach_type: *attach_type,
})
} }
ProgramSection::BtfTracePoint => Program::BtfTracePoint(BtfTracePoint { ProgramSection::BtfTracePoint => Program::BtfTracePoint(BtfTracePoint {
data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),

@ -1,12 +1,16 @@
//! LSM probes. //! LSM probes.
use std::os::fd::AsFd;
use aya_obj::programs::LsmAttachType;
use crate::{ use crate::{
generated::{bpf_attach_type::BPF_LSM_MAC, bpf_prog_type::BPF_PROG_TYPE_LSM}, generated::bpf_prog_type::BPF_PROG_TYPE_LSM,
obj::btf::{Btf, BtfKind}, obj::btf::{Btf, BtfKind},
programs::{ programs::{
define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId, define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId,
ProgramData, ProgramError, ProgramData, ProgramError,
}, }, sys::{bpf_link_create, LinkTarget, SyscallError},
}; };
/// A program that attaches to Linux LSM hooks. Used to implement security policy and /// A program that attaches to Linux LSM hooks. Used to implement security policy and
@ -24,7 +28,7 @@ use crate::{
/// The minimum kernel version required to use this feature is 5.7. /// The minimum kernel version required to use this feature is 5.7.
/// ///
/// # Examples /// # Examples
/// /// LSM with MAC attachment type
/// ```no_run /// ```no_run
/// # #[derive(thiserror::Error, Debug)] /// # #[derive(thiserror::Error, Debug)]
/// # enum LsmError { /// # enum LsmError {
@ -45,11 +49,35 @@ use crate::{
/// # Ok::<(), LsmError>(()) /// # 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 /// [1]: https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h
#[derive(Debug)] #[derive(Debug)]
#[doc(alias = "BPF_PROG_TYPE_LSM")] #[doc(alias = "BPF_PROG_TYPE_LSM")]
pub struct Lsm { pub struct Lsm {
pub(crate) data: ProgramData<LsmLink>, pub(crate) data: ProgramData<LsmLink>,
pub(crate) attach_type: LsmAttachType,
} }
impl Lsm { impl Lsm {
@ -60,7 +88,7 @@ impl Lsm {
/// * `lsm_hook_name` - full name of the LSM hook that the program should /// * `lsm_hook_name` - full name of the LSM hook that the program should
/// be attached to /// be attached to
pub fn load(&mut self, lsm_hook_name: &str, btf: &Btf) -> Result<(), ProgramError> { 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}"); let type_name = format!("bpf_lsm_{lsm_hook_name}");
self.data.attach_btf_id = self.data.attach_btf_id =
Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Func)?); Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Func)?);
@ -70,11 +98,47 @@ impl Lsm {
/// Attaches the program. /// Attaches the program.
/// ///
/// The returned value can be used to detach, see [Lsm::detach]. /// The returned value can be used to detach, see [Lsm::detach].
pub fn attach(&mut self) -> Result<LsmLinkId, ProgramError> { pub fn attach<T: AsFd>(&mut self, cgroup: Option<T>) -> Result<LsmLinkId, ProgramError> {
attach_raw_tracepoint(&mut self.data, None) 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)
}
}
} }
} }
define_link_wrapper!( define_link_wrapper!(
/// The link used by [Lsm] programs. /// The link used by [Lsm] programs.
LsmLink, LsmLink,

@ -963,7 +963,6 @@ impl_from_pin!(
CgroupSysctl, CgroupSysctl,
LircMode2, LircMode2,
PerfEvent, PerfEvent,
Lsm,
RawTracePoint, RawTracePoint,
BtfTracePoint, BtfTracePoint,
FEntry, FEntry,

@ -2,9 +2,7 @@
#![no_main] #![no_main]
use aya_ebpf::{ use aya_ebpf::{
bindings::xdp_action, 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}
macros::{kprobe, kretprobe, tracepoint, uprobe, uretprobe, xdp},
programs::{ProbeContext, RetProbeContext, TracePointContext, XdpContext},
}; };
#[xdp] #[xdp]
@ -44,6 +42,11 @@ pub fn test_uretprobe(_ctx: RetProbeContext) -> u32 {
0 0
} }
#[lsm(hook="socket_bind", cgroup)]
pub fn test_lsmcgroup(_ctx: LsmContext) -> i32 {
0
}
#[cfg(not(test))] #[cfg(not(test))]
#[panic_handler] #[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { fn panic(_info: &core::panic::PanicInfo) -> ! {

@ -28,6 +28,7 @@ test-case = { workspace = true }
test-log = { workspace = true, features = ["log"] } test-log = { workspace = true, features = ["log"] }
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
xdpilone = { workspace = true } xdpilone = { workspace = true }
nix = { workspace = true, features = ["process"]}
[build-dependencies] [build-dependencies]
anyhow = { workspace = true } anyhow = { workspace = true }

@ -13,3 +13,4 @@ mod smoke;
mod strncmp; mod strncmp;
mod tcx; mod tcx;
mod xdp; mod xdp;
mod lsm;

@ -0,0 +1,54 @@
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 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 Lsm = 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}");
}
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)))
);
}
}
}
}
Loading…
Cancel
Save