Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 92a61ab

Browse files
Altug Bozkurtaltugbozkurt07
Altug Bozkurt
authored andcommittedJan 14, 2025·
lsm :: cgroup attachment type support
1 parent de7e61f commit 92a61ab

File tree

13 files changed

+399
-198
lines changed

13 files changed

+399
-198
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

+22-75
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
use std::borrow::Cow;
2-
32
use proc_macro2::TokenStream;
43
use quote::quote;
54
use syn::{ItemFn, Result};
6-
75
use crate::args::{err_on_unknown_args, pop_bool_arg, pop_string_arg};
86

97
pub(crate) struct Lsm {
108
item: ItemFn,
119
hook: Option<String>,
12-
cgroup: bool,
1310
sleepable: bool,
1411
}
1512

@@ -19,64 +16,43 @@ impl Lsm {
1916
let mut args = syn::parse2(attrs)?;
2017
let hook = pop_string_arg(&mut args, "hook");
2118
let sleepable = pop_bool_arg(&mut args, "sleepable");
22-
let cgroup = pop_bool_arg(&mut args, "cgroup");
2319
err_on_unknown_args(&args)?;
2420

2521
Ok(Self {
2622
item,
2723
hook,
28-
cgroup,
2924
sleepable,
3025
})
3126
}
3227

3328
pub(crate) fn expand(&self) -> TokenStream {
34-
if self.cgroup{
35-
let section_name = if let Some(name) = &self.hook{
36-
format!("lsm_cgroup/{}", name)
37-
} else {
38-
("lsm_cgroup").to_owned()
39-
};
40-
41-
let fn_name = &self.item.sig.ident;
42-
let item = &self.item;
43-
44-
quote! {
45-
#[no_mangle]
46-
#[link_section = #section_name]
47-
fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 {
48-
return #fn_name(::aya_ebpf::programs::LsmContext::new(ctx));
49-
50-
#item
51-
}
52-
}
29+
let Self { item, hook, sleepable } = self;
30+
let ItemFn {
31+
attrs: _,
32+
vis,
33+
sig,
34+
block: _,
35+
} = item;
36+
let section_prefix = if *sleepable { "lsm.s" } else { "lsm" };
37+
let section_name: Cow<'_, _>= if let Some(name) = hook {
38+
format!("{}/{}", section_prefix, name).into()
5339
} else {
54-
let section_prefix = if self.sleepable { "lsm.s" } else { "lsm" };
55-
let section_name: Cow<'_, _> = if let Some(hook) = &self.hook {
56-
format!("{}/{}", section_prefix, hook).into()
57-
} else {
58-
section_prefix.into()
59-
};
60-
61-
let fn_vis = &self.item.vis;
62-
let fn_name = self.item.sig.ident.clone();
63-
let item = &self.item;
40+
section_prefix.into()
41+
};
42+
let fn_name = &sig.ident;
43+
// LSM probes need to return an integer corresponding to the correct
44+
// policy decision. Therefore we do not simply default to a return value
45+
// of 0 as in other program types.
46+
quote! {
47+
#[no_mangle]
48+
#[link_section = #section_name]
49+
#vis fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 {
50+
return #fn_name(::aya_ebpf::programs::LsmContext::new(ctx));
6451

65-
quote! {
66-
#[no_mangle]
67-
#[link_section = #section_name]
68-
#fn_vis fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 {
69-
return #fn_name(::aya_ebpf::programs::LsmContext::new(ctx));
70-
71-
#item
72-
}
52+
#item
7353
}
7454
}
7555
}
76-
77-
// LSM probes need to return an integer corresponding to the correct
78-
// policy decision. Therefore we do not simply default to a return value
79-
// of 0 as in other program types.
8056
}
8157

