use std::io::{Error, Result};
#[cfg(not(target_os = "linux"))]
compile_error!("seccomp filtering is only for Linux targets");
#[link(name = "seccomp")]
unsafe extern "C" {
static statime_kill_thread: u32;
static statime_kill_process: u32;
static statime_trap: u32;
static statime_err: u32;
static statime_log: u32;
fn statime_sandbox(def_action: u32) -> std::ffi::c_int;
}
pub enum Seccomp {
KillThread,
KillProcess,
Trap,
Fail,
Log,
}
pub use Seccomp::*;
impl Seccomp {
fn into_u32(self) -> u32 {
unsafe {
match self {
Seccomp::KillThread => statime_kill_thread,
Seccomp::KillProcess => statime_kill_process,
Seccomp::Trap => statime_trap,
Seccomp::Fail => statime_err,
Seccomp::Log => statime_log,
}
}
}
pub fn enable(self) -> Result<()> {
let scmp_action = self.into_u32();
match unsafe { statime_sandbox(scmp_action) } {
0 => Ok(()),
x if x < 0 => Err(Error::from_raw_os_error(-x)),
_ => panic!("libseccomp returned a positive error"),
}
}
}
#[cfg(test)]
mod test {
use super::*;
fn not_allowed() -> std::io::Result<()> {
std::env::set_current_dir(".").map(|_| ())
}
fn allowed() -> std::io::Result<()> {
std::net::UdpSocket::bind("0.0.0.0:0").map(|_| ())
}
#[test]
fn cannot_downgrade() {
Fail.enable().unwrap();
assert!(Log.enable().is_err());
}
#[test]
fn allowlisted_functions_are_fine() {
Fail.enable().unwrap();
assert!(allowed().is_ok());
}
#[test]
fn blocklisted_functions_are_stopped() {
Fail.enable().unwrap();
assert!(not_allowed().is_err());
}
#[test]
fn blocklisted_functions_are_fine_when_logged() {
Log.enable().unwrap();
assert!(not_allowed().is_ok());
}
#[test]
fn thread_is_stopped() {
let result = std::panic::catch_unwind(move || {
let handle = std::thread::spawn(|| {
KillThread.enable().unwrap();
let _ = not_allowed();
});
let _ = handle.join();
});
assert!(result.is_err());
}
#[allow(clippy::undocumented_unsafe_blocks)]
#[test]
fn process_is_stopped() {
for catchable in [true, false] {
unsafe {
match libc::fork() {
0 => {
unsafe extern "C" fn exit42(_: std::ffi::c_int) {
unsafe { libc::_exit(42) }
}
libc::signal(libc::SIGSYS, exit42 as *const () as libc::sighandler_t);
if catchable { Trap } else { KillProcess }.enable().unwrap();
let _ = not_allowed();
unreachable!();
}
child_pid => {
let mut status = 0;
assert_eq!(libc::wait(&mut status), child_pid);
if catchable {
assert!(libc::WIFEXITED(status));
assert_eq!(libc::WEXITSTATUS(status), 42);
} else {
assert!(libc::WIFSIGNALED(status));
assert_eq!(libc::WTERMSIG(status), libc::SIGSYS);
}
}
};
}
}
}
}