Skip to content

Commit 28e6188

Browse files
Altug Bozkurtaltugbozkurt07
Altug Bozkurt
authored andcommitted
lsm: cgroup attachment type support
1 parent 39e40ba commit 28e6188

File tree

16 files changed

+595
-10
lines changed

16 files changed

+595
-10
lines changed

aya-ebpf-macros/src/lib.rs

+45
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod fentry;
1010
mod fexit;
1111
mod kprobe;
1212
mod lsm;
13+
mod lsm_cgroup;
1314
mod map;
1415
mod perf_event;
1516
mod raw_tracepoint;
@@ -34,6 +35,7 @@ use fentry::FEntry;
3435
use fexit::FExit;
3536
use kprobe::{KProbe, KProbeKind};
3637
use lsm::Lsm;
38+
use lsm_cgroup::LsmCgroup;
3739
use map::Map;
3840
use perf_event::PerfEvent;
3941
use proc_macro::TokenStream;
@@ -326,6 +328,49 @@ pub fn lsm(attrs: TokenStream, item: TokenStream) -> TokenStream {
326328
.into()
327329
}
328330

331+
/// Marks a function as an LSM program that can be attached to cgroups.
332+
/// This program will only trigger for workloads in the attached cgroups.
333+
/// Used to implement security policy and audit logging.
334+
///
335+
/// The hook name is the first and only argument to the macro.
336+
///
337+
/// LSM probes can be attached to the kernel's security hooks to implement mandatory
338+
/// access control policy and security auditing.
339+
///
340+
/// LSM probes require a kernel compiled with `CONFIG_BPF_LSM=y` and `CONFIG_DEBUG_INFO_BTF=y`.
341+
/// In order for the probes to fire, you also need the BPF LSM to be enabled through your
342+
/// kernel's boot paramters (like `lsm=lockdown,yama,bpf`).
343+
///
344+
/// # Minimum kernel version
345+
///
346+
/// The minimum kernel version required to use this feature is 6.0.
347+
///
348+
/// # Examples
349+
///
350+
/// ```no_run
351+
/// use aya_ebpf::{macros::lsm_cgroup, programs::LsmContext};
352+
///
353+
/// #[lsm_cgroup(hook = "file_open")]
354+
/// pub fn file_open(ctx: LsmContext) -> i32 {
355+
/// match unsafe { try_file_open(ctx) } {
356+
/// Ok(ret) => ret,
357+
/// Err(ret) => ret,
358+
/// }
359+
/// }
360+
///
361+
/// unsafe fn try_file_open(_ctx: LsmContext) -> Result<i32, i32> {
362+
/// Err(0)
363+
/// }
364+
/// ```
365+
#[proc_macro_attribute]
366+
pub fn lsm_cgroup(attrs: TokenStream, item: TokenStream) -> TokenStream {
367+
match LsmCgroup::parse(attrs.into(), item.into()) {
368+
Ok(prog) => prog.expand(),
369+
Err(err) => err.into_compile_error(),
370+
}
371+
.into()
372+
}
373+
329374
/// Marks a function as a [BTF-enabled raw tracepoint][1] eBPF program that can be attached at
330375
/// a pre-defined kernel trace point.
331376
///

aya-ebpf-macros/src/lsm.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ impl Lsm {
4444
} else {
4545
section_prefix.into()
4646
};
47+
let fn_name = &sig.ident;
4748
// LSM probes need to return an integer corresponding to the correct
4849
// policy decision. Therefore we do not simply default to a return value
4950
// of 0 as in other program types.
50-
let fn_name = &sig.ident;
5151
quote! {
5252
#[no_mangle]
5353
#[link_section = #section_name]

aya-ebpf-macros/src/lsm_cgroup.rs

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use std::borrow::Cow;
2+
3+
use proc_macro2::TokenStream;
4+
use quote::quote;
5+
use syn::{ItemFn, Result};
6+
7+
use crate::args::{err_on_unknown_args, pop_string_arg};
8+
9+
pub(crate) struct LsmCgroup {
10+
item: ItemFn,
11+
hook: Option<String>,
12+
}
13+
14+
impl LsmCgroup {
15+
pub(crate) fn parse(attrs: TokenStream, item: TokenStream) -> Result<Self> {
16+
let item = syn::parse2(item)?;
17+
let mut args = syn::parse2(attrs)?;
18+
let hook = pop_string_arg(&mut args, "hook");
19+
err_on_unknown_args(&args)?;
20+
21+
Ok(Self { item, hook })
22+
}
23+
24+
pub(crate) fn expand(&self) -> TokenStream {
25+
let Self { item, hook } = self;
26+
let ItemFn {
27+
attrs: _,
28+
vis,
29+
sig,
30+
block: _,
31+
} = item;
32+
let section_prefix = "lsm_cgroup";
33+
let section_name: Cow<'_, _> = if let Some(name) = hook {
34+
format!("{}/{}", section_prefix, name).into()
35+
} else {
36+
section_prefix.into()
37+
};
38+
let fn_name = &sig.ident;
39+
// LSM probes need to return an integer corresponding to the correct
40+
// policy decision. Therefore we do not simply default to a return value
41+
// of 0 as in other program types.
42+
quote! {
43+
#[no_mangle]
44+
#[link_section = #section_name]
45+
#vis fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 {
46+
return #fn_name(::aya_ebpf::programs::LsmContext::new(ctx));
47+
48+
#item
49+
}
50+
}
51+
}
52+
}
53+
54+
#[cfg(test)]
55+
mod tests {
56+
use syn::parse_quote;
57+
58+
use super::*;
59+
60+
#[test]
61+
fn test_lsm_cgroup() {
62+
let prog = LsmCgroup::parse(
63+
parse_quote! {
64+
hook = "bprm_committed_creds",
65+
},
66+
parse_quote! {
67+
fn bprm_committed_creds(ctx: &mut ::aya_ebpf::programs::LsmContext) -> i32 {
68+
0
69+
}
70+
},
71+
)
72+
.unwrap();
73+
let expanded = prog.expand();
74+
let expected = quote! {
75+
#[no_mangle]
76+
#[link_section = "lsm_cgroup/bprm_committed_creds"]
77+
fn bprm_committed_creds(ctx: *mut ::core::ffi::c_void) -> i32 {
78+
return bprm_committed_creds(::aya_ebpf::programs::LsmContext::new(ctx));
79+
80+
fn bprm_committed_creds(ctx: &mut ::aya_ebpf::programs::LsmContext) -> i32 {
81+
0
82+
}
83+
}
84+
};
85+
assert_eq!(expected.to_string(), expanded.to_string());
86+
}
87+
}