8258
#[cfg(test)]
@@ -141,33 +117,4 @@ mod tests {
141117
};
142118
assert_eq!(expected.to_string(), expanded.to_string());
143119
}
144-
145-
#[test]
146-
fn test_lsm_cgroup() {
147-
let prog = Lsm::parse(
148-
parse_quote! {
149-
hook = "bprm_committed_creds",
150-
cgroup
151-
},
152-
parse_quote! {
153-
fn bprm_committed_creds(ctx: &mut ::aya_ebpf::programs::LsmContext) -> i32 {
154-
0
155-
}
156-
},
157-
)
158-
.unwrap();
159-
let expanded = prog.expand();
160-
let expected = quote! {
161-
#[no_mangle]
162-
#[link_section = "lsm_cgroup/bprm_committed_creds"]
163-
fn bprm_committed_creds(ctx: *mut ::core::ffi::c_void) -> i32 {
164-
return bprm_committed_creds(::aya_ebpf::programs::LsmContext::new(ctx));
165-
166-
fn bprm_committed_creds(ctx: &mut ::aya_ebpf::programs::LsmContext) -> i32 {
167-
0
168-
}
169-
}
170-
};
171-
assert_eq!(expected.to_string(), expanded.to_string());
172-
}
173120
}

‎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

+18-7
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ use crate::{
2727
},
2828
maps::{bpf_map_def, BtfMap, BtfMapDef, LegacyMap, Map, PinningType, MINIMUM_MAP_SIZE},
2929
programs::{
30-
CgroupSockAddrAttachType, CgroupSockAttachType, CgroupSockoptAttachType, LsmAttachType, XdpAttachType
30+
CgroupSockAddrAttachType, CgroupSockAttachType, CgroupSockoptAttachType, LsmAttachType,
31+
XdpAttachType,
3132
},
3233
relocation::*,
3334
util::HashMap,
@@ -276,6 +277,9 @@ pub enum ProgramSection {
276277
sleepable: bool,
277278
attach_type: LsmAttachType,
278279
},
280+
LsmCgroup {
281+
attach_type: LsmAttachType,
282+
},
279283
BtfTracePoint,
280284
FEntry {
281285
sleepable: bool,
@@ -435,9 +439,17 @@ impl FromStr for ProgramSection {
435439
"lirc_mode2" => LircMode2,
436440
"perf_event" => PerfEvent,
437441
"raw_tp" | "raw_tracepoint" => RawTracePoint,
438-
"lsm" => Lsm { sleepable: false, attach_type: LsmAttachType::Mac},
439-
"lsm.s" => Lsm { sleepable: true, attach_type: LsmAttachType::Mac },
440-
"lsm_cgroup" => Lsm { sleepable: false, attach_type: LsmAttachType::Cgroup },
442+
"lsm" => Lsm {
443+
sleepable: false,
444+
attach_type: LsmAttachType::Mac,
445+
},
446+
"lsm.s" => Lsm {
447+
sleepable: true,
448+
attach_type: LsmAttachType::Mac,
449+
},
450+
"lsm_cgroup" => LsmCgroup {
451+
attach_type: LsmAttachType::Cgroup,
452+
},
441453
"fentry" => FEntry { sleepable: false },
442454
"fentry.s" => FEntry { sleepable: true },
443455
"fexit" => FExit { sleepable: false },
@@ -2242,9 +2254,8 @@ mod tests {
22422254
assert_matches!(
22432255
obj.programs.get("foo"),
22442256
Some(Program {
2245-
section: ProgramSection::Lsm {
2246-
sleepable: false,
2247-
attach_type: LsmAttachType::Cgroup
2257+
section: ProgramSection::LsmCgroup {
2258+
attach_type: LsmAttachType::Cgroup,
22482259
},
22492260
..
22502261
})

‎aya-obj/src/programs/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
pub mod cgroup_sock;
44
pub mod cgroup_sock_addr;
55
pub mod cgroup_sockopt;
6+
pub mod lsm;
67
mod types;
78
pub mod xdp;
8-
pub mod lsm;
99

1010
pub use cgroup_sock::CgroupSockAttachType;
1111
pub use cgroup_sock_addr::CgroupSockAddrAttachType;
1212
pub use cgroup_sockopt::CgroupSockoptAttachType;
13-
pub use xdp::XdpAttachType;
1413
pub use lsm::LsmAttachType;
14+
pub use xdp::XdpAttachType;

‎aya/src/bpf.rs

+20-6
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ use crate::{
3030
programs::{
3131
BtfTracePoint, CgroupDevice, CgroupSkb, CgroupSkbAttachType, CgroupSock, CgroupSockAddr,
3232
CgroupSockopt, CgroupSysctl, Extension, FEntry, FExit, Iter, KProbe, LircMode2, Lsm,
33-
PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier,
34-
SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp,
33+
LsmCgroup, PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint,
34+
SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint,
35+
UProbe, Xdp,
3536
},
3637
sys::{
3738
bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported,
@@ -411,7 +412,11 @@ impl<'a> EbpfLoader<'a> {
411412
ProgramSection::Extension
412413
| ProgramSection::FEntry { sleepable: _ }
413414
| ProgramSection::FExit { sleepable: _ }
414-
| ProgramSection::Lsm { sleepable: _, attach_type: _ }
415+
| ProgramSection::Lsm {
416+
sleepable: _,
417+
attach_type: _,
418+
}
419+
| ProgramSection::LsmCgroup { attach_type: _ }
415420
| ProgramSection::BtfTracePoint
416421
| ProgramSection::Iter { sleepable: _ } => {
417422
return Err(EbpfError::BtfError(err))
@@ -649,16 +654,25 @@ impl<'a> EbpfLoader<'a> {
649654
ProgramSection::RawTracePoint => Program::RawTracePoint(RawTracePoint {
650655
data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
651656
}),
652-
ProgramSection::Lsm { sleepable , attach_type} => {
657+
ProgramSection::Lsm {
658+
sleepable,
659+
attach_type,
660+
} => {
653661
let mut data =
654662
ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level);
655663
if *sleepable {
656664
data.flags = BPF_F_SLEEPABLE;
657665
}
658-
Program::Lsm(Lsm {
666+
Program::Lsm(Lsm {
659667
data,
660668
attach_type: *attach_type,
661-
})
669+
})
670+
}
671+
ProgramSection::LsmCgroup { attach_type } => {
672+
Program::LsmCgroup(LsmCgroup {
673+
data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),
674+
attach_type: *attach_type,
675+
})
662676
}
663677
ProgramSection::BtfTracePoint => Program::BtfTracePoint(BtfTracePoint {
664678
data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level),

‎aya/src/programs/lsm.rs

+8-69
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
//! LSM probes.
2-
3-
use std::os::fd::AsFd;
4-
5-
use aya_obj::programs::LsmAttachType;
6-
72
use crate::{
83
generated::bpf_prog_type::BPF_PROG_TYPE_LSM,
9-
obj::btf::{Btf, BtfKind},
4+
obj::{
5+
btf::{Btf, BtfKind},
6+
programs::LsmAttachType,
7+
},
108
programs::{
119
define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId,
1210
ProgramData, ProgramError,
13-
}, sys::{bpf_link_create, LinkTarget, SyscallError},
11+
},
1412
};
1513

1614
/// A program that attaches to Linux LSM hooks. Used to implement security policy and
@@ -28,7 +26,7 @@ use crate::{
2826
/// The minimum kernel version required to use this feature is 5.7.
2927
///
3028
/// # Examples
31-
/// LSM with MAC attachment type
29+
/// ## LSM with MAC attachment type
3230
/// ```no_run
3331
/// # #[derive(thiserror::Error, Debug)]
3432
/// # enum LsmError {
@@ -48,29 +46,6 @@ use crate::{
4846
/// program.attach()?;
4947
/// # Ok::<(), LsmError>(())
5048
/// ```
51-
///
52-
/// LSM with cgroup attachment type
53-
/// ```no_run
54-
/// # #[derive(thiserror::Error, Debug)]
55-
/// # enum LsmError {
56-
/// # #[error(transparent)]
57-
/// # BtfError(#[from] aya::BtfError),
58-
/// # #[error(transparent)]
59-
/// # Program(#[from] aya::programs::ProgramError),
60-
/// # #[error(transparent)]
61-
/// # Ebpf(#[from] aya::EbpfError),
62-
/// # }
63-
/// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?;
64-
/// use aya::{Ebpf, programs::Lsm, BtfError, Btf};
65-
///
66-
/// let btf = Btf::from_sys_fs()?;
67-
/// let file = File::open("/sys/fs/cgroup/unified")?;
68-
/// let program: &mut Lsm = bpf.program_mut("lsm_prog").unwrap().try_into()?;
69-
/// program.load("security_bprm_exec", &btf)?;
70-
/// program.attach(Some(file))?;
71-
/// # Ok::<(), LsmError>(())
72-
/// ```
73-
///
7449
///
7550
/// [1]: https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h
7651
#[derive(Debug)]
@@ -98,47 +73,11 @@ impl Lsm {
9873
/// Attaches the program.
9974
///
10075
/// The returned value can be used to detach, see [Lsm::detach].
101-
pub fn attach<T: AsFd>(&mut self, cgroup: Option<T>) -> Result<LsmLinkId, ProgramError> {
102-
match self.attach_type{
103-
LsmAttachType::Cgroup => {
104-
if let Some(cgroup) = cgroup{
105-
let prog_fd = self.fd()?;
106-
let prog_fd = prog_fd.as_fd();
107-
let cgroup_fd = cgroup.as_fd();
108-
let attach_type = self.data.expected_attach_type.unwrap();
109-
let btf_id = self.data.attach_btf_id;
110-
111-
let link_fd = bpf_link_create(
112-
prog_fd,
113-
LinkTarget::Fd(cgroup_fd),
114-
attach_type,
115-
btf_id,
116-
0,
117-
None,
118-
)
119-
.map_err(|(_, io_error)| SyscallError {
120-
call: "bpf_link_create",
121-
io_error,
122-
})?;
123-
124-
self.data
125-
.links
126-
.insert(LsmLink::new(FdLink::new(
127-
link_fd,
128-
)))
129-
}else {
130-
return Err(ProgramError::UnexpectedProgramType);
131-
}
132-
},
133-
LsmAttachType::Mac => {
134-
attach_raw_tracepoint(&mut self.data, None)
135-
}
136-
}
137-
76+
pub fn attach(&mut self) -> Result<LsmLinkId, ProgramError> {
77+
attach_raw_tracepoint(&mut self.data, None)
13878
}
13979
}
14080

141-
14281
define_link_wrapper!(
14382
/// The link used by [Lsm] programs.
14483
LsmLink,

‎aya/src/programs/lsm_cgroup.rs

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//! LSM probes.
2+
3+
use std::os::fd::AsFd;
4+
5+
use crate::{
6+
generated::bpf_prog_type::BPF_PROG_TYPE_LSM,
7+
obj::{
8+
btf::{Btf, BtfKind},
9+
programs::LsmAttachType,
10+
},
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. 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+
/// ## LSM with cgroup attachment type
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+
///
44+
/// let btf = Btf::from_sys_fs()?;
45+
/// let file = File::open("/sys/fs/cgroup/unified")?;
46+
/// let program: &mut LsmCgroup = bpf.program_mut("lsm_prog").unwrap().try_into()?;
47+
/// program.load("security_bprm_exec", &btf)?;
48+
/// program.attach(file)?;
49+
/// # Ok::<(), LsmError>(())
50+
/// ```
51+
///
52+
/// [1]: https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h
53+
#[derive(Debug)]
54+
#[doc(alias = "BPF_PROG_TYPE_LSM")]
55+
pub struct LsmCgroup {
56+
pub(crate) data: ProgramData<LsmLink>,
57+
pub(crate) attach_type: LsmAttachType,
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(self.attach_type.into());
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 [Lsm::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+
85+
let link_fd = bpf_link_create(
86+
prog_fd,
87+
LinkTarget::Fd(cgroup_fd),
88+
attach_type,
89+
0,
90+
Some(BpfLinkCreateArgs::TargetBtfId(btf_id)),
91+
)
92+
.map_err(|(_, io_error)| SyscallError {
93+
call: "bpf_link_create",
94+
io_error,
95+
})?;
96+
97+
self.data.links.insert(LsmLink::new(FdLink::new(link_fd)))
98+
}
99+
}
100+
101+
define_link_wrapper!(
102+
/// The link used by [Lsm] programs.
103+
LsmLink,
104+
/// The type returned by [Lsm::attach]. Can be passed to [Lsm::detach].
105+
LsmLinkId,
106+
FdLink,
107+
FdLinkId,
108+
LsmCgroup,
109+
);

‎aya/src/programs/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub mod kprobe;
5656
pub mod links;
5757
pub mod lirc_mode2;
5858
pub mod lsm;
59+
pub mod lsm_cgroup;
5960
pub mod perf_attach;
6061
pub mod perf_event;
6162
pub mod raw_trace_point;
@@ -100,6 +101,7 @@ pub use crate::programs::{
100101
links::{CgroupAttachMode, Link, LinkOrder},
101102
lirc_mode2::LircMode2,
102103
lsm::Lsm,
104+
lsm_cgroup::LsmCgroup,
103105
perf_event::{PerfEvent, PerfEventScope, PerfTypeId, SamplePolicy},
104106
probe::ProbeKind,
105107
raw_trace_point::RawTracePoint,
@@ -295,6 +297,8 @@ pub enum Program {
295297
RawTracePoint(RawTracePoint),
296298
/// A [`Lsm`] program
297299
Lsm(Lsm),
300+
/// A [`LsmCgroup`] program
301+
LsmCgroup(LsmCgroup),
298302
/// A [`BtfTracePoint`] program
299303
BtfTracePoint(BtfTracePoint),
300304
/// A [`FEntry`] program
@@ -332,6 +336,7 @@ impl Program {
332336
Self::PerfEvent(_) => ProgramType::PerfEvent,
333337
Self::RawTracePoint(_) => ProgramType::RawTracePoint,
334338
Self::Lsm(_) => ProgramType::Lsm,
339+
Self::LsmCgroup(_) => ProgramType::Lsm,
335340
// The following program types are a subset of `TRACING` programs:
336341
//
337342
// - `BPF_TRACE_RAW_TP` (`BtfTracePoint`)
@@ -371,6 +376,7 @@ impl Program {
371376
Self::PerfEvent(p) => p.pin(path),
372377
Self::RawTracePoint(p) => p.pin(path),
373378
Self::Lsm(p) => p.pin(path),
379+
Self::LsmCgroup(p) => p.pin(path),
374380
Self::BtfTracePoint(p) => p.pin(path),
375381
Self::FEntry(p) => p.pin(path),
376382
Self::FExit(p) => p.pin(path),
@@ -402,6 +408,7 @@ impl Program {
402408
Self::PerfEvent(mut p) => p.unload(),
403409
Self::RawTracePoint(mut p) => p.unload(),
404410
Self::Lsm(mut p) => p.unload(),
411+
Self::LsmCgroup(mut p) => p.unload(),
405412
Self::BtfTracePoint(mut p) => p.unload(),
406413
Self::FEntry(mut p) => p.unload(),
407414
Self::FExit(mut p) => p.unload(),
@@ -435,6 +442,7 @@ impl Program {
435442
Self::PerfEvent(p) => p.fd(),
436443
Self::RawTracePoint(p) => p.fd(),
437444
Self::Lsm(p) => p.fd(),
445+
Self::LsmCgroup(p) => p.fd(),
438446
Self::BtfTracePoint(p) => p.fd(),
439447
Self::FEntry(p) => p.fd(),
440448
Self::FExit(p) => p.fd(),
@@ -469,6 +477,7 @@ impl Program {
469477
Self::PerfEvent(p) => p.info(),
470478
Self::RawTracePoint(p) => p.info(),
471479
Self::Lsm(p) => p.info(),
480+
Self::LsmCgroup(p) => p.info(),
472481
Self::BtfTracePoint(p) => p.info(),
473482
Self::FEntry(p) => p.info(),
474483
Self::FExit(p) => p.info(),
@@ -780,6 +789,7 @@ impl_program_unload!(
780789
LircMode2,
781790
PerfEvent,
782791
Lsm,
792+
LsmCgroup,
783793
RawTracePoint,
784794
BtfTracePoint,
785795
FEntry,
@@ -821,6 +831,7 @@ impl_fd!(
821831
LircMode2,
822832
PerfEvent,
823833
Lsm,
834+
LsmCgroup,
824835
RawTracePoint,
825836
BtfTracePoint,
826837
FEntry,
@@ -927,6 +938,7 @@ impl_program_pin!(
927938
LircMode2,
928939
PerfEvent,
929940
Lsm,
941+
LsmCgroup,
930942
RawTracePoint,
931943
BtfTracePoint,
932944
FEntry,
@@ -1022,6 +1034,7 @@ impl_try_from_program!(
10221034
LircMode2,
10231035
PerfEvent,
10241036
Lsm,
1037+
LsmCgroup,
10251038
RawTracePoint,
10261039
BtfTracePoint,
10271040
FEntry,
@@ -1049,6 +1062,7 @@ impl_info!(
10491062
LircMode2,
10501063
PerfEvent,
10511064
Lsm,
1065+
LsmCgroup,
10521066
RawTracePoint,
10531067
BtfTracePoint,
10541068
FEntry,

‎test/integration-ebpf/src/test.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
#![no_main]
33

44
use aya_ebpf::{
5-
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}
5+
bindings::xdp_action,
6+
macros::{kprobe, kretprobe, lsm, lsm_cgroup, tracepoint, uprobe, uretprobe, xdp},
7+
programs::{LsmContext, ProbeContext, RetProbeContext, TracePointContext, XdpContext},
68
};
79

810
#[xdp]
@@ -42,11 +44,16 @@ pub fn test_uretprobe(_ctx: RetProbeContext) -> u32 {
4244
0
4345
}
4446

45-
#[lsm(hook="socket_bind", cgroup)]
47+
#[lsm_cgroup(hook = "socket_bind")]
4648
pub fn test_lsmcgroup(_ctx: LsmContext) -> i32 {
4749
0
4850
}
4951

52+
#[lsm(hook = "socket_bind")]
53+
pub fn test_lsm(_ctx: LsmContext) -> i32 {
54+
-1
55+
}
56+
5057
#[cfg(not(test))]
5158
#[panic_handler]
5259
fn panic(_info: &core::panic::PanicInfo) -> ! {

‎test/integration-test/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ test-case = { workspace = true }
2828
test-log = { workspace = true, features = ["log"] }
2929
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
3030
xdpilone = { workspace = true }
31-
nix = { workspace = true, features = ["process"]}
31+
nix = { workspace = true, features = ["process"] }
3232

3333
[build-dependencies]
3434
anyhow = { workspace = true }

‎test/integration-test/src/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod info;
55
mod iter;
66
mod load;
77
mod log;
8+
mod lsm;
89
mod raw_tracepoint;
910
mod rbpf;
1011
mod relocations;
@@ -14,4 +15,3 @@ mod strncmp;
1415
mod tcx;
1516
mod uprobe_cookie;
1617
mod xdp;
17-
mod lsm;
+63-35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
use std::{fs::File, io::{ErrorKind, Write}, path::Path};
2-
use aya::{programs::Lsm, util::KernelVersion, Btf, Ebpf};
3-
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener};
1+
use std::{
2+
fs::File,
3+
io::{ErrorKind, Write},
4+
net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener},
5+
path::Path,
6+
};
7+
8+
use aya::{
9+
programs::{lsm_cgroup::LsmCgroup, Lsm},
10+
util::KernelVersion,
11+
Btf, Ebpf,
12+
};
413
use nix::{
514
sys::wait::waitpid,
615
unistd::{fork, getpid, ForkResult},
@@ -15,40 +24,59 @@ fn lsm_cgroup() {
1524
}
1625

1726
let mut bpf: Ebpf = Ebpf::load(crate::TEST).unwrap();
18-
let prog: &mut Lsm = bpf.program_mut("test_lsmcgroup").unwrap().try_into().unwrap();
27+
let prog: &mut LsmCgroup = bpf
28+
.program_mut("test_lsmcgroup")
29+
.unwrap()
30+
.try_into()
31+
.unwrap();
1932
let btf = Btf::from_sys_fs().expect("could not get btf from sys");
20-
if let Err(err) = prog.load("socket_bind", &btf) {
21-
panic!("{err}");
22-
}
33+
prog.load("socket_bind", &btf).unwrap();
34+
35+
let cgroup_path = Path::new("/sys/fs/cgroup/lsm_cgroup_test");
36+
37+
std::fs::create_dir_all(cgroup_path).expect("could not create the cgroup dir");
2338

24-
let cgroup_path = Path::new(".").join("/sys/fs/cgroup/").join("lsm_cgroup_test");
25-
26-
let _ = std::fs::create_dir_all( cgroup_path.clone()).expect("could not create the cgroup dir");
27-
28-
let p = prog.attach(
29-
Some(File::open(cgroup_path.clone()).unwrap()),
30-
)
31-
.unwrap();
32-
33-
unsafe {
34-
match fork().expect("Failed to fork process") {
35-
ForkResult::Parent { child } => {
36-
waitpid(Some(child), None).unwrap();
37-
38-
let pid = getpid();
39-
40-
let mut f = File::create(cgroup_path.join("cgroup.procs")).expect("could not open cgroup procs");
41-
f.write_fmt(format_args!("{}",pid.as_raw() as u64)).expect("could not write into procs file");
42-
43-
assert_matches::assert_matches!(TcpListener::bind("127.0.0.1:12345"), Err(e) => assert_eq!(
44-
e.kind(), ErrorKind::PermissionDenied)
45-
);
46-
}
47-
ForkResult::Child => {
48-
assert_matches::assert_matches!(TcpListener::bind("127.0.0.1:12345"), Ok(listener) => assert_eq!(
49-
listener.local_addr().unwrap(), SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 12345)))
50-
);
51-
}
39+
let _ = prog.attach(File::open(cgroup_path).unwrap()).unwrap();
40+
41+
match unsafe { fork().expect("Failed to fork process") } {
42+
ForkResult::Parent { child } => {
43+
waitpid(Some(child), None).unwrap();
44+
45+
let pid = getpid();
46+
47+
let mut f = File::create(cgroup_path.join("cgroup.procs"))
48+
.expect("could not open cgroup procs");
49+
f.write_fmt(format_args!("{}", pid.as_raw() as u64))
50+
.expect("could not write into procs file");
51+
52+
assert_matches::assert_matches!(TcpListener::bind("127.0.0.1:12345"), Err(e) => assert_eq!(
53+
e.kind(), ErrorKind::PermissionDenied)
54+
);
55+
}
56+
ForkResult::Child => {
57+
assert_matches::assert_matches!(TcpListener::bind("127.0.0.1:12345"), Ok(listener) => assert_eq!(
58+
listener.local_addr().unwrap(), SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 12345)))
59+
);
5260
}
5361
}
5462
}
63+
64+
#[test]
65+
fn lsm() {
66+
let kernel_version = KernelVersion::current().unwrap();
67+
if kernel_version < KernelVersion::new(5, 7, 0) {
68+
eprintln!("skipping lsm test on kernel {kernel_version:?}");
69+
return;
70+
}
71+
72+
let mut bpf: Ebpf = Ebpf::load(crate::TEST).unwrap();
73+
let prog: &mut Lsm = bpf.program_mut("test_lsm").unwrap().try_into().unwrap();
74+
let btf = Btf::from_sys_fs().expect("could not get btf from sys");
75+
prog.load("socket_bind", &btf).unwrap();
76+
77+
prog.attach().unwrap();
78+
79+
assert_matches::assert_matches!(TcpListener::bind("127.0.0.1:12345"), Err(e) => assert_eq!(
80+
e.kind(), ErrorKind::PermissionDenied)
81+
);
82+
}

0 commit comments

Comments
 (0)
Please sign in to comment.