-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for LSM programs attached to cgroups #439
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -820,6 +820,37 @@ impl SkLookup { | |||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
pub struct CgroupLsm { | ||||||||||||||||
item: ItemFn, | ||||||||||||||||
name: String, | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
impl CgroupLsm { | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
pub fn from_syn(mut args: Args, item: ItemFn) -> Result<CgroupLsm> { | ||||||||||||||||
let name = name_arg(&mut args)?.unwrap_or_else(|| item.sig.ident.to_string()); | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
|
||||||||||||||||
Ok(CgroupLsm { item, name }) | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
pub fn expand(&self) -> Result<TokenStream> { | ||||||||||||||||
let section_name = format!("lsm_cgroup/{}", self.name); | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
let fn_name = &self.item.sig.ident; | ||||||||||||||||
let item = &self.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. | ||||||||||||||||
Ok(quote! { | ||||||||||||||||
#[no_mangle] | ||||||||||||||||
#[link_section = #section_name] | ||||||||||||||||
fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 { | ||||||||||||||||
return #fn_name(::aya_bpf::programs::CgroupLsmContext::new(ctx)); | ||||||||||||||||
|
||||||||||||||||
#item | ||||||||||||||||
} | ||||||||||||||||
}) | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
#[cfg(test)] | ||||||||||||||||
mod tests { | ||||||||||||||||
use syn::parse_quote; | ||||||||||||||||
|
@@ -893,4 +924,38 @@ mod tests { | |||||||||||||||
.to_string() | ||||||||||||||||
.contains("[link_section = \"cgroup_skb/egress\"]")); | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
#[test] | ||||||||||||||||
fn cgroup_lsm_with_name() { | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
let prog = CgroupLsm::from_syn( | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
parse_quote!(name = "foo"), | ||||||||||||||||
parse_quote!( | ||||||||||||||||
fn bar(ctx: LsmContext) -> i32 { | ||||||||||||||||
0 | ||||||||||||||||
} | ||||||||||||||||
), | ||||||||||||||||
) | ||||||||||||||||
.unwrap(); | ||||||||||||||||
let stream = prog.expand().unwrap(); | ||||||||||||||||
assert!(stream | ||||||||||||||||
.to_string() | ||||||||||||||||
.contains("[link_section = \"lsm_cgroup/foo\"]")); | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
#[test] | ||||||||||||||||
fn cgroup_lsm_no_name() { | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
let prog = CgroupLsm::from_syn( | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
parse_quote!(), | ||||||||||||||||
parse_quote!( | ||||||||||||||||
fn foo(ct: LsmContext) -> i32 { | ||||||||||||||||
0 | ||||||||||||||||
} | ||||||||||||||||
), | ||||||||||||||||
) | ||||||||||||||||
.unwrap(); | ||||||||||||||||
let stream = prog.expand().unwrap(); | ||||||||||||||||
assert!(stream | ||||||||||||||||
.to_string() | ||||||||||||||||
.contains("[link_section = \"lsm_cgroup/foo\"]")); | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
} | ||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,9 +1,10 @@ | ||||||
mod expand; | ||||||
|
||||||
use expand::{ | ||||||
Args, BtfTracePoint, CgroupSkb, CgroupSock, CgroupSockAddr, CgroupSockopt, CgroupSysctl, | ||||||
FEntry, FExit, Lsm, Map, PerfEvent, Probe, ProbeKind, RawTracePoint, SchedClassifier, SkLookup, | ||||||
SkMsg, SkSkb, SkSkbKind, SockAddrArgs, SockOps, SocketFilter, SockoptArgs, TracePoint, Xdp, | ||||||
Args, BtfTracePoint, CgroupLsm, CgroupSkb, CgroupSock, CgroupSockAddr, CgroupSockopt, | ||||||
CgroupSysctl, FEntry, FExit, Lsm, Map, PerfEvent, Probe, ProbeKind, RawTracePoint, | ||||||
SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockAddrArgs, SockOps, SocketFilter, | ||||||
SockoptArgs, TracePoint, Xdp, | ||||||
}; | ||||||
use proc_macro::TokenStream; | ||||||
use syn::{parse_macro_input, ItemFn, ItemStatic}; | ||||||
|
@@ -507,3 +508,47 @@ pub fn sk_lookup(attrs: TokenStream, item: TokenStream) -> TokenStream { | |||||
.unwrap_or_else(|err| err.to_compile_error()) | ||||||
.into() | ||||||
} | ||||||
|
||||||
/// Marks a function as an LSM program that can be attached to Linux LSM hooks | ||||||
/// within a [cgroup]. Used to implement security policy and audit logging. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is |
||||||
/// | ||||||
/// 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 `lsm` option. If your kernel | ||||||
/// is not built with `lsm=[...],bpf` option, BPF LSM needs to be enabled | ||||||
/// through the kernel's boot parameter (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_bpf::{macros::lsm, programs::LsmContext}; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
/// | ||||||
/// #[lsm(name = "file_open")] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
/// 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<i32, i32> { | ||||||
/// Ok(0) | ||||||
/// } | ||||||
/// ``` | ||||||
#[proc_macro_attribute] | ||||||
pub fn cgroup_lsm(attrs: TokenStream, item: TokenStream) -> TokenStream { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
let args = parse_macro_input!(attrs as Args); | ||||||
let item = parse_macro_input!(item as ItemFn); | ||||||
|
||||||
CgroupLsm::from_syn(args, item) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
.and_then(|u| u.expand()) | ||||||
.unwrap_or_else(|err| err.to_compile_error()) | ||||||
.into() | ||||||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,134 @@ | ||||||||
//! LSM probes attached to cgroups. | ||||||||
use std::os::unix::prelude::{AsRawFd, RawFd}; | ||||||||
|
||||||||
use crate::{ | ||||||||
generated::{bpf_attach_type::BPF_LSM_CGROUP, bpf_prog_type::BPF_PROG_TYPE_LSM}, | ||||||||
obj::btf::{Btf, BtfKind}, | ||||||||
programs::{define_link_wrapper, load_program, FdLink, Link, ProgramData, ProgramError}, | ||||||||
sys::bpf_link_create, | ||||||||
}; | ||||||||
|
||||||||
/// A program that attaches to Linux LSM hooks within a [cgroup]. 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 `lsm` option. If your kernel | ||||||||
/// is not built with `lsm=[...],bpf` option, BPF LSM needs to be enabled | ||||||||
/// through the kernel's boot parameter (like `lsm=lockdown,yama,bpf`). | ||||||||
/// | ||||||||
/// # Minimum kernel version | ||||||||
/// | ||||||||
/// The minimum kernel version required to use this feature is 6.0. | ||||||||
/// | ||||||||
/// # Examples | ||||||||
/// | ||||||||
/// ```no_run | ||||||||
/// # #[derive(thiserror::Error, Debug)] | ||||||||
/// # enum LsmError { | ||||||||
/// # #[error(transparent)] | ||||||||
/// # BtfError(#[from] aya::BtfError), | ||||||||
/// # #[error(transparent)] | ||||||||
/// # Program(#[from] aya::programs::ProgramError), | ||||||||
/// # #[error(transparent)] | ||||||||
/// # Bpf(#[from] aya::BpfError), | ||||||||
/// # } | ||||||||
/// # let mut bpf = Bpf::load_file("ebpf_programs.o")?; | ||||||||
/// use aya::{Bpf, programs::LsmCgroup, BtfError, Btf}; | ||||||||
/// use std::{fs::File, os::unix::prelude::AsRawFd, path::Path}; | ||||||||
/// | ||||||||
/// let btf = Btf::from_sys_fs()?; | ||||||||
/// let program: &mut LsmCgroup = bpf.program_mut("lsm_prog").unwrap().try_into()?; | ||||||||
/// program.load("security_bprm_exec", &btf)?; | ||||||||
/// let cgroup = File::open(Path::new("/sys/fs/cgroup/unified/aya"))?; | ||||||||
/// program.attach(cgroup.as_raw_fd())?; | ||||||||
/// # Ok::<(), LsmError>(()) | ||||||||
/// ``` | ||||||||
#[derive(Debug)] | ||||||||
pub struct CgroupLsm { | ||||||||
pub(crate) data: ProgramData<CgroupLsmLink>, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
} | ||||||||
|
||||||||
impl CgroupLsm { | ||||||||
/// Loads the program inside the kernel. | ||||||||
pub fn load(&mut self, lsm_hook_name: &str, btf: &Btf) -> Result<(), ProgramError> { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to know the hook name at load time? If so, what's the point of |
||||||||
self.data.expected_attach_type = Some(BPF_LSM_CGROUP); | ||||||||
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 [CgroupLsm::detach]. | ||||||||
pub fn attach<T: AsRawFd>(&mut self, cgroup: T) -> Result<CgroupLsmLinkId, ProgramError> { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be more ergonomic to use the |
||||||||
let prog_fd = self.data.fd_or_err()?; | ||||||||
let cgroup_fd = cgroup.as_raw_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, cgroup_fd, attach_type, btf_id, 0).map_err( | ||||||||
|(_, io_error)| ProgramError::SyscallError { | ||||||||
call: "bpf_link_create".to_owned(), | ||||||||
io_error, | ||||||||
}, | ||||||||
)? as RawFd; | ||||||||
self.data | ||||||||
.links | ||||||||
.insert(CgroupLsmLink(CgroupLsmLinkInner::Fd(FdLink::new(link_fd)))) | ||||||||
} | ||||||||
|
||||||||
/// Detaches the program. | ||||||||
/// | ||||||||
/// See [CgroupLsm::attach]. | ||||||||
pub fn detach(&mut self, link_id: CgroupLsmLinkId) -> Result<(), ProgramError> { | ||||||||
self.data.links.remove(link_id) | ||||||||
} | ||||||||
|
||||||||
/// Takes ownership of the link referenced by the provided link_id. | ||||||||
/// | ||||||||
/// The link will be detached on `Drop` and the caller is now responsible | ||||||||
/// for managing its lifetime. | ||||||||
pub fn take_link(&mut self, link_id: CgroupLsmLinkId) -> Result<CgroupLsmLink, ProgramError> { | ||||||||
self.data.take_link(link_id) | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
#[derive(Debug, Hash, Eq, PartialEq)] | ||||||||
enum CgroupLsmLinkIdInner { | ||||||||
Fd(<FdLink as Link>::Id), | ||||||||
} | ||||||||
|
||||||||
#[derive(Debug)] | ||||||||
enum CgroupLsmLinkInner { | ||||||||
Fd(FdLink), | ||||||||
} | ||||||||
|
||||||||
impl Link for CgroupLsmLinkInner { | ||||||||
type Id = CgroupLsmLinkIdInner; | ||||||||
|
||||||||
fn id(&self) -> Self::Id { | ||||||||
match self { | ||||||||
CgroupLsmLinkInner::Fd(fd) => CgroupLsmLinkIdInner::Fd(fd.id()), | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
fn detach(self) -> Result<(), ProgramError> { | ||||||||
match self { | ||||||||
CgroupLsmLinkInner::Fd(fd) => fd.detach(), | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
define_link_wrapper!( | ||||||||
/// The link used by [CgroupLsm] programs. | ||||||||
CgroupLsmLink, | ||||||||
/// The type returned by [CgroupLsm::attach]. Can be passed to [CgroupLsm::detach]. | ||||||||
CgroupLsmLinkId, | ||||||||
CgroupLsmLinkInner, | ||||||||
CgroupLsmLinkIdInner | ||||||||
); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -35,6 +35,7 @@ | |||||
//! [`Bpf::program`]: crate::Bpf::program | ||||||
//! [`Bpf::program_mut`]: crate::Bpf::program_mut | ||||||
//! [`maps`]: crate::maps | ||||||
pub mod cgroup_lsm; | ||||||
pub mod cgroup_skb; | ||||||
pub mod cgroup_sock; | ||||||
pub mod cgroup_sock_addr; | ||||||
|
@@ -72,6 +73,7 @@ use std::{ | |||||
}; | ||||||
use thiserror::Error; | ||||||
|
||||||
pub use cgroup_lsm::CgroupLsm; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType}; | ||||||
pub use cgroup_sock::{CgroupSock, CgroupSockAttachType}; | ||||||
pub use cgroup_sock_addr::{CgroupSockAddr, CgroupSockAddrAttachType}; | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.