aya-obj/src/obj.rs

+26-4
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ pub enum ProgramSection {
275275
Lsm {
276276
sleepable: bool,
277277
},
278+
LsmCgroup,
278279
BtfTracePoint,
279280
FEntry {
280281
sleepable: bool,
@@ -436,6 +437,7 @@ impl FromStr for ProgramSection {
436437
"raw_tp" | "raw_tracepoint" => RawTracePoint,
437438
"lsm" => Lsm { sleepable: false },
438439
"lsm.s" => Lsm { sleepable: true },
440+
"lsm_cgroup" => LsmCgroup,
439441
"fentry" => FEntry { sleepable: false },
440442
"fentry.s" => FEntry { sleepable: true },
441443
"fexit" => FExit { sleepable: false },
@@ -2186,10 +2188,7 @@ mod tests {
21862188
assert_matches!(
21872189
obj.programs.get("foo"),
21882190
Some(Program {
2189-
section: ProgramSection::Lsm {
2190-
sleepable: false,
2191-
..
2192-
},
2191+
section: ProgramSection::Lsm { sleepable: false },
21932192
..
21942193
})
21952194
);
@@ -2221,6 +2220,29 @@ mod tests {
22212220
);
22222221
}
22232222

2223+
#[test]
2224+
fn test_parse_section_lsm_cgroup() {
2225+
let mut obj = fake_obj();
2226+
fake_sym(&mut obj, 0, 0, "foo", FAKE_INS_LEN);
2227+
2228+
assert_matches!(
2229+
obj.parse_section(fake_section(
2230+
EbpfSectionKind::Program,
2231+
"lsm_cgroup/foo",
2232+
bytes_of(&fake_ins()),
2233+
None
2234+
)),
2235+
Ok(())
2236+
);
2237+
assert_matches!(
2238+
obj.programs.get("foo"),
2239+
Some(Program {
2240+
section: ProgramSection::LsmCgroup,
2241+
..
2242+
})
2243+
);
2244+
}
2245+
22242246
#[test]
22252247
fn test_parse_section_btf_tracepoint() {
22262248
let mut obj = fake_obj();

aya/src/bpf.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ use crate::{
2424
programs::{
2525
BtfTracePoint, CgroupDevice, CgroupSkb, CgroupSkbAttachType, CgroupSock, CgroupSockAddr,
2626
CgroupSockopt, CgroupSysctl, Extension, FEntry, FExit, Iter, KProbe, LircMode2, Lsm,
27-
PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier,
28-
SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp,
27+
LsmCgroup, PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint,
28+
SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint,
29+
UProbe, Xdp,
2930
},
3031
sys::{
3132
bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported,
@@ -400,6 +401,7 @@ impl<'a> EbpfLoader<'a> {
400401
| ProgramSection::FEntry { sleepable: _ }
401402
| ProgramSection::FExit { sleepable: _ }
402403
| ProgramSection::Lsm { sleepable: _ }
404+
| ProgramSection::LsmCgroup
403405
| ProgramSection::BtfTracePoint
404406
| ProgramSection::Iter { sleepable: _ } => {
405407
return Err(EbpfError::BtfError(err))
@@ -645,6 +647,9 @@ impl<'a> EbpfLoader<'a> {
645647
}
646648
Program::Lsm(Lsm { data })
647649
}
650+
ProgramSection::LsmCgroup => Program::LsmCgroup(LsmCgroup {
651+
data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
652+
}),
648653
ProgramSection::BtfTracePoint => Program::BtfTracePoint(BtfTracePoint {
649654
data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
650655
}),

aya/src/programs/lsm_cgroup.rs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//! LSM probes.
2+
3+
use std::os::fd::AsFd;
4+
5+
use aya_obj::{
6+
btf::{Btf, BtfKind},
7+
generated::{bpf_attach_type::BPF_LSM_CGROUP, bpf_prog_type::BPF_PROG_TYPE_LSM},
8+
};
9+
10+
use crate::{
11+
programs::{define_link_wrapper, load_program, FdLink, FdLinkId, ProgramData, ProgramError},
12+
sys::{bpf_link_create, BpfLinkCreateArgs, LinkTarget, SyscallError},
13+
};
14+
15+
/// A program that attaches to Linux LSM hooks with per-cgroup attachment type. Used to implement security policy and
16+
/// audit logging.
17+
///
18+
/// LSM probes can be attached to the kernel's [security hooks][1] to implement mandatory
19+
/// access control policy and security auditing.
20+
///
21+
/// LSM probes require a kernel compiled with `CONFIG_BPF_LSM=y` and `CONFIG_DEBUG_INFO_BTF=y`.
22+
/// In order for the probes to fire, you also need the BPF LSM to be enabled through your
23+
/// kernel's boot paramters (like `lsm=lockdown,yama,bpf`).
24+
///
25+
/// # Minimum kernel version
26+
///
27+
/// The minimum kernel version required to use this feature is 6.0.
28+
///
29+
/// # Examples
30+
///
31+
/// ```no_run
32+
/// # #[derive(thiserror::Error, Debug)]
33+
/// # enum LsmError {
34+
/// # #[error(transparent)]
35+
/// # BtfError(#[from] aya::BtfError),
36+
/// # #[error(transparent)]
37+
/// # Program(#[from] aya::programs::ProgramError),
38+
/// # #[error(transparent)]
39+
/// # Ebpf(#[from] aya::EbpfError),
40+
/// # }
41+
/// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?;
42+
/// use aya::{Ebpf, programs::LsmCgroup, BtfError, Btf};
43+
/// use std::fs::File;
44+
///
45+
/// let btf = Btf::from_sys_fs()?;
46+
/// let file = File::open("/sys/fs/cgroup/unified").unwrap();
47+
/// let program: &mut LsmCgroup = bpf.program_mut("lsm_prog").unwrap().try_into()?;
48+
/// program.load("security_bprm_exec", &btf)?;
49+
/// program.attach(file)?;
50+
/// # Ok::<(), LsmError>(())
51+
/// ```
52+
///
53+
/// [1]: https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h
54+
#[derive(Debug)]
55+
#[doc(alias = "BPF_PROG_TYPE_LSM")]
56+
pub struct LsmCgroup {
57+
pub(crate) data: ProgramData<LsmLink>,
58+
}
59+
60+
impl LsmCgroup {
61+
/// Loads the program inside the kernel.
62+
///
63+
/// # Arguments
64+
///
65+
/// * `lsm_hook_name` - full name of the LSM hook that the program should
66+
/// be attached to
67+
pub fn load(&mut self, lsm_hook_name: &str, btf: &Btf) -> Result<(), ProgramError> {
68+
self.data.expected_attach_type = Some(BPF_LSM_CGROUP);
69+
let type_name = format!("bpf_lsm_{lsm_hook_name}");
70+
self.data.attach_btf_id =
71+
Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Func)?);
72+
load_program(BPF_PROG_TYPE_LSM, &mut self.data)
73+
}
74+
75+
/// Attaches the program.
76+
///
77+
/// The returned value can be used to detach, see [LsmCgroup::detach].
78+
pub fn attach<T: AsFd>(&mut self, cgroup: T) -> Result<LsmLinkId, ProgramError> {
79+
let prog_fd = self.fd()?;
80+
let prog_fd = prog_fd.as_fd();
81+
let cgroup_fd = cgroup.as_fd();
82+
let attach_type = self.data.expected_attach_type.unwrap();
83+
let btf_id = self.data.attach_btf_id.ok_or(ProgramError::NotLoaded)?;
84+
let link_fd = bpf_link_create(
85+
prog_fd,
86+
LinkTarget::Fd(cgroup_fd),
87+
attach_type,
88+
0,
89+
Some(BpfLinkCreateArgs::TargetBtfId(btf_id)),
90+
)
91+
.map_err(|(_, io_error)| SyscallError {
92+
call: "bpf_link_create",
93+
io_error,
94+
})?;
95+
96+
self.data.links.insert(LsmLink::new(FdLink::new(link_fd)))
97+
}
98+
}
99+
100+
define_link_wrapper!(
101+
/// The link used by [LsmCgroup] programs.
102+
LsmLink,
103+
/// The type returned by [LsmCgroup::attach]. Can be passed to [LsmCgroup::detach].
104+
LsmLinkId,
105+
FdLink,
106+
FdLinkId,
107+
LsmCgroup,
108+
);

0 commit comments

Comments
 (0)