use std::collections::HashMap;
use std::os::unix::io::RawFd;
use std::sync::Arc;
use super::ctx::SupervisorCtx;
use super::notif::{NotifAction, NotifPolicy};
use super::state::ResourceState;
use super::syscall::SyscallError;
use crate::arch;
use crate::sys::structs::SeccompNotif;
use thiserror::Error;
use tokio::sync::Mutex;
pub trait Handler: Send + Sync + 'static {
fn handle<'a>(
&'a self,
cx: &'a HandlerCtx,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>>;
}
pub struct HandlerCtx {
pub notif: SeccompNotif,
pub notif_fd: RawFd,
}
impl<F, Fut> Handler for F
where
F: Fn(&HandlerCtx) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = NotifAction> + Send + 'static,
{
fn handle<'a>(
&'a self,
cx: &'a HandlerCtx,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>> {
Box::pin((self)(cx))
}
}
impl Handler for Box<dyn Handler> {
fn handle<'a>(
&'a self,
cx: &'a HandlerCtx,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>> {
(**self).handle(cx)
}
}
impl Handler for std::sync::Arc<dyn Handler> {
fn handle<'a>(
&'a self,
cx: &'a HandlerCtx,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>> {
(**self).handle(cx)
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum HandlerError {
#[error("invalid syscall in handler registration: {0}")]
InvalidSyscall(#[from] SyscallError),
#[error(
"handler on syscall {syscall_nr} conflicts with the policy syscall blocklist \
and would let user code bypass it via SECCOMP_USER_NOTIF_FLAG_CONTINUE"
)]
OnDenySyscall { syscall_nr: i64 },
}
fn open_family_syscalls() -> Vec<i64> {
let mut v = vec![libc::SYS_openat, arch::SYS_OPENAT2];
if let Some(legacy_open) = arch::SYS_OPEN {
v.push(legacy_open);
}
v
}
pub(crate) fn validate_handler_syscalls_against_policy(
syscall_nrs: &[i64],
policy: &crate::sandbox::Sandbox,
) -> Result<(), i64> {
let blocklist: std::collections::HashSet<u32> =
crate::context::blocklist_syscall_numbers(policy).into_iter().collect();
for &nr in syscall_nrs {
if blocklist.contains(&(nr as u32)) {
return Err(nr);
}
}
Ok(())
}
struct HandlerChain {
handlers: Vec<std::sync::Arc<dyn Handler>>,
}
pub struct DispatchTable {
chains: HashMap<i64, HandlerChain>,
}
impl DispatchTable {
pub fn new() -> Self {
Self {
chains: HashMap::new(),
}
}
pub fn register<H: Handler>(&mut self, syscall_nr: i64, handler: H) {
self.register_arc(syscall_nr, std::sync::Arc::new(handler));
}
pub(crate) fn register_arc(
&mut self,
syscall_nr: i64,
handler: std::sync::Arc<dyn Handler>,
) {
self.chains
.entry(syscall_nr)
.or_insert_with(|| HandlerChain { handlers: Vec::new() })
.handlers
.push(handler);
}
pub(crate) async fn dispatch(
&self,
notif: SeccompNotif,
notif_fd: RawFd,
) -> NotifAction {
let nr = notif.data.nr as i64;
if let Some(chain) = self.chains.get(&nr) {
let handler_ctx = HandlerCtx { notif, notif_fd };
for handler in &chain.handlers {
let action = handler.handle(&handler_ctx).await;
if !matches!(action, NotifAction::Continue) {
return action;
}
}
}
NotifAction::Continue
}
}
pub(crate) fn build_dispatch_table(
policy: &Arc<NotifPolicy>,
resource: &Arc<Mutex<ResourceState>>,
ctx: &Arc<SupervisorCtx>,
pending_handlers: Vec<(i64, std::sync::Arc<dyn Handler>)>,
) -> DispatchTable {
let mut table = DispatchTable::new();
for &nr in arch::FORK_LIKE_SYSCALLS {
let policy_for_fork = Arc::clone(policy);
let resource_for_fork = Arc::clone(resource);
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let notif_fd = cx.notif_fd;
let policy = Arc::clone(&policy_for_fork);
let resource = Arc::clone(&resource_for_fork);
async move {
crate::resource::handle_fork(¬if, notif_fd, &resource, &policy).await
}
});
}
for &nr in &[libc::SYS_wait4, libc::SYS_waitid] {
let resource_for_wait = Arc::clone(resource);
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let resource = Arc::clone(&resource_for_wait);
async move {
crate::resource::handle_wait(¬if, &resource).await
}
});
}
if policy.has_memory_limit {
for &nr in &[
libc::SYS_mmap, libc::SYS_munmap, libc::SYS_brk,
libc::SYS_mremap, libc::SYS_shmget,
] {
let policy_for_mem = Arc::clone(policy);
let __sup = Arc::clone(ctx);
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let policy = Arc::clone(&policy_for_mem);
async move {
crate::resource::handle_memory(¬if, &sup, &policy).await
}
});
}
}
if policy.has_net_allowlist || policy.has_http_acl {
for &nr in &[
libc::SYS_connect,
libc::SYS_sendto,
libc::SYS_sendmsg,
libc::SYS_sendmmsg,
] {
let __sup = Arc::clone(ctx);
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
async move {
crate::network::handle_net(¬if, &sup, notif_fd).await
}
});
}
}
if policy.has_random_seed {
let __sup = Arc::clone(ctx);
table.register(libc::SYS_getrandom, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
async move {
let mut tr = sup.time_random.lock().await;
if let Some(ref mut rng) = tr.random_state {
crate::random::handle_getrandom(¬if, rng, notif_fd)
} else {
NotifAction::Continue
}
}
});
}
if policy.has_random_seed {
for nr in open_family_syscalls() {
let __sup = Arc::clone(ctx);
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
async move {
let mut tr = sup.time_random.lock().await;
if let Some(ref mut rng) = tr.random_state {
if let Some(action) = crate::random::handle_random_open(¬if, rng, notif_fd) {
return action;
}
}
NotifAction::Continue
}
});
}
}
if policy.has_time_start {
let time_offset = policy.time_offset;
for &nr in &[
libc::SYS_clock_nanosleep as i64,
libc::SYS_timerfd_settime as i64,
libc::SYS_timer_settime as i64,
] {
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let notif_fd = cx.notif_fd;
async move {
crate::time::handle_timer(¬if, time_offset, notif_fd)
}
});
}
}
{
let etc_hosts = policy.virtual_etc_hosts.clone();
for nr in open_family_syscalls() {
let etc_hosts = etc_hosts.clone();
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let notif_fd = cx.notif_fd;
let etc_hosts = etc_hosts.clone();
async move {
if let Some(action) = crate::procfs::handle_etc_hosts_open(¬if, &etc_hosts, notif_fd) {
action
} else {
NotifAction::Continue
}
}
});
}
}
if policy.chroot_root.is_some() {
register_chroot_handlers(&mut table, policy, ctx);
}
if policy.cow_enabled {
register_cow_handlers(&mut table, ctx);
}
for nr in open_family_syscalls() {
let policy_for_proc_open = Arc::clone(policy);
let resource_for_proc_open = Arc::clone(resource);
let __sup = Arc::clone(ctx);
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
let policy = Arc::clone(&policy_for_proc_open);
let resource = Arc::clone(&resource_for_proc_open);
async move {
let processes = Arc::clone(&sup.processes);
let network = Arc::clone(&sup.network);
crate::procfs::handle_proc_open(¬if, &processes, &resource, &network, &policy, notif_fd).await
}
});
}
let mut getdents_nrs = vec![libc::SYS_getdents64];
if let Some(getdents) = arch::SYS_GETDENTS {
getdents_nrs.push(getdents);
}
for nr in getdents_nrs {
let policy_for_getdents = Arc::clone(policy);
let __sup = Arc::clone(ctx);
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
let policy = Arc::clone(&policy_for_getdents);
async move {
let processes = Arc::clone(&sup.processes);
crate::procfs::handle_getdents(¬if, &processes, &policy, notif_fd).await
}
});
}
if let Some(n) = policy.num_cpus {
table.register(libc::SYS_sched_getaffinity, move |cx: &HandlerCtx| {
let notif = cx.notif;
let notif_fd = cx.notif_fd;
async move {
crate::procfs::handle_sched_getaffinity(¬if, n, notif_fd)
}
});
}
if let Some(ref hostname) = policy.virtual_hostname {
let hostname_for_uname = hostname.clone();
let hostname_for_open = hostname.clone();
table.register(libc::SYS_uname, move |cx: &HandlerCtx| {
let notif = cx.notif;
let notif_fd = cx.notif_fd;
let hostname = hostname_for_uname.clone();
async move {
crate::procfs::handle_uname(¬if, &hostname, notif_fd)
}
});
for nr in open_family_syscalls() {
let hostname = hostname_for_open.clone();
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let notif_fd = cx.notif_fd;
let hostname = hostname.clone();
async move {
if let Some(action) = crate::procfs::handle_hostname_open(¬if, &hostname, notif_fd) {
action
} else {
NotifAction::Continue
}
}
});
}
}
if policy.deterministic_dirs {
let mut getdents_nrs = vec![libc::SYS_getdents64];
if let Some(getdents) = arch::SYS_GETDENTS {
getdents_nrs.push(getdents);
}
for nr in getdents_nrs {
let __sup = Arc::clone(ctx);
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
async move {
let processes = Arc::clone(&sup.processes);
crate::procfs::handle_sorted_getdents(¬if, &processes, notif_fd).await
}
});
}
}
{
let __sup = Arc::clone(ctx);
table.register(libc::SYS_socket, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
async move {
let state = Arc::clone(&sup.netlink);
crate::netlink::handlers::handle_socket(¬if, &state).await
}
});
let __sup = Arc::clone(ctx);
table.register(libc::SYS_bind, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
async move {
let state = Arc::clone(&sup.netlink);
crate::netlink::handlers::handle_bind(¬if, &state).await
}
});
let __sup = Arc::clone(ctx);
table.register(libc::SYS_getsockname, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
async move {
let state = Arc::clone(&sup.netlink);
crate::netlink::handlers::handle_getsockname(¬if, &state, notif_fd).await
}
});
for &nr in &[libc::SYS_recvfrom, libc::SYS_recvmsg] {
let __sup = Arc::clone(ctx);
table.register(nr, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
async move {
let state = Arc::clone(&sup.netlink);
crate::netlink::handlers::handle_netlink_recvmsg(¬if, &state, notif_fd).await
}
});
}
let __sup = Arc::clone(ctx);
table.register(libc::SYS_close, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
async move {
let state = Arc::clone(&sup.netlink);
crate::netlink::handlers::handle_close(¬if, &state).await
}
});
}
if policy.port_remap || policy.has_net_allowlist {
let __sup = Arc::clone(ctx);
table.register(libc::SYS_bind, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
async move {
crate::port_remap::handle_bind(¬if, &sup.network, notif_fd).await
}
});
}
if policy.port_remap {
let __sup = Arc::clone(ctx);
table.register(libc::SYS_getsockname, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
async move {
crate::port_remap::handle_getsockname(¬if, &sup.network, notif_fd).await
}
});
}
for (nr, h) in pending_handlers {
table.register_arc(nr, h);
}
table
}
fn register_chroot_handlers(
table: &mut DispatchTable,
policy: &Arc<NotifPolicy>,
ctx: &Arc<SupervisorCtx>,
) {
use crate::chroot::dispatch::ChrootCtx;
macro_rules! chroot_handler {
($policy:expr, $handler:expr) => {{
let policy = Arc::clone($policy);
let chroot_state = Arc::clone(&ctx.chroot);
let cow_state = Arc::clone(&ctx.cow);
move |cx: &HandlerCtx| {
let notif = cx.notif;
let chroot_state = Arc::clone(&chroot_state);
let cow_state = Arc::clone(&cow_state);
let notif_fd = cx.notif_fd;
let policy = Arc::clone(&policy);
async move {
let chroot_ctx = ChrootCtx {
root: policy.chroot_root.as_ref().unwrap(),
readable: &policy.chroot_readable,
writable: &policy.chroot_writable,
denied: &policy.chroot_denied,
mounts: &policy.chroot_mounts,
};
$handler(¬if, &chroot_state, &cow_state, notif_fd, &chroot_ctx).await
}
}
}};
}
macro_rules! chroot_handler_fallthrough {
($policy:expr, $handler:expr) => {{
let policy = Arc::clone($policy);
let chroot_state = Arc::clone(&ctx.chroot);
let cow_state = Arc::clone(&ctx.cow);
move |cx: &HandlerCtx| {
let notif = cx.notif;
let chroot_state = Arc::clone(&chroot_state);
let cow_state = Arc::clone(&cow_state);
let notif_fd = cx.notif_fd;
let policy = Arc::clone(&policy);
async move {
let chroot_ctx = ChrootCtx {
root: policy.chroot_root.as_ref().unwrap(),
readable: &policy.chroot_readable,
writable: &policy.chroot_writable,
denied: &policy.chroot_denied,
mounts: &policy.chroot_mounts,
};
$handler(¬if, &chroot_state, &cow_state, notif_fd, &chroot_ctx).await
}
}
}};
}
table.register(libc::SYS_openat, chroot_handler_fallthrough!(policy,
crate::chroot::dispatch::handle_chroot_open));
if let Some(open) = arch::SYS_OPEN {
table.register(open, chroot_handler_fallthrough!(policy,
crate::chroot::dispatch::handle_chroot_legacy_open));
}
for &nr in &[libc::SYS_execve, libc::SYS_execveat] {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_exec));
}
for &nr in &[
libc::SYS_unlinkat, libc::SYS_mkdirat, libc::SYS_renameat2,
libc::SYS_symlinkat, libc::SYS_linkat, libc::SYS_fchmodat,
libc::SYS_fchownat, libc::SYS_truncate,
] {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_write));
}
if let Some(nr) = arch::SYS_UNLINK {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_unlink));
}
if let Some(nr) = arch::SYS_RMDIR {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_rmdir));
}
if let Some(nr) = arch::SYS_MKDIR {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_mkdir));
}
if let Some(nr) = arch::SYS_RENAME {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_rename));
}
if let Some(nr) = arch::SYS_SYMLINK {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_symlink));
}
if let Some(nr) = arch::SYS_LINK {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_link));
}
if let Some(nr) = arch::SYS_CHMOD {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_chmod));
}
if let Some(chown) = arch::SYS_CHOWN {
let policy_for_chown = Arc::clone(policy);
let __sup = Arc::clone(ctx);
table.register(chown, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
let policy = Arc::clone(&policy_for_chown);
async move {
let chroot_ctx = ChrootCtx {
root: policy.chroot_root.as_ref().unwrap(),
readable: &policy.chroot_readable,
writable: &policy.chroot_writable,
denied: &policy.chroot_denied,
mounts: &policy.chroot_mounts,
};
crate::chroot::dispatch::handle_chroot_legacy_chown(¬if, &sup.chroot, &sup.cow, notif_fd, &chroot_ctx, false).await
}
});
}
if let Some(lchown) = arch::SYS_LCHOWN {
let policy_for_lchown = Arc::clone(policy);
let __sup = Arc::clone(ctx);
table.register(lchown, move |cx: &HandlerCtx| {
let notif = cx.notif;
let sup = Arc::clone(&__sup);
let notif_fd = cx.notif_fd;
let policy = Arc::clone(&policy_for_lchown);
async move {
let chroot_ctx = ChrootCtx {
root: policy.chroot_root.as_ref().unwrap(),
readable: &policy.chroot_readable,
writable: &policy.chroot_writable,
denied: &policy.chroot_denied,
mounts: &policy.chroot_mounts,
};
crate::chroot::dispatch::handle_chroot_legacy_chown(¬if, &sup.chroot, &sup.cow, notif_fd, &chroot_ctx, true).await
}
});
}
for &nr in &[
libc::SYS_newfstatat,
libc::SYS_faccessat,
crate::chroot::dispatch::SYS_FACCESSAT2,
] {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_stat));
}
if let Some(nr) = arch::SYS_STAT {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_stat));
}
if let Some(nr) = arch::SYS_LSTAT {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_lstat));
}
if let Some(nr) = arch::SYS_ACCESS {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_access));
}
table.register(libc::SYS_statx, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_statx));
table.register(libc::SYS_readlinkat, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_readlink));
if let Some(nr) = arch::SYS_READLINK {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_readlink));
}
let mut getdents_nrs = vec![libc::SYS_getdents64];
if let Some(getdents) = arch::SYS_GETDENTS {
getdents_nrs.push(getdents);
}
for nr in getdents_nrs {
table.register(nr, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_getdents));
}
table.register(libc::SYS_chdir as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_chdir));
table.register(libc::SYS_getcwd as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_getcwd));
table.register(libc::SYS_statfs as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_statfs));
table.register(libc::SYS_utimensat as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_utimensat));
}
fn register_cow_handlers(table: &mut DispatchTable, ctx: &Arc<SupervisorCtx>) {
macro_rules! cow_call {
($handler:expr) => {{
let cow_state = Arc::clone(&ctx.cow);
let processes_state = Arc::clone(&ctx.processes);
move |cx: &HandlerCtx| {
let notif = cx.notif;
let cow_state = Arc::clone(&cow_state);
let processes_state = Arc::clone(&processes_state);
let notif_fd = cx.notif_fd;
async move {
$handler(¬if, &cow_state, &processes_state, notif_fd).await
}
}
}};
}
let mut write_nrs = vec![
libc::SYS_unlinkat, libc::SYS_mkdirat, libc::SYS_renameat2,
libc::SYS_symlinkat, libc::SYS_linkat, libc::SYS_fchmodat,
libc::SYS_fchownat, libc::SYS_truncate,
];
write_nrs.extend([
arch::SYS_UNLINK, arch::SYS_RMDIR, arch::SYS_MKDIR, arch::SYS_RENAME,
arch::SYS_SYMLINK, arch::SYS_LINK, arch::SYS_CHMOD, arch::SYS_CHOWN,
arch::SYS_LCHOWN,
].into_iter().flatten());
for nr in write_nrs {
table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_write));
}
table.register(libc::SYS_utimensat, cow_call!(crate::cow::dispatch::handle_cow_utimensat));
let mut access_nrs = vec![libc::SYS_faccessat, crate::cow::dispatch::SYS_FACCESSAT2];
access_nrs.extend(arch::SYS_ACCESS);
for nr in access_nrs {
table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_access));
}
let mut open_nrs = vec![libc::SYS_openat];
open_nrs.extend(arch::SYS_OPEN);
for nr in open_nrs {
table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_open));
}
let mut stat_nrs = vec![libc::SYS_newfstatat, libc::SYS_faccessat];
stat_nrs.extend([arch::SYS_STAT, arch::SYS_LSTAT, arch::SYS_ACCESS].into_iter().flatten());
for nr in stat_nrs {
table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_stat));
}
table.register(libc::SYS_statx, cow_call!(crate::cow::dispatch::handle_cow_statx));
let mut readlink_nrs = vec![libc::SYS_readlinkat];
readlink_nrs.extend(arch::SYS_READLINK);
for nr in readlink_nrs {
table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_readlink));
}
let mut getdents_nrs = vec![libc::SYS_getdents64];
getdents_nrs.extend(arch::SYS_GETDENTS);
for nr in getdents_nrs {
table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_getdents));
}
table.register(libc::SYS_chdir, cow_call!(crate::cow::dispatch::handle_cow_chdir));
table.register(libc::SYS_getcwd, cow_call!(crate::cow::dispatch::handle_cow_getcwd));
for &nr in &[libc::SYS_execve, libc::SYS_execveat] {
table.register(nr, cow_call!(crate::cow::dispatch::handle_cow_exec));
}
}
#[cfg(test)]
mod handler_tests {
use super::*;
use crate::netlink::NetlinkState;
use crate::seccomp::ctx::SupervisorCtx;
use crate::seccomp::notif::NotifPolicy;
use crate::seccomp::state::{
ChrootState, CowState, NetworkState, PolicyFnState, ProcessIndex, ProcfsState,
ResourceState, TimeRandomState,
};
use crate::sys::structs::{SeccompData, SeccompNotif};
use std::sync::atomic::{AtomicUsize, Ordering};
fn fake_notif(nr: i32) -> SeccompNotif {
SeccompNotif {
id: 0,
pid: 1,
flags: 0,
data: SeccompData {
nr,
arch: 0,
instruction_pointer: 0,
args: [0; 6],
},
}
}
fn fake_supervisor_ctx() -> Arc<SupervisorCtx> {
Arc::new(SupervisorCtx {
resource: Arc::new(Mutex::new(ResourceState::new(0, 0))),
cow: Arc::new(Mutex::new(CowState::new())),
procfs: Arc::new(Mutex::new(ProcfsState::new())),
network: Arc::new(Mutex::new(NetworkState::new())),
time_random: Arc::new(Mutex::new(TimeRandomState::new(None, None))),
policy_fn: Arc::new(Mutex::new(PolicyFnState::new())),
chroot: Arc::new(Mutex::new(ChrootState::new())),
netlink: Arc::new(NetlinkState::new()),
processes: Arc::new(ProcessIndex::new()),
policy: Arc::new(NotifPolicy {
max_memory_bytes: 0,
max_processes: 0,
has_memory_limit: false,
has_net_allowlist: false,
has_random_seed: false,
has_time_start: false,
time_offset: 0,
num_cpus: None,
argv_safety_required: false,
port_remap: false,
cow_enabled: false,
chroot_root: None,
chroot_readable: Vec::new(),
chroot_writable: Vec::new(),
chroot_denied: Vec::new(),
chroot_mounts: Vec::new(),
deterministic_dirs: false,
virtual_hostname: None,
has_http_acl: false,
virtual_etc_hosts: String::new(),
}),
child_pidfd: None,
notif_fd: -1,
})
}
#[tokio::test]
async fn dispatch_walks_chain_in_registration_order() {
let mut table = DispatchTable::new();
let order = Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
for tag in [1u8, 2u8, 3u8] {
let order_clone = Arc::clone(&order);
table.register(
libc::SYS_openat,
move |_cx: &HandlerCtx| {
let order = Arc::clone(&order_clone);
async move {
order.lock().unwrap().push(tag);
NotifAction::Continue
}
},
);
}
let _ctx = fake_supervisor_ctx();
let action = table
.dispatch(fake_notif(libc::SYS_openat as i32), -1)
.await;
assert!(matches!(action, NotifAction::Continue));
let recorded = order.lock().unwrap();
assert_eq!(
*recorded,
[1u8, 2u8, 3u8],
"every handler must run, in the order it was registered"
);
}
#[tokio::test]
async fn dispatch_runs_builtin_before_extra() {
let mut table = DispatchTable::new();
let order = Arc::new(std::sync::Mutex::new(Vec::<u8>::new()));
let order_builtin = Arc::clone(&order);
table.register(
libc::SYS_openat,
move |_cx: &HandlerCtx| {
let order = Arc::clone(&order_builtin);
async move {
order.lock().unwrap().push(b'B');
NotifAction::Continue
}
},
);
let order_extra = Arc::clone(&order);
table.register(
libc::SYS_openat,
move |_cx: &HandlerCtx| {
let order = Arc::clone(&order_extra);
async move {
order.lock().unwrap().push(b'E');
NotifAction::Continue
}
},
);
let _ctx = fake_supervisor_ctx();
let action = table
.dispatch(fake_notif(libc::SYS_openat as i32), -1)
.await;
assert!(matches!(action, NotifAction::Continue));
let recorded = order.lock().unwrap();
assert_eq!(
*recorded,
[b'B', b'E'],
"builtin must run before extra (insertion order preserved)"
);
}
#[tokio::test]
async fn dispatch_stops_at_first_non_continue() {
let mut table = DispatchTable::new();
let calls = Arc::new(AtomicUsize::new(0));
let calls_first = Arc::clone(&calls);
table.register(
libc::SYS_openat,
move |_cx: &HandlerCtx| {
let calls = Arc::clone(&calls_first);
async move {
calls.fetch_add(1, Ordering::SeqCst);
NotifAction::Errno(libc::EACCES)
}
},
);
let calls_second = Arc::clone(&calls);
table.register(
libc::SYS_openat,
move |_cx: &HandlerCtx| {
let calls = Arc::clone(&calls_second);
async move {
calls.fetch_add(1, Ordering::SeqCst);
NotifAction::Continue
}
},
);
let _ctx = fake_supervisor_ctx();
let action = table
.dispatch(fake_notif(libc::SYS_openat as i32), -1)
.await;
match action {
NotifAction::Errno(e) => assert_eq!(e, libc::EACCES),
other => panic!("expected Errno(EACCES), got {:?}", other),
}
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"second handler must not run after first returned non-Continue"
);
}
#[tokio::test]
async fn dispatch_short_circuits_on_defer() {
let mut table = DispatchTable::new();
let later_ran = Arc::new(AtomicUsize::new(0));
table.register(
libc::SYS_openat,
|_cx: &HandlerCtx| async { NotifAction::defer(async { NotifAction::ReturnValue(1) }) },
);
let later = Arc::clone(&later_ran);
table.register(
libc::SYS_openat,
move |_cx: &HandlerCtx| {
let later = Arc::clone(&later);
async move {
later.fetch_add(1, Ordering::SeqCst);
NotifAction::Continue
}
},
);
let _ctx = fake_supervisor_ctx();
let action = table
.dispatch(fake_notif(libc::SYS_openat as i32), -1)
.await;
assert!(
matches!(action, NotifAction::Defer(_)),
"dispatch must return the Defer produced by the first handler"
);
assert_eq!(
later_ran.load(Ordering::SeqCst),
0,
"Defer must short-circuit the chain like any non-Continue action"
);
}
#[test]
fn validate_extras_rejects_user_specified_blocklist() {
let policy = crate::sandbox::Sandbox::builder()
.extra_deny_syscalls(vec!["mremap".into()])
.build()
.expect("policy builds");
let result = validate_handler_syscalls_against_policy(&[libc::SYS_mremap], &policy);
assert_eq!(
result,
Err(libc::SYS_mremap),
"handler on user-specified blocklist must be rejected, naming the offending syscall"
);
}
#[tokio::test]
async fn handler_via_blanket_impl_dispatches_closures() {
use std::sync::atomic::{AtomicU64, Ordering};
let counter = Arc::new(AtomicU64::new(0));
let counter_clone = Arc::clone(&counter);
let h = move |cx: &HandlerCtx| {
let counter = Arc::clone(&counter_clone);
async move {
counter.fetch_add(1, Ordering::SeqCst);
let _ = cx.notif.pid; NotifAction::Continue
}
};
let _sup = fake_supervisor_ctx();
let notif = fake_notif(libc::SYS_openat as i32);
let cx = HandlerCtx { notif, notif_fd: -1 };
let action = h.handle(&cx).await;
assert!(matches!(action, NotifAction::Continue));
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn dispatch_invokes_struct_handler_with_persistent_self_state() {
use std::sync::atomic::{AtomicU64, Ordering};
struct StructHandler {
calls: AtomicU64,
}
impl Handler for StructHandler {
fn handle<'a>(
&'a self,
_cx: &'a HandlerCtx,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = NotifAction> + Send + 'a>> {
Box::pin(async move {
self.calls.fetch_add(1, Ordering::SeqCst);
NotifAction::Continue
})
}
}
let mut table = DispatchTable::new();
let handler = std::sync::Arc::new(StructHandler {
calls: AtomicU64::new(0),
});
table.register_arc(libc::SYS_openat, handler.clone() as std::sync::Arc<dyn Handler>);
let _sup = fake_supervisor_ctx();
let notif = fake_notif(libc::SYS_openat as i32);
for _ in 0..3 {
let action = table.dispatch(notif, -1).await;
assert!(matches!(action, NotifAction::Continue));
}
assert_eq!(
handler.calls.load(Ordering::SeqCst),
3,
"dispatch must invoke the struct-based handler on every walk"
);
}
}