use std::os::unix::io::{FromRawFd, OwnedFd};
use crate::sys::structs::{
BPF_ABS, BPF_JEQ, BPF_JMP, BPF_K, BPF_LD, BPF_RET, BPF_W,
EPERM,
OFFSET_ARCH, OFFSET_NR,
SECCOMP_FILTER_FLAG_NEW_LISTENER, SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV,
SECCOMP_RET_ALLOW, SECCOMP_RET_ERRNO, SECCOMP_RET_KILL_PROCESS, SECCOMP_RET_USER_NOTIF,
SECCOMP_SET_MODE_FILTER,
SockFilter, SockFprog,
};
use crate::sys::syscall::seccomp;
#[inline]
pub(crate) fn stmt(code: u16, k: u32) -> SockFilter {
SockFilter { code, jt: 0, jf: 0, k }
}
#[inline]
pub(crate) fn jump(code: u16, k: u32, jt: u8, jf: u8) -> SockFilter {
SockFilter { code, jt, jf, k }
}
pub fn assemble_filter(
notif_syscalls: &[u32],
deny_syscalls: &[u32],
arg_block: &[SockFilter],
) -> Result<Vec<SockFilter>, std::io::Error> {
let arch_block = 2usize; let arg_block_len = arg_block.len();
let load_nr = 1usize;
let notif_jmps = notif_syscalls.len();
let deny_jmps = deny_syscalls.len();
let ret_section = 4usize;
let total = arch_block + arg_block_len + load_nr + notif_jmps + deny_jmps + ret_section;
const MAX_BPF_INSNS: usize = 4096;
if total > MAX_BPF_INSNS {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("BPF program too large: {} instructions (max {})", total, MAX_BPF_INSNS),
));
}
let ret_kill_idx = total - 1;
let mut prog: Vec<SockFilter> = Vec::with_capacity(total);
prog.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARCH));
let arch_jf = (ret_kill_idx - 2) as u8;
prog.push(jump(BPF_JMP | BPF_JEQ | BPF_K, crate::arch::AUDIT_ARCH, 0, arch_jf));
prog.extend_from_slice(arg_block);
prog.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_NR));
let ret_notif_idx = total - 3;
let notif_base = arch_block + arg_block_len + load_nr;
for (i, &nr) in notif_syscalls.iter().enumerate() {
let pos = notif_base + i;
let jt = (ret_notif_idx - (pos + 1)) as u8;
prog.push(jump(BPF_JMP | BPF_JEQ | BPF_K, nr, jt, 0));
}
let ret_errno_idx = total - 2;
let deny_base = notif_base + notif_jmps;
for (i, &nr) in deny_syscalls.iter().enumerate() {
let pos = deny_base + i;
let jt = (ret_errno_idx - (pos + 1)) as u8;
prog.push(jump(BPF_JMP | BPF_JEQ | BPF_K, nr, jt, 0));
}
prog.push(stmt(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)); prog.push(stmt(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF)); prog.push(stmt(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM as u32)); prog.push(stmt(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS));
debug_assert_eq!(prog.len(), total, "BPF program length mismatch");
Ok(prog)
}
pub fn install_deny_filter(prog: &[SockFilter]) -> std::io::Result<()> {
let fprog = SockFprog {
len: prog.len() as u16,
filter: prog.as_ptr(),
};
seccomp(
SECCOMP_SET_MODE_FILTER,
0,
&fprog as *const SockFprog as *const std::ffi::c_void,
)?;
Ok(())
}
pub fn install_filter(prog: &[SockFilter]) -> std::io::Result<OwnedFd> {
let fprog = SockFprog {
len: prog.len() as u16,
filter: prog.as_ptr(),
};
let flags = SECCOMP_FILTER_FLAG_NEW_LISTENER | SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV;
let fd = seccomp(
SECCOMP_SET_MODE_FILTER,
flags,
&fprog as *const SockFprog as *const std::ffi::c_void,
)?;
Ok(unsafe { OwnedFd::from_raw_fd(fd as i32) })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_filter_has_arch_check_and_allow() {
let prog = assemble_filter(&[], &[], &[]).unwrap();
assert!(prog.len() >= 5);
assert_eq!(prog[0].code, BPF_LD | BPF_W | BPF_ABS);
assert_eq!(prog[0].k, OFFSET_ARCH);
}
#[test]
fn test_deny_syscall_present() {
let prog = assemble_filter(&[], &[libc::SYS_mount as u32], &[]).unwrap();
let has_mount = prog
.iter()
.any(|f| f.code == (BPF_JMP | BPF_JEQ | BPF_K) && f.k == libc::SYS_mount as u32);
assert!(has_mount);
}
#[test]
fn test_notif_syscall_present() {
let prog = assemble_filter(&[libc::SYS_openat as u32], &[], &[]).unwrap();
let has_openat = prog
.iter()
.any(|f| f.code == (BPF_JMP | BPF_JEQ | BPF_K) && f.k == libc::SYS_openat as u32);
assert!(has_openat);
}
#[test]
fn test_arch_jf_lands_on_kill() {
let prog = assemble_filter(&[], &[], &[]).unwrap();
let arch_jeq = &prog[1];
assert_eq!(arch_jeq.code, BPF_JMP | BPF_JEQ | BPF_K);
assert_eq!(arch_jeq.k, crate::arch::AUDIT_ARCH);
let kill_idx = prog.len() - 1;
let expected_jf = (kill_idx - 2) as u8;
assert_eq!(arch_jeq.jf, expected_jf);
assert_eq!(prog[kill_idx].k, SECCOMP_RET_KILL_PROCESS);
}
#[test]
fn test_default_allow_is_before_returns() {
let prog = assemble_filter(&[libc::SYS_openat as u32], &[libc::SYS_mount as u32], &[]).unwrap();
let allow_instr = &prog[prog.len() - 4];
assert_eq!(allow_instr.code, BPF_RET | BPF_K);
assert_eq!(allow_instr.k, SECCOMP_RET_ALLOW);
}
#[test]
fn test_notif_jt_lands_on_user_notif() {
let prog = assemble_filter(&[libc::SYS_openat as u32], &[], &[]).unwrap();
let ret_notif_idx = prog.len() - 3;
let notif_jeq = &prog[3];
assert_eq!(notif_jeq.code, BPF_JMP | BPF_JEQ | BPF_K);
assert_eq!(notif_jeq.k, libc::SYS_openat as u32);
let expected_jt = (ret_notif_idx - 4) as u8;
assert_eq!(notif_jeq.jt, expected_jt);
}
#[test]
fn test_arg_block_is_embedded() {
use crate::sys::structs::{BPF_JSET, OFFSET_ARGS0_LO};
let arg_block = vec![
stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_NR),
jump(BPF_JMP | BPF_JEQ | BPF_K, libc::SYS_clone as u32, 0, 3),
stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARGS0_LO),
jump(BPF_JMP | BPF_JSET | BPF_K, 0x0200_0000, 0, 1),
stmt(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM as u32),
];
let prog = assemble_filter(&[], &[], &arg_block).unwrap();
assert_eq!(prog[2].code, BPF_LD | BPF_W | BPF_ABS);
assert_eq!(prog[2].k, OFFSET_NR);
assert_eq!(prog[3].code, BPF_JMP | BPF_JEQ | BPF_K);
assert_eq!(prog[3].k, libc::SYS_clone as u32);
assert_eq!(prog[4].code, BPF_LD | BPF_W | BPF_ABS);
assert_eq!(prog[4].k, OFFSET_ARGS0_LO);
assert_eq!(prog[5].code, BPF_JMP | BPF_JSET | BPF_K);
assert_eq!(prog[5].k, 0x0200_0000);
}
#[test]
fn test_oversized_filter_is_rejected() {
let deny: Vec<u32> = (0..4097u32).collect();
let res = assemble_filter(&[], &deny, &[]);
let err = match res {
Ok(_) => panic!("expected oversize error"),
Err(e) => e,
};
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
}