use std::collections::HashMap;
use std::future::Future;
use std::os::unix::io::RawFd;
use std::pin::Pin;
use std::sync::Arc;
use super::ctx::SupervisorCtx;
use super::notif::{NotifAction, NotifPolicy};
use super::state::ResourceState;
use crate::sys::structs::SeccompNotif;
use tokio::sync::Mutex;
pub type HandlerFn = Box<
dyn Fn(SeccompNotif, Arc<SupervisorCtx>, RawFd) -> Pin<Box<dyn Future<Output = NotifAction> + Send>>
+ Send
+ Sync,
>;
struct HandlerChain {
handlers: Vec<HandlerFn>,
}
pub struct DispatchTable {
chains: HashMap<i64, HandlerChain>,
}
impl DispatchTable {
pub fn new() -> Self {
Self {
chains: HashMap::new(),
}
}
pub fn register(&mut self, syscall_nr: i64, handler: HandlerFn) {
self.chains
.entry(syscall_nr)
.or_insert_with(|| HandlerChain {
handlers: Vec::new(),
})
.handlers
.push(handler);
}
pub async fn dispatch(
&self,
notif: SeccompNotif,
ctx: &Arc<SupervisorCtx>,
notif_fd: RawFd,
) -> NotifAction {
let nr = notif.data.nr as i64;
if let Some(chain) = self.chains.get(&nr) {
for handler in &chain.handlers {
let action = handler(notif, Arc::clone(ctx), notif_fd).await;
if !matches!(action, NotifAction::Continue) {
return action;
}
}
}
NotifAction::Continue
}
}
pub fn build_dispatch_table(
policy: &Arc<NotifPolicy>,
resource: &Arc<Mutex<ResourceState>>,
) -> DispatchTable {
let mut table = DispatchTable::new();
for &nr in &[libc::SYS_clone, libc::SYS_clone3, libc::SYS_vfork] {
let policy = Arc::clone(policy);
let resource = Arc::clone(resource);
table.register(nr, Box::new(move |notif, ctx, _notif_fd| {
let policy = Arc::clone(&policy);
let resource = Arc::clone(&resource);
let procfs_inner = Arc::clone(&ctx.procfs);
Box::pin(async move {
crate::resource::handle_fork(¬if, &resource, &procfs_inner, &policy).await
})
}));
}
for &nr in &[libc::SYS_wait4, libc::SYS_waitid] {
let resource = Arc::clone(resource);
table.register(nr, Box::new(move |notif, _ctx, _notif_fd| {
let resource = Arc::clone(&resource);
Box::pin(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 = Arc::clone(policy);
let resource = Arc::clone(resource);
table.register(nr, Box::new(move |notif, _ctx, _notif_fd| {
let policy = Arc::clone(&policy);
let resource = Arc::clone(&resource);
Box::pin(async move {
crate::resource::handle_memory(¬if, &resource, &policy).await
})
}));
}
}
if policy.has_net_allowlist || policy.has_http_acl {
for &nr in &[libc::SYS_connect, libc::SYS_sendto, libc::SYS_sendmsg] {
table.register(nr, Box::new(|notif, ctx, notif_fd| {
Box::pin(async move {
crate::network::handle_net(¬if, &ctx, notif_fd).await
})
}));
}
}
if policy.has_random_seed {
table.register(libc::SYS_getrandom, Box::new(|notif, ctx, notif_fd| {
Box::pin(async move {
let mut tr = ctx.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 {
table.register(libc::SYS_openat, Box::new(|notif, ctx, notif_fd| {
Box::pin(async move {
let mut tr = ctx.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, Box::new(move |notif, _ctx, notif_fd| {
Box::pin(async move {
crate::time::handle_timer(¬if, time_offset, notif_fd)
})
}));
}
}
if policy.chroot_root.is_some() {
register_chroot_handlers(&mut table, policy);
}
if policy.cow_enabled {
register_cow_handlers(&mut table);
}
{
let policy = Arc::clone(policy);
let resource = Arc::clone(resource);
table.register(libc::SYS_openat, Box::new(move |notif, ctx, notif_fd| {
let policy = Arc::clone(&policy);
let resource = Arc::clone(&resource);
let procfs_inner = Arc::clone(&ctx.procfs);
let network = Arc::clone(&ctx.network);
Box::pin(async move {
crate::procfs::handle_proc_open(¬if, &procfs_inner, &resource, &network, &policy, notif_fd).await
})
}));
}
for &nr in &[libc::SYS_getdents64, libc::SYS_getdents as i64] {
let policy = Arc::clone(policy);
table.register(nr, Box::new(move |notif, ctx, notif_fd| {
let policy = Arc::clone(&policy);
let procfs_inner = Arc::clone(&ctx.procfs);
Box::pin(async move {
crate::procfs::handle_getdents(¬if, &procfs_inner, &policy, notif_fd).await
})
}));
}
if let Some(n) = policy.num_cpus {
table.register(libc::SYS_sched_getaffinity, Box::new(move |notif, _ctx, notif_fd| {
Box::pin(async move {
crate::procfs::handle_sched_getaffinity(¬if, n, notif_fd)
})
}));
}
if let Some(ref hostname) = policy.hostname {
let hostname = hostname.clone();
let hostname2 = hostname.clone();
table.register(libc::SYS_uname, Box::new(move |notif, _ctx, notif_fd| {
let hostname = hostname.clone();
Box::pin(async move {
crate::procfs::handle_uname(¬if, &hostname, notif_fd)
})
}));
table.register(libc::SYS_openat, Box::new(move |notif, _ctx, notif_fd| {
let hostname = hostname2.clone();
Box::pin(async move {
if let Some(action) = crate::procfs::handle_hostname_open(¬if, &hostname, notif_fd) {
action
} else {
NotifAction::Continue
}
})
}));
}
if let Some(ref etc_hosts) = policy.virtual_etc_hosts {
let etc_hosts = etc_hosts.clone();
table.register(libc::SYS_openat, Box::new(move |notif, _ctx, notif_fd| {
let etc_hosts = etc_hosts.clone();
Box::pin(async move {
if let Some(action) = crate::procfs::handle_etc_hosts_open(¬if, &etc_hosts, notif_fd) {
action
} else {
NotifAction::Continue
}
})
}));
}
if policy.deterministic_dirs {
for &nr in &[libc::SYS_getdents64, libc::SYS_getdents as i64] {
table.register(nr, Box::new(|notif, ctx, notif_fd| {
let procfs_inner = Arc::clone(&ctx.procfs);
Box::pin(async move {
crate::procfs::handle_sorted_getdents(¬if, &procfs_inner, notif_fd).await
})
}));
}
}
if policy.port_remap || policy.has_net_allowlist {
table.register(libc::SYS_bind, Box::new(|notif, ctx, notif_fd| {
Box::pin(async move {
crate::port_remap::handle_bind(¬if, &ctx.network, notif_fd).await
})
}));
}
if policy.port_remap {
table.register(libc::SYS_getsockname, Box::new(|notif, ctx, notif_fd| {
Box::pin(async move {
crate::port_remap::handle_getsockname(¬if, &ctx.network, notif_fd).await
})
}));
}
table
}
fn register_chroot_handlers(table: &mut DispatchTable, policy: &Arc<NotifPolicy>) {
use crate::chroot::dispatch::ChrootCtx;
macro_rules! chroot_handler {
($policy:expr, $handler:expr) => {{
let policy = Arc::clone($policy);
let handler_fn: HandlerFn = Box::new(move |notif, ctx, notif_fd| {
let policy = Arc::clone(&policy);
Box::pin(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, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx).await
})
});
handler_fn
}};
}
macro_rules! chroot_handler_fallthrough {
($policy:expr, $handler:expr) => {{
let policy = Arc::clone($policy);
let handler_fn: HandlerFn = Box::new(move |notif, ctx, notif_fd| {
let policy = Arc::clone(&policy);
Box::pin(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, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx).await
})
});
handler_fn
}};
}
table.register(libc::SYS_openat, chroot_handler_fallthrough!(policy,
crate::chroot::dispatch::handle_chroot_open));
table.register(libc::SYS_open as i64, 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));
}
table.register(libc::SYS_unlink as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_unlink));
table.register(libc::SYS_rmdir as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_rmdir));
table.register(libc::SYS_mkdir as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_mkdir));
table.register(libc::SYS_rename as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_rename));
table.register(libc::SYS_symlink as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_symlink));
table.register(libc::SYS_link as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_link));
table.register(libc::SYS_chmod as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_chmod));
{
let policy = Arc::clone(policy);
table.register(libc::SYS_chown as i64, Box::new(move |notif, ctx, notif_fd| {
let policy = Arc::clone(&policy);
Box::pin(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, &ctx.chroot, &ctx.cow, notif_fd, &chroot_ctx, false).await
})
}));
}
{
let policy = Arc::clone(policy);
table.register(libc::SYS_lchown as i64, Box::new(move |notif, ctx, notif_fd| {
let policy = Arc::clone(&policy);
Box::pin(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, &ctx.chroot, &ctx.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));
}
table.register(libc::SYS_stat as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_stat));
table.register(libc::SYS_lstat as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_lstat));
table.register(libc::SYS_access as i64, 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));
table.register(libc::SYS_readlink as i64, chroot_handler!(policy,
crate::chroot::dispatch::handle_chroot_legacy_readlink));
for &nr in &[libc::SYS_getdents64, libc::SYS_getdents as i64] {
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) {
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,
libc::SYS_unlink as i64, libc::SYS_rmdir as i64,
libc::SYS_mkdir as i64, libc::SYS_rename as i64,
libc::SYS_symlink as i64, libc::SYS_link as i64,
libc::SYS_chmod as i64, libc::SYS_chown as i64,
libc::SYS_lchown as i64,
] {
table.register(nr, Box::new(|notif, ctx, notif_fd| {
let cow = Arc::clone(&ctx.cow);
Box::pin(async move {
crate::cow::dispatch::handle_cow_write(¬if, &cow, notif_fd).await
})
}));
}
table.register(libc::SYS_utimensat, Box::new(|notif, ctx, notif_fd| {
let cow = Arc::clone(&ctx.cow);
Box::pin(async move {
crate::cow::dispatch::handle_cow_utimensat(¬if, &cow, notif_fd).await
})
}));
for &nr in &[
libc::SYS_faccessat,
crate::cow::dispatch::SYS_FACCESSAT2,
libc::SYS_access as i64,
] {
table.register(nr, Box::new(|notif, ctx, notif_fd| {
let cow = Arc::clone(&ctx.cow);
Box::pin(async move {
crate::cow::dispatch::handle_cow_access(¬if, &cow, notif_fd).await
})
}));
}
for &nr in &[libc::SYS_openat, libc::SYS_open as i64] {
table.register(nr, Box::new(|notif, ctx, notif_fd| {
let cow = Arc::clone(&ctx.cow);
Box::pin(async move {
crate::cow::dispatch::handle_cow_open(¬if, &cow, notif_fd).await
})
}));
}
for &nr in &[
libc::SYS_newfstatat, libc::SYS_faccessat,
libc::SYS_stat as i64, libc::SYS_lstat as i64,
libc::SYS_access as i64,
] {
table.register(nr, Box::new(|notif, ctx, notif_fd| {
let cow = Arc::clone(&ctx.cow);
Box::pin(async move {
crate::cow::dispatch::handle_cow_stat(¬if, &cow, notif_fd).await
})
}));
}
table.register(libc::SYS_statx, Box::new(|notif, ctx, notif_fd| {
let cow = Arc::clone(&ctx.cow);
Box::pin(async move {
crate::cow::dispatch::handle_cow_statx(¬if, &cow, notif_fd).await
})
}));
for &nr in &[libc::SYS_readlinkat, libc::SYS_readlink as i64] {
table.register(nr, Box::new(|notif, ctx, notif_fd| {
let cow = Arc::clone(&ctx.cow);
Box::pin(async move {
crate::cow::dispatch::handle_cow_readlink(¬if, &cow, notif_fd).await
})
}));
}
for &nr in &[libc::SYS_getdents64, libc::SYS_getdents as i64] {
table.register(nr, Box::new(|notif, ctx, notif_fd| {
let cow = Arc::clone(&ctx.cow);
Box::pin(async move {
crate::cow::dispatch::handle_cow_getdents(¬if, &cow, notif_fd).await
})
}));
}
table.register(libc::SYS_chdir, Box::new(|notif, ctx, notif_fd| {
let cow = Arc::clone(&ctx.cow);
Box::pin(async move {
crate::cow::dispatch::handle_cow_chdir(¬if, &cow, notif_fd).await
})
}));
}