use crate::config::Config;
use crate::output;
use nix::libc;
use seccompiler::{
SeccompAction, SeccompCmpArgLen, SeccompCmpOp, SeccompCondition,
SeccompFilter, SeccompRule,
};
use std::collections::BTreeMap;
use std::convert::TryInto;
const DENY_ALWAYS: &[i64] = &[
libc::SYS_init_module,
libc::SYS_finit_module,
libc::SYS_delete_module,
libc::SYS_kexec_load,
libc::SYS_kexec_file_load,
libc::SYS_reboot,
libc::SYS_acct,
libc::SYS_swapon,
libc::SYS_swapoff,
libc::SYS_mount,
libc::SYS_umount2,
libc::SYS_pivot_root,
libc::SYS_open_tree,
libc::SYS_move_mount,
libc::SYS_fsopen,
libc::SYS_fsconfig,
libc::SYS_fsmount,
libc::SYS_fspick,
libc::SYS_mount_setattr,
libc::SYS_ptrace,
libc::SYS_process_vm_readv,
libc::SYS_process_vm_writev,
libc::SYS_kcmp,
libc::SYS_userfaultfd,
libc::SYS_bpf,
libc::SYS_perf_event_open,
libc::SYS_io_uring_setup,
libc::SYS_io_uring_enter,
libc::SYS_io_uring_register,
libc::SYS_open_by_handle_at,
libc::SYS_unshare,
libc::SYS_setns,
libc::SYS_personality,
libc::SYS_pidfd_getfd,
libc::SYS_settimeofday,
libc::SYS_clock_settime,
libc::SYS_adjtimex,
libc::SYS_add_key,
libc::SYS_keyctl,
libc::SYS_request_key,
libc::SYS_quotactl,
libc::SYS_lookup_dcookie,
];
const DENY_LOCKDOWN: &[i64] = &[
libc::SYS_mbind,
libc::SYS_set_mempolicy,
libc::SYS_move_pages,
libc::SYS_sethostname,
libc::SYS_setdomainname,
];
#[cfg(target_arch = "x86_64")]
const DENY_ARCH: &[i64] =
&[libc::SYS_ioperm, libc::SYS_iopl, libc::SYS_modify_ldt];
#[cfg(not(target_arch = "x86_64"))]
const DENY_ARCH: &[i64] = &[];
pub fn apply(config: &Config, verbose: bool) -> Result<(), String> {
if !config.seccomp_enabled() {
if verbose {
output::verbose("Seccomp: disabled");
}
return Ok(());
}
let (bpf, clone3_bpf, count) = compile_filter(config)?;
seccompiler::apply_filter(&bpf)
.map_err(|e| format!("Seccomp: failed to install filter: {e}"))?;
seccompiler::apply_filter(&clone3_bpf).map_err(|e| {
format!("Seccomp: failed to install clone3 filter: {e}")
})?;
if verbose {
output::verbose(&format!(
"Seccomp: {} syscalls blocked ({})",
count,
if config.lockdown_enabled() {
"lockdown"
} else {
"normal"
}
));
}
Ok(())
}
fn compile_filter(
config: &Config,
) -> Result<(seccompiler::BpfProgram, seccompiler::BpfProgram, usize), String> {
let lockdown = config.lockdown_enabled();
let mut rules: BTreeMap<i64, Vec<seccompiler::SeccompRule>> =
BTreeMap::new();
for &nr in DENY_ALWAYS {
rules.insert(nr, vec![]);
}
for &nr in DENY_ARCH {
rules.insert(nr, vec![]);
}
if lockdown {
for &nr in DENY_LOCKDOWN {
rules.insert(nr, vec![]);
}
}
let socket_rules = [
SeccompCondition::new(0, SeccompCmpArgLen::Dword, SeccompCmpOp::Eq, 17),
SeccompCondition::new(0, SeccompCmpArgLen::Dword, SeccompCmpOp::Eq, 31),
SeccompCondition::new(0, SeccompCmpArgLen::Dword, SeccompCmpOp::Eq, 15),
SeccompCondition::new(
1,
SeccompCmpArgLen::Dword,
SeccompCmpOp::MaskedEq(0xF),
libc::SOCK_RAW as u64,
),
]
.into_iter()
.map(|condition| {
condition.and_then(|condition| SeccompRule::new(vec![condition]))
})
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("Seccomp: failed to build socket rules: {e}"))?;
rules.insert(libc::SYS_socket, socket_rules);
let tiocsti = SeccompCondition::new(
1,
SeccompCmpArgLen::Qword,
SeccompCmpOp::Eq,
libc::TIOCSTI,
)
.and_then(|condition| SeccompRule::new(vec![condition]))
.map_err(|e| format!("Seccomp: failed to build ioctl rules: {e}"))?;
rules.insert(libc::SYS_ioctl, vec![tiocsti]);
let count = rules.len() + 1;
let arch: seccompiler::TargetArch =
std::env::consts::ARCH.try_into().map_err(|_| {
format!(
"Seccomp: unsupported architecture: {}",
std::env::consts::ARCH
)
})?;
let filter = SeccompFilter::new(
rules,
SeccompAction::Allow,
SeccompAction::Errno(libc::EPERM as u32),
arch,
)
.map_err(|e| format!("Seccomp: failed to build filter: {e}"))?;
let bpf: seccompiler::BpfProgram = filter
.try_into()
.map_err(|e| format!("Seccomp: failed to compile BPF: {e}"))?;
let clone3_filter = SeccompFilter::new(
BTreeMap::from([(libc::SYS_clone3, vec![])]),
SeccompAction::Allow,
SeccompAction::Errno(libc::ENOSYS as u32),
arch,
)
.map_err(|e| format!("Seccomp: failed to build clone3 filter: {e}"))?;
let clone3_bpf = clone3_filter
.try_into()
.map_err(|e| format!("Seccomp: failed to compile clone3 BPF: {e}"))?;
Ok((bpf, clone3_bpf, count))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn filter_compiles_normal_mode() {
let config = Config::default();
let (bpf, clone3_bpf, count) = compile_filter(&config).unwrap();
assert!(!bpf.is_empty());
assert!(!clone3_bpf.is_empty());
assert!(count >= DENY_ALWAYS.len() + 2);
}
#[test]
fn filter_compiles_lockdown_mode() {
let config = Config {
lockdown: Some(true),
..Config::default()
};
let (_, _, count) = compile_filter(&config).unwrap();
assert!(count >= DENY_ALWAYS.len() + DENY_LOCKDOWN.len());
}
#[test]
fn filter_respects_disabled() {
let config = Config {
no_seccomp: Some(true),
..Config::default()
};
apply(&config, false).unwrap();
}
#[test]
fn deny_lists_have_no_duplicates() {
let mut seen = std::collections::HashSet::new();
for &nr in DENY_ALWAYS
.iter()
.chain(DENY_ARCH.iter())
.chain(DENY_LOCKDOWN.iter())
{
assert!(
seen.insert(nr),
"Duplicate syscall number {} in deny lists",
nr
);
}
}
#[test]
fn lockdown_blocks_more_than_normal() {
assert!(!DENY_LOCKDOWN.is_empty());
}
#[test]
fn filter_includes_new_deny_rules() {
assert!(DENY_ALWAYS.contains(&libc::SYS_personality));
assert!(DENY_ALWAYS.contains(&libc::SYS_pidfd_getfd));
let (_, clone3_bpf, _) = compile_filter(&Config::default()).unwrap();
assert!(!clone3_bpf.is_empty());
}
}