use std::{collections::BTreeMap, convert::TryInto};
use seccompiler::{BpfProgram, SeccompAction, SeccompFilter, SeccompRule};
use crate::{
error::CoreError,
profile::SandboxProfile,
sandbox::{BackendOptions, linux::probe::ProbeResult},
};
pub const KILL_LIST: &[&str] = &[
"ptrace",
"process_vm_readv",
"process_vm_writev",
"kcmp",
"userfaultfd",
"bpf",
"perf_event_open",
"kexec_load",
"kexec_file_load",
"init_module",
"finit_module",
"delete_module",
];
pub const ERRNO_LIST: &[&str] = &[
"unshare", "setns",
"mount",
"umount2",
"swapon",
"swapoff",
"pivot_root",
"chroot",
"reboot",
"settimeofday",
"clock_settime",
"clock_adjtime",
"syslog",
"acct",
"vhangup",
"ioperm",
"iopl",
"open_by_handle_at",
];
#[derive(Debug)]
pub struct CompiledSeccomp {
pub kill: BpfProgram,
pub errno: BpfProgram,
}
pub fn compile(
profile: &SandboxProfile,
proxy_port: Option<u16>,
probe: &ProbeResult,
_options: BackendOptions,
) -> Result<CompiledSeccomp, CoreError> {
let target_arch = std::env::consts::ARCH
.try_into()
.map_err(|e| CoreError::Backend(format!("seccomp arch: {e}")))?;
let mut kill_rules: BTreeMap<i64, Vec<SeccompRule>> = BTreeMap::new();
for name in KILL_LIST {
if let Some(nr) = syscall_number(name) {
kill_rules.entry(nr).or_default();
}
}
let kill: BpfProgram = SeccompFilter::new(
kill_rules,
SeccompAction::Allow,
SeccompAction::KillProcess,
target_arch,
)
.map_err(|e| CoreError::Backend(format!("seccomp kill filter: {e}")))?
.try_into()
.map_err(|e| CoreError::Backend(format!("seccomp kill compile: {e}")))?;
let mut errno_rules: BTreeMap<i64, Vec<SeccompRule>> = BTreeMap::new();
for name in ERRNO_LIST {
if let Some(nr) = syscall_number(name) {
errno_rules.entry(nr).or_default();
}
}
let _ = (profile, proxy_port, probe);
let errno: BpfProgram = SeccompFilter::new(
errno_rules,
SeccompAction::Allow,
SeccompAction::Errno(libc::EPERM as u32),
target_arch,
)
.map_err(|e| CoreError::Backend(format!("seccomp errno filter: {e}")))?
.try_into()
.map_err(|e| CoreError::Backend(format!("seccomp errno compile: {e}")))?;
Ok(CompiledSeccomp { kill, errno })
}
fn syscall_number(name: &str) -> Option<i64> {
let n: libc::c_long = match name {
"ptrace" => libc::SYS_ptrace,
"process_vm_readv" => libc::SYS_process_vm_readv,
"process_vm_writev" => libc::SYS_process_vm_writev,
"kcmp" => libc::SYS_kcmp,
"userfaultfd" => libc::SYS_userfaultfd,
"bpf" => libc::SYS_bpf,
"perf_event_open" => libc::SYS_perf_event_open,
"kexec_load" => libc::SYS_kexec_load,
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
"kexec_file_load" => libc::SYS_kexec_file_load,
"init_module" => libc::SYS_init_module,
"finit_module" => libc::SYS_finit_module,
"delete_module" => libc::SYS_delete_module,
"unshare" => libc::SYS_unshare,
"setns" => libc::SYS_setns,
"mount" => libc::SYS_mount,
"umount2" => libc::SYS_umount2,
"swapon" => libc::SYS_swapon,
"swapoff" => libc::SYS_swapoff,
"pivot_root" => libc::SYS_pivot_root,
"chroot" => libc::SYS_chroot,
"reboot" => libc::SYS_reboot,
"settimeofday" => libc::SYS_settimeofday,
"clock_settime" => libc::SYS_clock_settime,
"clock_adjtime" => libc::SYS_clock_adjtime,
"syslog" => libc::SYS_syslog,
"acct" => libc::SYS_acct,
#[cfg(target_arch = "x86_64")]
"vhangup" => libc::SYS_vhangup,
#[cfg(target_arch = "x86_64")]
"ioperm" => libc::SYS_ioperm,
#[cfg(target_arch = "x86_64")]
"iopl" => libc::SYS_iopl,
"open_by_handle_at" => libc::SYS_open_by_handle_at,
_ => return None,
};
Some(n as i64)
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
use crate::{
detect::Ecosystem,
sandbox::linux::probe::{LandlockAbi, ProbeResult},
};
fn probe(abi: LandlockAbi) -> ProbeResult {
ProbeResult {
kernel: "Linux 6.8.0".to_owned(),
abi,
}
}
#[test]
fn test_should_compile_kill_and_errno_filters() {
let profile = SandboxProfile::for_ecosystem(
Ecosystem::Rust,
&PathBuf::from("/home/test"),
&PathBuf::from("/home/test/pwd"),
);
let compiled = compile(
&profile,
Some(8080),
&probe(LandlockAbi::V4),
BackendOptions::default(),
)
.expect("seccomp compile");
assert!(!compiled.kill.is_empty(), "kill filter empty");
assert!(!compiled.errno.is_empty(), "errno filter empty");
}
#[test]
fn test_should_resolve_known_syscalls() {
assert!(syscall_number("ptrace").is_some());
assert!(syscall_number("unshare").is_some());
assert!(syscall_number("does_not_exist").is_none());
}
}