#[cfg(target_os = "linux")]
pub fn is_available() -> bool {
std::path::Path::new("/proc/sys/kernel/seccomp/actions_avail").exists()
}
#[cfg(not(target_os = "linux"))]
pub fn is_available() -> bool {
false
}
#[cfg(target_os = "linux")]
pub fn detect_denial(exit_code: i32, stderr: &str) -> bool {
if exit_code == 31 {
return true;
}
stderr.contains("Bad system call")
|| stderr.contains("bad system call")
|| stderr.contains("SIGSYS")
|| stderr.contains("seccomp")
|| stderr.contains("invalid argument") && exit_code == 159
}
#[cfg(not(target_os = "linux"))]
pub fn detect_denial(_exit_code: i32, _stderr: &str) -> bool {
false
}
#[cfg(target_os = "linux")]
pub fn apply_seccomp_filter() -> std::io::Result<()> {
#[repr(C)]
struct sock_filter {
code: u16,
jt: u8,
jf: u8,
k: u32,
}
const BPF_LD: u16 = 0x00;
const BPF_JMP: u16 = 0x05;
const BPF_RET: u16 = 0x06;
const BPF_W: u16 = 0x00;
const BPF_ABS: u16 = 0x20;
const BPF_JEQ: u16 = 0x10;
const BPF_JGE: u16 = 0x30;
const BPF_JA: u16 = 0x00;
const SECCOMP_RET_KILL_PROCESS: u32 = 0x8000_0000;
const SECCOMP_RET_ALLOW: u32 = 0x7FFF_0000;
const AUDIT_ARCH_X86_64: u32 = 0xC000_003E;
let allowed_syscalls: &[u32] = &[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 29, 30, 32, 33, 35, 39, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 102, 104, 107, 108, 110, 111, 112, 116, 131, 137, 138, 157, 158, 186, 201, 202, 204, 217, 218, 228, 230, 231, 232, 233, 234, 235, 257, 262, 273, 281, 291, 292, 293, 302, 318, 332, 334, 435, ];
let mut filter = vec![
sock_filter {
code: BPF_LD | BPF_W | BPF_ABS,
jt: 0,
jf: 0,
k: 4, },
sock_filter {
code: BPF_JMP | BPF_JEQ,
jt: 0,
jf: 1, k: AUDIT_ARCH_X86_64,
},
sock_filter {
code: BPF_RET,
jt: 0,
jf: 0,
k: SECCOMP_RET_KILL_PROCESS,
},
sock_filter {
code: BPF_LD | BPF_W | BPF_ABS,
jt: 0,
jf: 0,
k: 0, },
];
for &syscall in allowed_syscalls {
let remaining = (allowed_syscalls.len() as u8).saturating_sub(
allowed_syscalls
.iter()
.position(|&s| s == syscall)
.unwrap_or(0) as u8,
);
filter.push(sock_filter {
code: BPF_JMP | BPF_JEQ,
jt: remaining, jf: 0, k: syscall,
});
}
filter.push(sock_filter {
code: BPF_RET,
jt: 0,
jf: 0,
k: SECCOMP_RET_KILL_PROCESS,
});
filter.push(sock_filter {
code: BPF_RET,
jt: 0,
jf: 0,
k: SECCOMP_RET_ALLOW,
});
#[repr(C)]
struct sock_fprog {
len: u16,
filter: *const sock_filter,
}
let prog = sock_fprog {
len: filter.len() as u16,
filter: filter.as_ptr(),
};
let result = unsafe {
libc::prctl(
libc::PR_SET_SECCOMP,
libc::SECCOMP_MODE_FILTER,
&raw const prog,
0i64,
0i64,
)
};
if result != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_available_does_not_panic() {
let _ = is_available();
}
#[test]
#[cfg(target_os = "linux")]
fn test_detect_denial() {
assert!(detect_denial(31, ""));
assert!(detect_denial(1, "Bad system call"));
assert!(detect_denial(1, "SIGSYS"));
assert!(!detect_denial(0, "Success"));
assert!(!detect_denial(1, "File not found"));
}
#[test]
fn test_detect_denial_non_linux() {
#[cfg(not(target_os = "linux"))]
{
assert!(!detect_denial(31, "Bad system call"));
}
}
}