From 2cac478d70445b318794741e4dffddcc6febd38d Mon Sep 17 00:00:00 2001 From: Colin Marc Date: Thu, 12 Sep 2024 09:15:37 +0200 Subject: [PATCH] Add support for seccomp_unotify This allows users to install rules which notify a supervisory process. See https://man7.org/linux/man-pages/man2/seccomp_unotify.2.html. Signed-off-by: Colin Marc --- CHANGELOG.md | 5 +++++ Cargo.toml | 1 + src/backend/mod.rs | 9 +++++++++ src/lib.rs | 40 ++++++++++++++++++++++++++++++---------- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a733bcdc..06c83769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Upcoming release + +## Added +- Support for `seccomp_unotify`. + # v0.4.0 ## Changed diff --git a/Cargo.toml b/Cargo.toml index 88dae9b0..5ef84878 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ edition = "2021" [features] json = ["serde", "serde_json"] +seccomp_unotify = [] [dependencies] libc = "^0.2.153" diff --git a/src/backend/mod.rs b/src/backend/mod.rs index d330b0c6..9235a04e 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -25,6 +25,10 @@ use libc::{ SECCOMP_RET_KILL_THREAD, SECCOMP_RET_LOG, SECCOMP_RET_TRACE, SECCOMP_RET_TRAP, }; +// Not available in the libc crate yet. +#[cfg(feature = "seccomp_unotify")] +const SECCOMP_RET_USER_NOTIF: libc::c_uint = 0x7fc00000; + use bpf::{ARG_NUMBER_MAX, AUDIT_ARCH_AARCH64, AUDIT_ARCH_X86_64, BPF_MAX_LEN}; pub use bpf::{sock_filter, BpfProgram, BpfProgramRef}; @@ -162,6 +166,9 @@ pub enum SeccompAction { Trace(u32), /// Sends `SIGSYS` to the calling process. Trap, + /// Sends a notification to a supervisory process. + #[cfg(feature = "seccomp_unotify")] + Notify, } impl From for u32 { @@ -181,6 +188,8 @@ impl From for u32 { SeccompAction::Log => SECCOMP_RET_LOG, SeccompAction::Trace(x) => SECCOMP_RET_TRACE | (x & SECCOMP_RET_DATA), SeccompAction::Trap => SECCOMP_RET_TRAP, + #[cfg(feature = "seccomp_unotify")] + SeccompAction::Notify => SECCOMP_RET_USER_NOTIF, } } } diff --git a/src/lib.rs b/src/lib.rs index 10f3a79e..9700713e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -195,6 +195,9 @@ use std::convert::TryInto; #[cfg(feature = "json")] use std::io::Read; +#[cfg(feature = "seccomp_unotify")] +use std::os::fd::{FromRawFd as _, OwnedFd}; + use std::collections::HashMap; use std::fmt::{Display, Formatter}; use std::io; @@ -309,7 +312,8 @@ impl From for Error { /// /// [`BpfProgram`]: type.BpfProgram.html pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> { - apply_filter_with_flags(bpf_filter, 0) + apply_filter_with_flags(bpf_filter, 0)?; + Ok(()) } /// Apply a BPF filter to the all threads in the process via the TSYNC feature. Please read the @@ -321,7 +325,28 @@ pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> { /// /// [`BpfProgram`]: type.BpfProgram.html pub fn apply_filter_all_threads(bpf_filter: BpfProgramRef) -> Result<()> { - apply_filter_with_flags(bpf_filter, libc::SECCOMP_FILTER_FLAG_TSYNC) + let rc = apply_filter_with_flags(bpf_filter, libc::SECCOMP_FILTER_FLAG_TSYNC)?; + + if rc > 0 { + return Err(Error::ThreadSync(rc)); + } + + Ok(()) +} + +/// Apply a filter with the SECCOMP_FILTER_FLAG_NEW_LISTENER flag. +/// +/// The returned FD can be polled for notifications generated by a rule with the +/// action [SeccompAction::Notify]. See `man 2 seccomp_unotify` for more +/// information. +#[cfg(feature = "seccomp_unotify")] +pub fn apply_filter_with_notify_fd(bpf_filter: BpfProgramRef) -> Result { + let rc = apply_filter_with_flags(bpf_filter, libc::SECCOMP_FILTER_FLAG_NEW_LISTENER)?; + + // SAFETY: seccomp_unotify documents that it returns a valid FD if the + // syscall is a success. + let fd = unsafe { OwnedFd::from_raw_fd(rc as _) }; + Ok(fd) } /// Apply a BPF filter to the calling thread. @@ -332,7 +357,7 @@ pub fn apply_filter_all_threads(bpf_filter: BpfProgramRef) -> Result<()> { /// * `flags` - A u64 representing a bitset of seccomp's flags parameter. /// /// [`BpfProgram`]: type.BpfProgram.html -fn apply_filter_with_flags(bpf_filter: BpfProgramRef, flags: libc::c_ulong) -> Result<()> { +fn apply_filter_with_flags(bpf_filter: BpfProgramRef, flags: libc::c_ulong) -> Result { // If the program is empty, don't install the filter. if bpf_filter.is_empty() { return Err(Error::EmptyFilter); @@ -363,16 +388,11 @@ fn apply_filter_with_flags(bpf_filter: BpfProgramRef, flags: libc::c_ulong) -> R ) }; - #[allow(clippy::comparison_chain)] - // Per manpage, if TSYNC fails, retcode is >0 and equals the pid of the thread that caused the - // failure. Otherwise, error code is -1 and errno is set. if rc < 0 { - return Err(Error::Seccomp(io::Error::last_os_error())); - } else if rc > 0 { - return Err(Error::ThreadSync(rc)); + return Err(Error::Seccomp(std::io::Error::last_os_error())); } - Ok(()) + Ok(rc) } /// Compile [`BpfProgram`]s from JSON.