use syscalls::{Sysno, SysnoSet};
use crate::arch;
use crate::resolved::ResolvedSandbox;
use crate::sandbox::Sandbox;
use crate::seccomp::bpf::{jump, stmt};
use crate::sys::structs::{
AF_INET, AF_INET6, BPF_ABS, BPF_ALU, BPF_AND, BPF_JEQ, BPF_JMP, BPF_JSET, BPF_K,
BPF_LD, BPF_RET, BPF_W, CLONE_NS_FLAGS, DEFAULT_BLOCKLIST_SYSCALLS, EPERM,
OFFSET_ARGS0_LO, OFFSET_ARGS1_LO, OFFSET_ARGS2_LO, OFFSET_ARGS3_LO, OFFSET_NR,
PR_SET_DUMPABLE, PR_SET_PTRACER, PR_SET_SECUREBITS, SECCOMP_RET_ALLOW,
SECCOMP_RET_ERRNO, SIOCETHTOOL, SIOCGIFADDR, SIOCGIFBRDADDR, SIOCGIFCONF,
SIOCGIFDSTADDR, SIOCGIFFLAGS, SIOCGIFHWADDR, SIOCGIFINDEX, SIOCGIFNAME,
SIOCGIFNETMASK, SOCK_DGRAM, SOCK_RAW, SOCK_TYPE_MASK, SYSV_IPC_BLOCKLIST_SYSCALLS,
TIOCLINUX, TIOCSTI, SockFilter,
};
#[derive(Default)]
struct SyscallList {
nrs: Vec<u32>,
}
impl SyscallList {
fn with(syscalls: &[i64]) -> Self {
let mut list = Self::default();
list.extend(syscalls);
list
}
fn push(&mut self, nr: i64) {
self.nrs.push(nr as u32);
}
fn extend(&mut self, syscalls: &[i64]) {
self.nrs.extend(syscalls.iter().map(|&nr| nr as u32));
}
fn push_optional(&mut self, nr: Option<i64>) {
if let Some(nr) = nr {
self.push(nr);
}
}
fn finish(mut self) -> Vec<u32> {
self.nrs.sort_unstable();
self.nrs.dedup();
self.nrs
}
}
const BASE_NOTIF_SYSCALLS: &[i64] = &[
libc::SYS_clone,
libc::SYS_clone3,
libc::SYS_wait4,
libc::SYS_waitid,
];
const MEMORY_NOTIF_SYSCALLS: &[i64] = &[
libc::SYS_mmap,
libc::SYS_munmap,
libc::SYS_brk,
libc::SYS_mremap,
];
const NETWORK_POLICY_SYSCALLS: &[i64] = &[
libc::SYS_connect,
libc::SYS_sendto,
libc::SYS_sendmsg,
libc::SYS_sendmmsg,
libc::SYS_bind,
];
const RANDOM_NOTIF_SYSCALLS: &[i64] = &[libc::SYS_getrandom, libc::SYS_openat];
const TIME_NOTIF_SYSCALLS: &[i64] = &[
libc::SYS_clock_nanosleep,
libc::SYS_timerfd_settime,
libc::SYS_timer_settime,
libc::SYS_openat,
];
fn procfs_hosts_notif_syscalls() -> Vec<i64> {
let mut v = vec![libc::SYS_openat, arch::SYS_OPENAT2, libc::SYS_getdents64];
v.extend([arch::sys_open(), arch::sys_getdents()].into_iter().flatten());
v
}
const NETLINK_NOTIF_SYSCALLS: &[i64] = &[
libc::SYS_socket,
libc::SYS_bind,
libc::SYS_getsockname,
libc::SYS_recvfrom,
libc::SYS_recvmsg,
libc::SYS_close,
];
fn cow_path_syscalls() -> Vec<i64> {
let mut v = vec![
libc::SYS_openat,
libc::SYS_execve,
libc::SYS_execveat,
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_utimensat,
libc::SYS_newfstatat,
libc::SYS_statx,
libc::SYS_faccessat,
arch::SYS_FACCESSAT2,
libc::SYS_readlinkat,
libc::SYS_getdents64,
libc::SYS_chdir,
libc::SYS_getcwd,
];
v.extend(
[
arch::sys_open(),
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(),
arch::sys_stat(),
arch::sys_lstat(),
arch::sys_access(),
arch::sys_readlink(),
arch::sys_getdents(),
]
.into_iter()
.flatten(),
);
v
}
fn chroot_path_syscalls() -> Vec<i64> {
let mut v = vec![
libc::SYS_openat,
libc::SYS_execve,
libc::SYS_execveat,
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_newfstatat,
libc::SYS_statx,
libc::SYS_faccessat,
arch::SYS_FACCESSAT2,
libc::SYS_readlinkat,
libc::SYS_getdents64,
libc::SYS_chdir,
libc::SYS_getcwd,
libc::SYS_statfs,
libc::SYS_utimensat,
libc::SYS_getxattr,
libc::SYS_lgetxattr,
libc::SYS_setxattr,
libc::SYS_lsetxattr,
libc::SYS_listxattr,
libc::SYS_llistxattr,
libc::SYS_removexattr,
libc::SYS_lremovexattr,
];
v.extend(
[
arch::sys_open(),
arch::sys_stat(),
arch::sys_lstat(),
arch::sys_access(),
arch::sys_readlink(),
arch::sys_getdents(),
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(),
);
v
}
fn fs_denied_path_syscalls() -> Vec<i64> {
let mut v = vec![
libc::SYS_openat,
arch::SYS_OPENAT2,
libc::SYS_execve,
libc::SYS_execveat,
libc::SYS_linkat,
libc::SYS_renameat2,
];
v.extend(
[
arch::sys_open(),
arch::sys_link(),
arch::sys_rename(),
]
.into_iter()
.flatten(),
);
v
}
const POLICY_EVENT_SYSCALLS: &[i64] = &[
libc::SYS_openat,
arch::SYS_OPENAT2,
libc::SYS_connect,
libc::SYS_sendto,
libc::SYS_bind,
libc::SYS_execve,
libc::SYS_execveat,
];
const PORT_REMAP_SYSCALLS: &[i64] = &[
libc::SYS_bind,
libc::SYS_getsockname,
];
pub(crate) fn notif_syscalls(policy: &Sandbox, sandbox_name: Option<&str>) -> Vec<u32> {
let resolved = ResolvedSandbox::from_sandbox(policy, sandbox_name, &[]);
notif_syscalls_resolved(&resolved)
}
pub(crate) fn notif_syscalls_resolved(resolved: &ResolvedSandbox) -> Vec<u32> {
let features = &resolved.features;
let mut nrs = SyscallList::with(BASE_NOTIF_SYSCALLS);
nrs.push_optional(arch::sys_vfork());
if features.argv_safety_required {
nrs.push_optional(arch::sys_fork());
}
if features.memory_limit {
nrs.extend(MEMORY_NOTIF_SYSCALLS);
if features.sysv_ipc_allowed {
nrs.push(libc::SYS_shmget);
}
}
if features.network_supervision {
nrs.extend(NETWORK_POLICY_SYSCALLS);
} else if features.unix_fs_gate {
nrs.push(libc::SYS_connect);
nrs.push(libc::SYS_sendto);
nrs.push(libc::SYS_sendmsg);
nrs.push(libc::SYS_sendmmsg);
}
if features.random_seed {
nrs.extend(RANDOM_NOTIF_SYSCALLS);
}
if features.time_start {
nrs.extend(TIME_NOTIF_SYSCALLS);
}
nrs.extend(&procfs_hosts_notif_syscalls());
nrs.extend(NETLINK_NOTIF_SYSCALLS);
if features.virtual_cpu_count {
nrs.push(libc::SYS_sched_getaffinity);
}
if features.virtual_hostname {
nrs.extend(&[libc::SYS_uname, libc::SYS_openat]);
}
if features.cow {
nrs.extend(&cow_path_syscalls());
}
if features.chroot {
nrs.extend(&chroot_path_syscalls());
}
if features.fs_denies {
nrs.extend(&fs_denied_path_syscalls());
}
if features.policy_fn {
nrs.extend(POLICY_EVENT_SYSCALLS);
}
if features.port_remap {
nrs.extend(PORT_REMAP_SYSCALLS);
}
nrs.finish()
}
fn resolve_blocklist(base: &[&str], policy: &Sandbox) -> Vec<u32> {
let mut set: SysnoSet = base
.iter()
.copied()
.chain(policy.extra_deny_syscalls.iter().map(String::as_str))
.filter_map(|n| n.parse::<Sysno>().ok())
.collect();
if !policy.allows_sysv_ipc() {
for name in SYSV_IPC_BLOCKLIST_SYSCALLS {
if let Ok(sysno) = name.parse::<Sysno>() {
set.insert(sysno);
}
}
}
set.iter().map(|s| s.id() as u32).collect()
}
pub(crate) fn no_supervisor_blocklist_syscall_numbers(policy: &Sandbox) -> Vec<u32> {
use crate::sys::structs::NO_SUPERVISOR_BLOCKLIST_SYSCALLS;
resolve_blocklist(NO_SUPERVISOR_BLOCKLIST_SYSCALLS, policy)
}
pub(crate) fn blocklist_syscall_numbers(policy: &Sandbox) -> Vec<u32> {
resolve_blocklist(DEFAULT_BLOCKLIST_SYSCALLS, policy)
}
pub(crate) fn arg_filters(policy: &Sandbox) -> Vec<SockFilter> {
let resolved = ResolvedSandbox::from_sandbox(policy, None, &[]);
arg_filters_resolved(&resolved)
}
pub(crate) fn arg_filters_resolved(resolved: &ResolvedSandbox) -> Vec<SockFilter> {
let features = &resolved.features;
let ret_errno = SECCOMP_RET_ERRNO | EPERM as u32;
let nr_clone = libc::SYS_clone as u32;
let nr_ioctl = libc::SYS_ioctl as u32;
let nr_prctl = libc::SYS_prctl as u32;
let nr_socket = libc::SYS_socket as u32;
let mut insns: Vec<SockFilter> = Vec::new();
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_NR));
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, nr_clone, 0, 3));
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARGS0_LO));
insns.push(jump(BPF_JMP | BPF_JSET | BPF_K, CLONE_NS_FLAGS as u32, 0, 1));
insns.push(stmt(BPF_RET | BPF_K, ret_errno));
let dangerous_ioctls: &[u32] = &[
TIOCSTI as u32,
TIOCLINUX as u32,
SIOCGIFNAME as u32,
SIOCGIFCONF as u32,
SIOCGIFFLAGS as u32,
SIOCGIFADDR as u32,
SIOCGIFDSTADDR as u32,
SIOCGIFBRDADDR as u32,
SIOCGIFNETMASK as u32,
SIOCGIFHWADDR as u32,
SIOCGIFINDEX as u32,
SIOCETHTOOL as u32,
];
let n_ioctls = dangerous_ioctls.len();
let skip_count = (1 + n_ioctls * 2) as u8;
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_NR));
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, nr_ioctl, 0, skip_count));
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARGS1_LO));
for &cmd in dangerous_ioctls {
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, cmd, 0, 1));
insns.push(stmt(BPF_RET | BPF_K, ret_errno));
}
let dangerous_prctl_ops: &[u32] = &[PR_SET_DUMPABLE, PR_SET_SECUREBITS, PR_SET_PTRACER];
let n_ops = dangerous_prctl_ops.len();
let skip_count = (1 + n_ops * 2) as u8;
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_NR));
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, nr_prctl, 0, skip_count));
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARGS0_LO));
for &op in dangerous_prctl_ops {
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, op, 0, 1));
insns.push(stmt(BPF_RET | BPF_K, ret_errno));
}
let mut blocked_types: Vec<u32> = Vec::new();
blocked_types.push(SOCK_RAW);
if !features.udp_or_icmp_allowed && !features.net_deny {
blocked_types.push(SOCK_DGRAM);
}
if !blocked_types.is_empty() {
let n = blocked_types.len();
let after_domain = 2 + n + 1;
let skip_all = (3 + after_domain) as u8;
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_NR));
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, nr_socket, 0, skip_all));
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARGS0_LO));
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, AF_INET, 1, 0));
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, AF_INET6, 0, after_domain as u8));
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARGS1_LO));
insns.push(stmt(BPF_ALU | BPF_AND | BPF_K, SOCK_TYPE_MASK));
for (i, &sock_type) in blocked_types.iter().enumerate() {
let remaining = n - i - 1;
let jf: u8 = if remaining == 0 { 1 } else { 0 };
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, sock_type, remaining as u8, jf));
}
insns.push(stmt(BPF_RET | BPF_K, ret_errno));
}
{
let nr_wait4 = libc::SYS_wait4 as u32;
let wnohang_or_wnowait = (libc::WNOHANG | 0x0100_0000) as u32;
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_NR));
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, nr_wait4, 0, 3));
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARGS2_LO));
insns.push(jump(BPF_JMP | BPF_JSET | BPF_K, wnohang_or_wnowait, 0, 1));
insns.push(stmt(BPF_RET | BPF_K, SECCOMP_RET_ALLOW));
}
{
let nr_waitid = libc::SYS_waitid as u32;
let wnohang_or_wnowait = (libc::WNOHANG | 0x0100_0000) as u32;
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_NR));
insns.push(jump(BPF_JMP | BPF_JEQ | BPF_K, nr_waitid, 0, 3));
insns.push(stmt(BPF_LD | BPF_W | BPF_ABS, OFFSET_ARGS3_LO));
insns.push(jump(BPF_JMP | BPF_JSET | BPF_K, wnohang_or_wnowait, 0, 1));
insns.push(stmt(BPF_RET | BPF_K, SECCOMP_RET_ALLOW));
}
insns
}