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::arch;
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();
let mut fork_nrs = vec![libc::SYS_clone, libc::SYS_clone3];
if let Some(vfork) = arch::SYS_VFORK {
fork_nrs.push(vfork);
}
for nr in fork_nrs {
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_fork(¬if, &resource, &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);
table.register(nr, Box::new(move |notif, ctx, _notif_fd| {
let policy = Arc::clone(&policy);
Box::pin(async move {
crate::resource::handle_memory(¬if, &ctx, &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 processes = Arc::clone(&ctx.processes);
let network = Arc::clone(&ctx.network);
Box::pin(async move {
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 = Arc::clone(policy);
table.register(nr, Box::new(move |notif, ctx, notif_fd| {
let policy = Arc::clone(&policy);
let processes = Arc::clone(&ctx.processes);
Box::pin(async move {
crate::procfs::handle_getdents(¬if, &processes, &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 {
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, Box::new(|notif, ctx, notif_fd| {
let processes = Arc::clone(&ctx.processes);
Box::pin(async move {
crate::procfs::handle_sorted_getdents(¬if, &processes, notif_fd).await
})
}));
}
}
{
table.register(libc::SYS_socket, Box::new(|notif, ctx, _fd| {
let state = Arc::clone(&ctx.netlink);
Box::pin(async move {
crate::netlink::handlers::handle_socket(¬if, &state).await
})
}));
table.register(libc::SYS_bind, Box::new(|notif, ctx, _fd| {
let state = Arc::clone(&ctx.netlink);
Box::pin(async move {
crate::netlink::handlers::handle_bind(¬if, &state).await
})
}));
table.register(libc::SYS_getsockname, Box::new(|notif, ctx, notif_fd| {
let state = Arc::clone(&ctx.netlink);
Box::pin(async move {
crate::netlink::handlers::handle_getsockname(¬if, &state, notif_fd).await
})
}));
for &nr in &[libc::SYS_recvfrom, libc::SYS_recvmsg] {
table.register(nr, Box::new(|notif, ctx, notif_fd| {
let state = Arc::clone(&ctx.netlink);
Box::pin(async move {
crate::netlink::handlers::handle_netlink_recvmsg(¬if, &state, notif_fd).await
})
}));
}
table.register(libc::SYS_close, Box::new(|notif, ctx, _fd| {
let state = Arc::clone(&ctx.netlink);
Box::pin(async move {
crate::netlink::handlers::handle_close(¬if, &state).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));
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 = Arc::clone(policy);
table.register(chown, 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
})
}));
}
if let Some(lchown) = arch::SYS_LCHOWN {
let policy = Arc::clone(policy);
table.register(lchown, 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));
}
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) {
macro_rules! cow_call {
($handler:expr) => {
Box::new(|notif, ctx, notif_fd| {
let cow = Arc::clone(&ctx.cow);
let processes = Arc::clone(&ctx.processes);
Box::pin(async move { $handler(¬if, &cow, &processes, 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));
}