use mimobox_core::{SandboxError, SeccompProfile};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
struct SockFilter {
code: u16,
jt: u8,
jf: u8,
k: u32,
}
#[repr(C)]
struct SockFprog {
len: u16,
filter: *const SockFilter,
}
const BPF_LD: u16 = 0x00;
const BPF_W: u16 = 0x00;
const BPF_ABS: u16 = 0x20;
const BPF_JMP: u16 = 0x05;
const BPF_JEQ: u16 = 0x10;
const BPF_JGT: u16 = 0x20;
const BPF_JSET: u16 = 0x40;
const BPF_ALU: u16 = 0x04;
const BPF_AND: u16 = 0x50;
const BPF_K: u16 = 0x00;
const BPF_RET: u16 = 0x06;
const SECCOMP_RET_TRAP: u32 = 0x00030000;
const SECCOMP_RET_ALLOW: u32 = 0x7FFF0000;
const SECCOMP_DATA_NR: u32 = 0; const SECCOMP_DATA_ARCH: u32 = 4; const SECCOMP_DATA_ARGS_BASE: u32 = 16; const SECCOMP_DATA_ARG_SIZE: u32 = 8;
const AUDIT_ARCH_X86_64: u32 = 0xC000_003E;
const AF_UNIX: u32 = 1;
const AF_INET: u32 = 2;
const SOCK_STREAM: u32 = 1;
const SOCK_CLOEXEC: u32 = 0x80000;
const SOCK_NONBLOCK: u32 = 0x800;
const SOCK_ALLOWED_TYPE_MASK: u32 = SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK;
const BIND_MAX_ADDRLEN: u32 = 128;
const LISTEN_MAX_BACKLOG: u32 = 128;
const TCGETS: u32 = 0x5401;
const TCSETS: u32 = 0x5402;
const TCSETSW: u32 = 0x5403;
const TCSETSF: u32 = 0x5404;
const TIOCGPGRP: u32 = 0x540F;
const TIOCSPGRP: u32 = 0x5410;
const TIOCGWINSZ: u32 = 0x5413;
const TIOCSWINSZ: u32 = 0x5414;
const FIONBIO: u32 = 0x5421;
const FIONREAD: u32 = 0x541B;
const TIOCNOTTY: u32 = 0x5422;
const IOCTL_ALLOWED_REQUESTS: &[u32] = &[
TCGETS, TCSETS, TCSETSW, TCSETSF, TIOCGPGRP, TIOCSPGRP, TIOCGWINSZ, TIOCSWINSZ, FIONBIO,
FIONREAD, TIOCNOTTY,
];
const PR_CAPBSET_READ: u32 = 23;
const PRCTL_ALLOWED_OPS: &[u32] = &[PR_CAPBSET_READ];
const FUTEX_WAIT: u32 = 0;
const FUTEX_WAKE: u32 = 1;
const FUTEX_WAIT_PRIVATE: u32 = 128; const FUTEX_WAKE_PRIVATE: u32 = 129; const FUTEX_WAIT_BITSET_PRIVATE: u32 = 137; const FUTEX_ALLOWED_OPS: &[u32] = &[
FUTEX_WAIT,
FUTEX_WAKE,
FUTEX_WAIT_PRIVATE,
FUTEX_WAKE_PRIVATE,
FUTEX_WAIT_BITSET_PRIVATE,
];
const CLONE_NAMESPACE_MASK: u32 = 0x4C02_0000;
const MAP_SHARED: u32 = 0x01;
const MAP_PRIVATE: u32 = 0x02;
#[allow(dead_code)]
const MAP_SHARED_VALIDATE: u32 = 0x03;
#[allow(dead_code)]
const MAP_ANONYMOUS: u32 = 0x20;
#[allow(dead_code)]
const MAP_FIXED: u32 = 0x10;
#[allow(dead_code)]
const MAP_NORESERVE: u32 = 0x4000;
#[allow(dead_code)]
const MAP_POPULATE: u32 = 0x8000;
#[allow(dead_code)]
const MAP_STACK: u32 = 0x20000;
#[allow(dead_code)]
const MAP_HUGETLB: u32 = 0x40000;
#[allow(dead_code)]
const MAP_LOCKED: u32 = 0x2000;
const BPF_MAX_INSTRUCTIONS: usize = 4096;
const PR_SET_NO_NEW_PRIVS: i32 = 38;
const PR_SET_SECCOMP: i32 = 22;
const SECCOMP_MODE_FILTER: i32 = 2;
#[allow(dead_code)]
mod syscall_nr {
pub const READ: u32 = 0;
pub const WRITE: u32 = 1;
pub const OPEN: u32 = 2;
pub const CLOSE: u32 = 3;
pub const STAT: u32 = 4;
pub const FSTAT: u32 = 5;
pub const LSTAT: u32 = 6;
pub const POLL: u32 = 7;
pub const MMAP: u32 = 9;
pub const MPROTECT: u32 = 10;
pub const MUNMAP: u32 = 11;
pub const BRK: u32 = 12;
pub const RT_SIGACTION: u32 = 13;
pub const RT_SIGPROCMASK: u32 = 14;
pub const RT_SIGRETURN: u32 = 15;
pub const IOCTL: u32 = 16;
pub const PREAD64: u32 = 17;
pub const PWRITE64: u32 = 18;
pub const READV: u32 = 19;
pub const WRITEV: u32 = 20;
pub const ACCESS: u32 = 21;
pub const PIPE: u32 = 22;
pub const SELECT: u32 = 23;
pub const SCHED_YIELD: u32 = 24;
pub const MREMAP: u32 = 25;
pub const NANOSLEEP: u32 = 35;
pub const ALARM: u32 = 37;
pub const GETPID: u32 = 39;
pub const SENDFILE: u32 = 40;
pub const SOCKET: u32 = 41;
pub const CONNECT: u32 = 42;
pub const ACCEPT: u32 = 43;
pub const SENDTO: u32 = 44;
pub const RECVFROM: u32 = 45;
pub const SENDMSG: u32 = 46;
pub const RECVMSG: u32 = 47;
pub const SHUTDOWN: u32 = 48;
pub const BIND: u32 = 49;
pub const LISTEN: u32 = 50;
pub const GETSOCKNAME: u32 = 51;
pub const GETPEERNAME: u32 = 52;
pub const SETSOCKOPT: u32 = 54;
pub const GETSOCKOPT: u32 = 55;
pub const CLONE: u32 = 56;
pub const FORK: u32 = 57;
pub const VFORK: u32 = 58;
pub const EXECVE: u32 = 59;
pub const EXIT: u32 = 60;
pub const WAIT4: u32 = 61;
pub const UNAME: u32 = 63;
pub const FCNTL: u32 = 72;
pub const FSYNC: u32 = 74;
pub const FTRUNCATE: u32 = 77;
pub const GETDENTS: u32 = 78;
pub const GETCWD: u32 = 79;
pub const CHDIR: u32 = 80;
pub const FCHDIR: u32 = 81;
pub const RENAME: u32 = 82;
pub const MKDIR: u32 = 83;
pub const RMDIR: u32 = 84;
pub const UNLINK: u32 = 87;
pub const SYMLINK: u32 = 88;
pub const READLINK: u32 = 89;
pub const CHMOD: u32 = 90;
pub const GETUID: u32 = 102;
pub const SYSLOG: u32 = 103;
pub const GETGID: u32 = 104;
pub const SETUID: u32 = 105;
pub const SETGID: u32 = 106;
pub const GETEUID: u32 = 107;
pub const GETEGID: u32 = 108;
pub const SETPGID: u32 = 109;
pub const GETPPID: u32 = 110;
pub const GETPGRP: u32 = 111;
pub const SETSID: u32 = 112;
pub const GETGROUPS: u32 = 115;
pub const SETGROUPS: u32 = 116;
pub const SIGALTSTACK: u32 = 131;
pub const RT_SIGQUEUEINFO: u32 = 129;
pub const RT_TGSIGQUEUEINFO: u32 = 240;
pub const MADVISE: u32 = 28;
pub const DUP: u32 = 32;
pub const DUP2: u32 = 33;
pub const PAUSE: u32 = 34;
pub const ARCH_PRCTL: u32 = 158;
pub const GETXATTR: u32 = 191;
pub const LGETXATTR: u32 = 192;
pub const FGETXATTR: u32 = 193;
pub const LISTXATTR: u32 = 195;
pub const LLISTXATTR: u32 = 196;
pub const FLISTXATTR: u32 = 197;
pub const SET_TID_ADDRESS: u32 = 218;
pub const EXIT_GROUP: u32 = 231;
pub const SET_ROBUST_LIST: u32 = 273;
pub const GET_ROBUST_LIST: u32 = 274;
pub const PRLIMIT64: u32 = 302;
pub const GETRANDOM: u32 = 318;
pub const STATFS: u32 = 137;
pub const PRCTL: u32 = 157;
pub const GETDENTS64: u32 = 217;
pub const RSEQ: u32 = 334;
pub const PREADV: u32 = 296;
pub const PWRITEV: u32 = 297;
pub const FUTEX: u32 = 202;
pub const CLOCK_GETTIME: u32 = 228;
pub const CLOCK_GETRES: u32 = 229;
pub const CLOCK_NANOSLEEP: u32 = 230;
pub const TGKILL: u32 = 234;
pub const TEE: u32 = 276;
pub const SPLICE: u32 = 275;
pub const EPOLL_CREATE: u32 = 213;
pub const EPOLL_WAIT: u32 = 232;
pub const EPOLL_CTL: u32 = 233;
pub const EPOLL_CREATE1: u32 = 291;
pub const EPOLL_PWAIT: u32 = 281;
pub const TIMERFD_CREATE: u32 = 283;
pub const TIMERFD_SETTIME: u32 = 286;
pub const OPENAT: u32 = 257;
pub const MKDIRAT: u32 = 258;
pub const UNLINKAT: u32 = 263;
pub const READLINKAT: u32 = 267;
pub const FSTATAT: u32 = 262;
pub const FACCESSAT: u32 = 269;
pub const NEWFSTATAT: u32 = 262;
pub const FCHMODAT: u32 = 268;
pub const LINKAT: u32 = 265;
pub const SYMLINKAT: u32 = 266;
pub const RENAMEAT: u32 = 264;
pub const FUTIMENSAT: u32 = 280;
pub const PPOLL: u32 = 271;
pub const LSEEK: u32 = 8;
pub const SIGPROCMASK: u32 = 14;
pub const SIGPENDING: u32 = 73;
pub const KILL: u32 = 62;
pub const TKILL: u32 = 200;
pub const SIGTIMEDWAIT: u32 = 128;
pub const SIGWAITINFO: u32 = 130;
pub const PIPE2: u32 = 293;
pub const DUP3: u32 = 292;
pub const GETTID: u32 = 186;
pub const GETRLIMIT: u32 = 97;
pub const UMASK: u32 = 95;
pub const STATX: u32 = 332;
pub const FADVISE64: u32 = 221;
pub const CLONE3: u32 = 435;
pub const CLOSE_RANGE: u32 = 436;
pub const OPENAT2: u32 = 437;
pub const FACCESSAT2: u32 = 439;
}
fn essential_syscalls() -> Vec<u32> {
use syscall_nr::*;
vec![
READ,
WRITE,
CLOSE,
LSEEK,
PREAD64,
PWRITE64,
READV,
WRITEV,
PREADV,
PWRITEV,
SENDFILE,
OPEN,
OPENAT,
STAT,
FSTAT,
LSTAT,
FSTATAT,
NEWFSTATAT,
STATX,
GETXATTR,
LGETXATTR,
FGETXATTR,
LISTXATTR,
LLISTXATTR,
FLISTXATTR,
ACCESS,
FACCESSAT,
FACCESSAT2,
READLINK,
READLINKAT,
GETDENTS,
GETDENTS64,
GETCWD,
CHDIR,
FCHDIR,
DUP,
DUP2,
DUP3,
PIPE,
PIPE2,
MMAP,
MUNMAP,
MPROTECT,
BRK,
MREMAP,
MADVISE,
EXECVE,
EXIT,
EXIT_GROUP,
GETPID,
GETPPID,
GETTID,
PRCTL,
ARCH_PRCTL,
SET_TID_ADDRESS,
SET_ROBUST_LIST,
GET_ROBUST_LIST,
RT_SIGACTION,
RT_SIGPROCMASK,
RT_SIGRETURN,
SIGALTSTACK,
IOCTL,
FCNTL,
FSYNC,
FTRUNCATE,
FUTIMENSAT,
MKDIR,
RMDIR,
MKDIRAT,
UNLINK,
UNLINKAT,
RENAME,
RENAMEAT,
LINKAT,
UMASK,
GETUID,
GETGID,
GETEUID,
GETEGID,
GETGROUPS,
GETPGRP,
UNAME,
SELECT,
POLL,
PPOLL,
STATFS,
NANOSLEEP,
SCHED_YIELD,
PAUSE,
CLOCK_GETTIME,
CLOCK_GETRES,
CLOCK_NANOSLEEP,
GETRANDOM,
PRLIMIT64,
GETRLIMIT,
EPOLL_CREATE,
EPOLL_CREATE1,
EPOLL_WAIT,
EPOLL_CTL,
EPOLL_PWAIT,
FUTEX,
RSEQ,
SPLICE,
TEE,
FADVISE64,
SOCKET,
CONNECT,
]
}
pub fn fork_allowed_syscalls() -> Vec<u32> {
use syscall_nr::*;
let mut syscalls = essential_syscalls();
syscalls.extend_from_slice(&[
CLONE,
FORK,
VFORK,
WAIT4,
TIMERFD_CREATE,
TIMERFD_SETTIME,
]);
syscalls
}
fn network_syscalls() -> Vec<u32> {
use syscall_nr::*;
let mut syscalls = essential_syscalls();
syscalls.extend_from_slice(&[
SOCKET,
CONNECT,
ACCEPT,
SENDTO,
RECVFROM,
SENDMSG,
RECVMSG,
SHUTDOWN,
BIND,
LISTEN,
GETSOCKNAME,
GETPEERNAME,
SETSOCKOPT,
GETSOCKOPT,
]);
syscalls
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SeccompArgConstraint {
Socket { allow_inet: bool },
BindAddrlen,
ConnectAddrlen,
SendtoAddrlen,
RecvfromAddrlen,
ListenBacklog,
IoctlRequest,
CloneFlags,
PrctlOp,
FutexOp,
MmapFlags,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct ConstrainedSyscall {
nr: u32,
constraint: SeccompArgConstraint,
}
const fn seccomp_arg_lo_offset(index: u32) -> u32 {
SECCOMP_DATA_ARGS_BASE + index * SECCOMP_DATA_ARG_SIZE
}
const fn seccomp_arg_hi_offset(index: u32) -> u32 {
seccomp_arg_lo_offset(index) + 4
}
fn load_abs(offset: u32) -> SockFilter {
SockFilter {
code: BPF_LD | BPF_W | BPF_ABS,
jt: 0,
jf: 0,
k: offset,
}
}
fn jump_eq(k: u32, jt: u8, jf: u8) -> SockFilter {
SockFilter {
code: BPF_JMP | BPF_JEQ | BPF_K,
jt,
jf,
k,
}
}
fn jump_gt(k: u32, jt: u8, jf: u8) -> SockFilter {
SockFilter {
code: BPF_JMP | BPF_JGT | BPF_K,
jt,
jf,
k,
}
}
fn jump_set(k: u32, jt: u8, jf: u8) -> SockFilter {
SockFilter {
code: BPF_JMP | BPF_JSET | BPF_K,
jt,
jf,
k,
}
}
fn alu_and(k: u32) -> SockFilter {
SockFilter {
code: BPF_ALU | BPF_AND | BPF_K,
jt: 0,
jf: 0,
k,
}
}
fn ret(action: u32) -> SockFilter {
SockFilter {
code: BPF_RET | BPF_K,
jt: 0,
jf: 0,
k: action,
}
}
fn forward_jump_offset(from: usize, to: usize, target: &str) -> u8 {
assert!(to > from, "BPF 跳转目标 {target} 必须位于当前指令之后");
let offset = to - from - 1;
assert!(
offset <= u8::MAX as usize,
"BPF 跳转到 {target} 的偏移过大: {offset}"
);
offset as u8
}
fn arg_constraint_for_syscall(
nr: u32,
constraints: &[ConstrainedSyscall],
) -> Option<SeccompArgConstraint> {
constraints
.iter()
.find(|constraint| constraint.nr == nr)
.map(|constraint| constraint.constraint)
}
fn build_arg_constraints(profile: SeccompProfile, allowed: &[u32]) -> Vec<ConstrainedSyscall> {
let mut constraints = Vec::with_capacity(12);
let is_network_profile = matches!(
profile,
SeccompProfile::Network | SeccompProfile::NetworkWithFork
);
if allowed.contains(&syscall_nr::MMAP) {
constraints.push(ConstrainedSyscall {
nr: syscall_nr::MMAP,
constraint: SeccompArgConstraint::MmapFlags,
});
}
if allowed.contains(&syscall_nr::SOCKET) {
constraints.push(ConstrainedSyscall {
nr: syscall_nr::SOCKET,
constraint: SeccompArgConstraint::Socket {
allow_inet: is_network_profile,
},
});
}
if is_network_profile && allowed.contains(&syscall_nr::BIND) {
constraints.push(ConstrainedSyscall {
nr: syscall_nr::BIND,
constraint: SeccompArgConstraint::BindAddrlen,
});
}
if is_network_profile && allowed.contains(&syscall_nr::LISTEN) {
constraints.push(ConstrainedSyscall {
nr: syscall_nr::LISTEN,
constraint: SeccompArgConstraint::ListenBacklog,
});
}
if is_network_profile && allowed.contains(&syscall_nr::CONNECT) {
constraints.push(ConstrainedSyscall {
nr: syscall_nr::CONNECT,
constraint: SeccompArgConstraint::ConnectAddrlen,
});
}
if is_network_profile && allowed.contains(&syscall_nr::SENDTO) {
constraints.push(ConstrainedSyscall {
nr: syscall_nr::SENDTO,
constraint: SeccompArgConstraint::SendtoAddrlen,
});
}
if is_network_profile && allowed.contains(&syscall_nr::RECVFROM) {
constraints.push(ConstrainedSyscall {
nr: syscall_nr::RECVFROM,
constraint: SeccompArgConstraint::RecvfromAddrlen,
});
}
if allowed.contains(&syscall_nr::IOCTL) {
constraints.push(ConstrainedSyscall {
nr: syscall_nr::IOCTL,
constraint: SeccompArgConstraint::IoctlRequest,
});
}
if allowed.contains(&syscall_nr::PRCTL) {
constraints.push(ConstrainedSyscall {
nr: syscall_nr::PRCTL,
constraint: SeccompArgConstraint::PrctlOp,
});
}
if allowed.contains(&syscall_nr::FUTEX) {
constraints.push(ConstrainedSyscall {
nr: syscall_nr::FUTEX,
constraint: SeccompArgConstraint::FutexOp,
});
}
if matches!(
profile,
SeccompProfile::EssentialWithFork | SeccompProfile::NetworkWithFork
) && allowed.contains(&syscall_nr::CLONE)
{
constraints.push(ConstrainedSyscall {
nr: syscall_nr::CLONE,
constraint: SeccompArgConstraint::CloneFlags,
});
}
constraints
}
fn build_socket_arg_check_block(allow_inet: bool) -> Vec<SockFilter> {
let mut block = Vec::with_capacity(18);
block.push(load_abs(seccomp_arg_hi_offset(0)));
block.push(jump_eq(0, 1, 0));
block.push(ret(SECCOMP_RET_TRAP));
block.push(load_abs(seccomp_arg_lo_offset(0)));
if allow_inet {
block.push(jump_eq(AF_UNIX, 2, 0));
block.push(jump_eq(AF_INET, 1, 0));
block.push(ret(SECCOMP_RET_TRAP));
} else {
block.push(jump_eq(AF_UNIX, 1, 0));
block.push(ret(SECCOMP_RET_TRAP));
}
block.push(load_abs(seccomp_arg_hi_offset(1)));
block.push(jump_eq(0, 1, 0));
block.push(ret(SECCOMP_RET_TRAP));
block.push(load_abs(seccomp_arg_lo_offset(1)));
block.push(alu_and(!SOCK_ALLOWED_TYPE_MASK));
block.push(jump_eq(0, 1, 0));
block.push(ret(SECCOMP_RET_TRAP));
block.push(load_abs(seccomp_arg_lo_offset(1)));
block.push(alu_and(SOCK_STREAM));
block.push(jump_eq(SOCK_STREAM, 1, 0));
block.push(ret(SECCOMP_RET_TRAP));
block.push(ret(SECCOMP_RET_ALLOW));
block
}
fn build_bind_arg_check_block() -> Vec<SockFilter> {
vec![
load_abs(seccomp_arg_hi_offset(2)),
jump_eq(0, 1, 0),
ret(SECCOMP_RET_TRAP),
load_abs(seccomp_arg_lo_offset(2)),
jump_gt(BIND_MAX_ADDRLEN, 0, 1),
ret(SECCOMP_RET_TRAP),
ret(SECCOMP_RET_ALLOW),
]
}
fn build_connect_arg_check_block() -> Vec<SockFilter> {
vec![
load_abs(seccomp_arg_hi_offset(2)),
jump_eq(0, 1, 0),
ret(SECCOMP_RET_TRAP),
load_abs(seccomp_arg_lo_offset(2)),
jump_gt(BIND_MAX_ADDRLEN, 0, 1),
ret(SECCOMP_RET_TRAP),
ret(SECCOMP_RET_ALLOW),
]
}
fn build_sendto_arg_check_block() -> Vec<SockFilter> {
vec![
load_abs(seccomp_arg_hi_offset(5)),
jump_eq(0, 1, 0),
ret(SECCOMP_RET_TRAP),
load_abs(seccomp_arg_lo_offset(5)),
jump_gt(BIND_MAX_ADDRLEN, 0, 1),
ret(SECCOMP_RET_TRAP),
ret(SECCOMP_RET_ALLOW),
]
}
fn build_recvfrom_arg_check_block() -> Vec<SockFilter> {
vec![
load_abs(seccomp_arg_hi_offset(5)),
jump_eq(0, 1, 0),
ret(SECCOMP_RET_TRAP),
load_abs(seccomp_arg_lo_offset(5)),
jump_gt(BIND_MAX_ADDRLEN, 0, 1),
ret(SECCOMP_RET_TRAP),
ret(SECCOMP_RET_ALLOW),
]
}
fn build_listen_arg_check_block() -> Vec<SockFilter> {
vec![
load_abs(seccomp_arg_hi_offset(1)),
jump_eq(0, 1, 0),
ret(SECCOMP_RET_TRAP),
load_abs(seccomp_arg_lo_offset(1)),
jump_gt(LISTEN_MAX_BACKLOG, 0, 1),
ret(SECCOMP_RET_TRAP),
ret(SECCOMP_RET_ALLOW),
]
}
fn build_ioctl_arg_check_block() -> Vec<SockFilter> {
let mut block = Vec::with_capacity(IOCTL_ALLOWED_REQUESTS.len() + 5);
block.push(load_abs(seccomp_arg_hi_offset(1)));
block.push(jump_eq(0, 1, 0));
block.push(ret(SECCOMP_RET_TRAP));
block.push(load_abs(seccomp_arg_lo_offset(1)));
for (index, &request) in IOCTL_ALLOWED_REQUESTS.iter().enumerate() {
let instructions_to_skip = (IOCTL_ALLOWED_REQUESTS.len() - index) as u8;
block.push(jump_eq(request, instructions_to_skip, 0));
}
block.push(ret(SECCOMP_RET_TRAP));
block.push(ret(SECCOMP_RET_ALLOW));
block
}
fn build_prctl_arg_check_block() -> Vec<SockFilter> {
let mut block = Vec::with_capacity(PRCTL_ALLOWED_OPS.len() + 5);
block.push(load_abs(seccomp_arg_hi_offset(0)));
block.push(jump_eq(0, 1, 0));
block.push(ret(SECCOMP_RET_TRAP));
block.push(load_abs(seccomp_arg_lo_offset(0)));
for (index, &op) in PRCTL_ALLOWED_OPS.iter().enumerate() {
let instructions_to_skip = (PRCTL_ALLOWED_OPS.len() - index) as u8;
block.push(jump_eq(op, instructions_to_skip, 0));
}
block.push(ret(SECCOMP_RET_TRAP));
block.push(ret(SECCOMP_RET_ALLOW));
block
}
fn build_futex_arg_check_block() -> Vec<SockFilter> {
let mut block = Vec::with_capacity(FUTEX_ALLOWED_OPS.len() + 5);
block.push(load_abs(seccomp_arg_hi_offset(1)));
block.push(jump_eq(0, 1, 0));
block.push(ret(SECCOMP_RET_TRAP));
block.push(load_abs(seccomp_arg_lo_offset(1)));
for (index, &op) in FUTEX_ALLOWED_OPS.iter().enumerate() {
let instructions_to_skip = (FUTEX_ALLOWED_OPS.len() - index) as u8;
block.push(jump_eq(op, instructions_to_skip, 0));
}
block.push(ret(SECCOMP_RET_TRAP));
block.push(ret(SECCOMP_RET_ALLOW));
block
}
fn build_clone_arg_check_block() -> Vec<SockFilter> {
vec![
load_abs(seccomp_arg_hi_offset(0)),
jump_eq(0, 1, 0),
ret(SECCOMP_RET_TRAP),
load_abs(seccomp_arg_lo_offset(0)),
jump_set(CLONE_NAMESPACE_MASK, 0, 1),
ret(SECCOMP_RET_TRAP),
ret(SECCOMP_RET_ALLOW),
]
}
fn build_mmap_flags_arg_check_block() -> Vec<SockFilter> {
vec![
load_abs(seccomp_arg_hi_offset(3)),
jump_eq(0, 1, 0),
ret(SECCOMP_RET_TRAP),
load_abs(seccomp_arg_lo_offset(3)),
alu_and(MAP_PRIVATE | MAP_SHARED),
jump_eq(0, 1, 0),
ret(SECCOMP_RET_ALLOW),
ret(SECCOMP_RET_TRAP),
]
}
fn build_arg_check_block(constraint: SeccompArgConstraint) -> Vec<SockFilter> {
match constraint {
SeccompArgConstraint::Socket { allow_inet } => build_socket_arg_check_block(allow_inet),
SeccompArgConstraint::BindAddrlen => build_bind_arg_check_block(),
SeccompArgConstraint::ConnectAddrlen => build_connect_arg_check_block(),
SeccompArgConstraint::SendtoAddrlen => build_sendto_arg_check_block(),
SeccompArgConstraint::RecvfromAddrlen => build_recvfrom_arg_check_block(),
SeccompArgConstraint::ListenBacklog => build_listen_arg_check_block(),
SeccompArgConstraint::IoctlRequest => build_ioctl_arg_check_block(),
SeccompArgConstraint::CloneFlags => build_clone_arg_check_block(),
SeccompArgConstraint::PrctlOp => build_prctl_arg_check_block(),
SeccompArgConstraint::FutexOp => build_futex_arg_check_block(),
SeccompArgConstraint::MmapFlags => build_mmap_flags_arg_check_block(),
}
}
fn build_bpf_program(allowed: &[u32], constraints: &[ConstrainedSyscall]) -> Vec<SockFilter> {
let mut prog = Vec::with_capacity(4 + allowed.len() + constraints.len() * 18 + 2);
let mut allow_jump_indexes = Vec::new();
prog.push(load_abs(SECCOMP_DATA_ARCH));
prog.push(jump_eq(AUDIT_ARCH_X86_64, 1, 0));
prog.push(ret(SECCOMP_RET_TRAP));
prog.push(load_abs(SECCOMP_DATA_NR));
for &syscall_nr in allowed {
if let Some(constraint) = arg_constraint_for_syscall(syscall_nr, constraints) {
let block = build_arg_check_block(constraint);
assert!(
block.len() <= u8::MAX as usize,
"BPF 参数约束块过长: syscall={syscall_nr}, len={}",
block.len()
);
prog.push(jump_eq(syscall_nr, 0, block.len() as u8));
prog.extend(block);
} else {
let jump_index = prog.len();
prog.push(jump_eq(syscall_nr, 0, 0));
allow_jump_indexes.push(jump_index);
}
}
prog.push(ret(SECCOMP_RET_TRAP));
let allow_index = prog.len();
prog.push(ret(SECCOMP_RET_ALLOW));
for jump_index in allow_jump_indexes {
prog[jump_index].jt = forward_jump_offset(jump_index, allow_index, "ALLOW");
}
assert!(
prog.len() <= BPF_MAX_INSTRUCTIONS,
"BPF 程序超过 seccomp 指令上限: {} > {}",
prog.len(),
BPF_MAX_INSTRUCTIONS
);
prog
}
pub fn apply_seccomp(profile: SeccompProfile) -> Result<(), SandboxError> {
let allowed = match profile {
SeccompProfile::Essential => essential_syscalls(),
SeccompProfile::Network => network_syscalls(),
SeccompProfile::EssentialWithFork => fork_allowed_syscalls(),
SeccompProfile::NetworkWithFork => {
let mut syscalls = fork_allowed_syscalls();
use syscall_nr::*;
syscalls.extend_from_slice(&[
SOCKET,
CONNECT,
ACCEPT,
SENDTO,
RECVFROM,
SENDMSG,
RECVMSG,
SHUTDOWN,
BIND,
LISTEN,
GETSOCKNAME,
GETPEERNAME,
SETSOCKOPT,
GETSOCKOPT,
]);
syscalls
}
};
let mut allowed = allowed;
allowed.sort_unstable();
allowed.dedup();
let constraints = build_arg_constraints(profile, &allowed);
let prog = build_bpf_program(&allowed, &constraints);
let ret = unsafe { libc::prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) };
if ret < 0 {
return Err(SandboxError::SeccompFailed(
"PR_SET_NO_NEW_PRIVS failed".into(),
));
}
let fprog = SockFprog {
len: prog.len() as u16,
filter: prog.as_ptr(),
};
let ret = unsafe {
libc::prctl(
PR_SET_SECCOMP,
SECCOMP_MODE_FILTER,
&fprog as *const SockFprog as libc::c_ulong,
0,
0,
)
};
if ret < 0 {
let errno = unsafe { *libc::__errno_location() };
return Err(SandboxError::SeccompFailed(format!(
"prctl(PR_SET_SECCOMP) 失败: errno={errno}"
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::syscall_nr::{
BIND, CLONE, CLONE3, CONNECT, FUTEX, IOCTL, LISTEN, MMAP, READ, RECVFROM, RT_SIGQUEUEINFO,
RT_TGSIGQUEUEINFO, SENDTO, SETPGID, SETSID, SOCKET, TGKILL,
};
use super::{
AF_INET, AF_UNIX, AUDIT_ARCH_X86_64, BIND_MAX_ADDRLEN, BPF_ABS, BPF_ALU, BPF_AND, BPF_JEQ,
BPF_JGT, BPF_JMP, BPF_JSET, BPF_K, BPF_LD, BPF_RET, BPF_W, CLONE_NAMESPACE_MASK,
ConstrainedSyscall, FIONBIO, FUTEX_WAIT, FUTEX_WAIT_BITSET_PRIVATE, FUTEX_WAIT_PRIVATE,
FUTEX_WAKE, FUTEX_WAKE_PRIVATE, LISTEN_MAX_BACKLOG, MAP_ANONYMOUS, MAP_FIXED, MAP_PRIVATE,
MAP_SHARED, SECCOMP_DATA_ARCH, SECCOMP_DATA_ARG_SIZE, SECCOMP_DATA_ARGS_BASE,
SECCOMP_DATA_NR, SECCOMP_RET_ALLOW, SECCOMP_RET_TRAP, SOCK_CLOEXEC, SOCK_NONBLOCK,
SOCK_STREAM, SeccompArgConstraint, SockFilter, TCGETS, TIOCGPGRP, TIOCSPGRP,
build_arg_constraints, build_bpf_program, essential_syscalls, fork_allowed_syscalls,
network_syscalls,
};
use mimobox_core::SeccompProfile;
#[derive(Clone, Copy)]
struct FakeSeccompData {
nr: u32,
arch: u32,
args: [[u32; 2]; 6],
}
impl FakeSeccompData {
fn new(nr: u32) -> Self {
Self {
nr,
arch: AUDIT_ARCH_X86_64,
args: [[0; 2]; 6],
}
}
fn with_arch(mut self, arch: u32) -> Self {
self.arch = arch;
self
}
fn with_arg(mut self, index: usize, value: u64) -> Self {
self.args[index][0] = value as u32;
self.args[index][1] = (value >> 32) as u32;
self
}
}
fn load_seccomp_word(data: &FakeSeccompData, offset: u32) -> u32 {
match offset {
SECCOMP_DATA_NR => data.nr,
SECCOMP_DATA_ARCH => data.arch,
offset
if (SECCOMP_DATA_ARGS_BASE..SECCOMP_DATA_ARGS_BASE + 6 * SECCOMP_DATA_ARG_SIZE)
.contains(&offset) =>
{
let relative_offset = offset - SECCOMP_DATA_ARGS_BASE;
assert_eq!(relative_offset % 4, 0, "BPF 测试只支持 32 位对齐读取");
let arg_index = (relative_offset / SECCOMP_DATA_ARG_SIZE) as usize;
let word_index = ((relative_offset % SECCOMP_DATA_ARG_SIZE) / 4) as usize;
data.args[arg_index][word_index]
}
_ => panic!("测试 BPF 解释器不支持 offset={offset}"),
}
}
fn run_bpf(program: &[SockFilter], data: FakeSeccompData) -> u32 {
let mut accumulator = 0_u32;
let mut pc = 0_usize;
for _ in 0..program.len() * 2 {
let instruction = program
.get(pc)
.unwrap_or_else(|| panic!("BPF pc 越界: pc={pc}"));
match instruction.code {
code if code == (BPF_LD | BPF_W | BPF_ABS) => {
accumulator = load_seccomp_word(&data, instruction.k);
pc += 1;
}
code if code == (BPF_JMP | BPF_JEQ | BPF_K) => {
let offset = if accumulator == instruction.k {
instruction.jt
} else {
instruction.jf
};
pc += offset as usize + 1;
}
code if code == (BPF_JMP | BPF_JGT | BPF_K) => {
let offset = if accumulator > instruction.k {
instruction.jt
} else {
instruction.jf
};
pc += offset as usize + 1;
}
code if code == (BPF_JMP | BPF_JSET | BPF_K) => {
let offset = if accumulator & instruction.k != 0 {
instruction.jt
} else {
instruction.jf
};
pc += offset as usize + 1;
}
code if code == (BPF_ALU | BPF_AND | BPF_K) => {
accumulator &= instruction.k;
pc += 1;
}
code if code == (BPF_RET | BPF_K) => return instruction.k,
code => panic!("测试 BPF 解释器不支持 code={code:#x}"),
}
}
panic!("BPF 程序疑似死循环");
}
fn program_for_profile(profile: SeccompProfile) -> Vec<SockFilter> {
let mut allowed = match profile {
SeccompProfile::Essential => essential_syscalls(),
SeccompProfile::Network => network_syscalls(),
SeccompProfile::EssentialWithFork => fork_allowed_syscalls(),
SeccompProfile::NetworkWithFork => {
let mut syscalls = fork_allowed_syscalls();
syscalls.extend(network_syscalls());
syscalls
}
};
allowed.sort_unstable();
allowed.dedup();
let constraints = build_arg_constraints(profile, &allowed);
build_bpf_program(&allowed, &constraints)
}
#[test]
fn test_essential_profile_blocks_process_group_escape_syscalls() {
let syscalls = essential_syscalls();
assert!(
!syscalls.contains(&SETSID),
"Essential profile 不应允许 setsid"
);
assert!(
!syscalls.contains(&SETPGID),
"Essential profile 不应允许 setpgid"
);
}
#[test]
fn test_fork_allowed_profile_blocks_process_group_escape_syscalls() {
let syscalls = fork_allowed_syscalls();
assert!(
!syscalls.contains(&SETSID),
"允许 fork 的 profile 也不应允许 setsid"
);
assert!(
!syscalls.contains(&SETPGID),
"允许 fork 的 profile 也不应允许 setpgid"
);
}
#[test]
fn test_fork_allowed_profile_blocks_signal_injection_syscalls() {
let syscalls = fork_allowed_syscalls();
assert!(
!syscalls.contains(&TGKILL),
"允许 fork 的 profile 不应允许 tgkill"
);
assert!(
!syscalls.contains(&RT_SIGQUEUEINFO),
"允许 fork 的 profile 不应允许 rt_sigqueueinfo"
);
assert!(
!syscalls.contains(&RT_TGSIGQUEUEINFO),
"允许 fork 的 profile 不应允许 rt_tgsigqueueinfo"
);
}
#[test]
fn test_fork_allowed_profile_blocks_unconstrainable_clone3() {
let syscalls = fork_allowed_syscalls();
assert!(
!syscalls.contains(&CLONE3),
"clone3 的 clone_args 位于用户态指针中,经典 seccomp-bpf 无法安全约束"
);
}
#[test]
fn test_bpf_program_starts_with_arch_check() {
let program = program_for_profile(SeccompProfile::Essential);
assert_eq!(program[0].code, BPF_LD | BPF_W | BPF_ABS);
assert_eq!(program[0].k, SECCOMP_DATA_ARCH);
assert_eq!(program[1], super::jump_eq(AUDIT_ARCH_X86_64, 1, 0));
assert_eq!(program[2], super::ret(SECCOMP_RET_TRAP));
let wrong_arch = FakeSeccompData::new(READ).with_arch(0);
assert_eq!(
run_bpf(&program, wrong_arch),
SECCOMP_RET_TRAP,
"arch 不匹配时必须触发 SIGSYS"
);
}
#[test]
fn test_socket_constraint_restricts_domain_and_type() {
let program = program_for_profile(SeccompProfile::NetworkWithFork);
let inet_stream = FakeSeccompData::new(SOCKET)
.with_arg(0, AF_INET as u64)
.with_arg(1, (SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK) as u64);
assert_eq!(run_bpf(&program, inet_stream), SECCOMP_RET_ALLOW);
let unix_stream = FakeSeccompData::new(SOCKET)
.with_arg(0, AF_UNIX as u64)
.with_arg(1, SOCK_STREAM as u64);
assert_eq!(run_bpf(&program, unix_stream), SECCOMP_RET_ALLOW);
let inet6_stream = FakeSeccompData::new(SOCKET)
.with_arg(0, 10)
.with_arg(1, SOCK_STREAM as u64);
assert_eq!(run_bpf(&program, inet6_stream), SECCOMP_RET_TRAP);
let datagram = FakeSeccompData::new(SOCKET)
.with_arg(0, AF_INET as u64)
.with_arg(1, 2);
assert_eq!(run_bpf(&program, datagram), SECCOMP_RET_TRAP);
let unknown_type_flag = FakeSeccompData::new(SOCKET)
.with_arg(0, AF_INET as u64)
.with_arg(1, (SOCK_STREAM | 0x10) as u64);
assert_eq!(run_bpf(&program, unknown_type_flag), SECCOMP_RET_TRAP);
}
#[test]
fn test_non_network_socket_constraint_allows_only_unix_domain() {
let constraints = [ConstrainedSyscall {
nr: SOCKET,
constraint: SeccompArgConstraint::Socket { allow_inet: false },
}];
let program = build_bpf_program(&[SOCKET], &constraints);
let unix_stream = FakeSeccompData::new(SOCKET)
.with_arg(0, AF_UNIX as u64)
.with_arg(1, SOCK_STREAM as u64);
assert_eq!(run_bpf(&program, unix_stream), SECCOMP_RET_ALLOW);
let inet_stream = FakeSeccompData::new(SOCKET)
.with_arg(0, AF_INET as u64)
.with_arg(1, SOCK_STREAM as u64);
assert_eq!(run_bpf(&program, inet_stream), SECCOMP_RET_TRAP);
}
#[test]
fn test_bind_constraint_allows_reasonable_addrlen() {
let program = program_for_profile(SeccompProfile::Network);
let sockaddr_in = FakeSeccompData::new(BIND).with_arg(2, 16);
assert_eq!(run_bpf(&program, sockaddr_in), SECCOMP_RET_ALLOW);
let sockaddr_storage = FakeSeccompData::new(BIND).with_arg(2, BIND_MAX_ADDRLEN as u64);
assert_eq!(run_bpf(&program, sockaddr_storage), SECCOMP_RET_ALLOW);
}
#[test]
fn test_bind_constraint_blocks_oversized_addrlen() {
let program = program_for_profile(SeccompProfile::Network);
let oversized_addrlen = FakeSeccompData::new(BIND).with_arg(2, BIND_MAX_ADDRLEN as u64 + 1);
assert_eq!(run_bpf(&program, oversized_addrlen), SECCOMP_RET_TRAP);
let addrlen_256 = FakeSeccompData::new(BIND).with_arg(2, 256);
assert_eq!(run_bpf(&program, addrlen_256), SECCOMP_RET_TRAP);
}
#[test]
fn test_bind_constraint_blocks_high_bits() {
let program = program_for_profile(SeccompProfile::Network);
let high_bits_set = FakeSeccompData::new(BIND).with_arg(2, 1_u64 << 32);
assert_eq!(run_bpf(&program, high_bits_set), SECCOMP_RET_TRAP);
}
#[test]
fn test_listen_constraint_allows_reasonable_backlog() {
let program = program_for_profile(SeccompProfile::Network);
let max_backlog = FakeSeccompData::new(LISTEN).with_arg(1, LISTEN_MAX_BACKLOG as u64);
assert_eq!(run_bpf(&program, max_backlog), SECCOMP_RET_ALLOW);
}
#[test]
fn test_listen_constraint_allows_small_backlog() {
let program = program_for_profile(SeccompProfile::Network);
let small_backlog = FakeSeccompData::new(LISTEN).with_arg(1, 5);
assert_eq!(run_bpf(&program, small_backlog), SECCOMP_RET_ALLOW);
}
#[test]
fn test_listen_constraint_blocks_oversized_backlog() {
let program = program_for_profile(SeccompProfile::Network);
let oversized_backlog =
FakeSeccompData::new(LISTEN).with_arg(1, LISTEN_MAX_BACKLOG as u64 + 1);
assert_eq!(run_bpf(&program, oversized_backlog), SECCOMP_RET_TRAP);
let backlog_256 = FakeSeccompData::new(LISTEN).with_arg(1, 256);
assert_eq!(run_bpf(&program, backlog_256), SECCOMP_RET_TRAP);
}
#[test]
fn test_listen_constraint_blocks_high_bits() {
let program = program_for_profile(SeccompProfile::Network);
let high_bits_set = FakeSeccompData::new(LISTEN).with_arg(1, 1_u64 << 32);
assert_eq!(run_bpf(&program, high_bits_set), SECCOMP_RET_TRAP);
}
#[test]
fn test_ioctl_constraint_allows_only_whitelisted_requests() {
let program = program_for_profile(SeccompProfile::EssentialWithFork);
let allowed_request = FakeSeccompData::new(IOCTL).with_arg(1, TCGETS as u64);
assert_eq!(run_bpf(&program, allowed_request), SECCOMP_RET_ALLOW);
let fionbio_request = FakeSeccompData::new(IOCTL).with_arg(1, FIONBIO as u64);
assert_eq!(run_bpf(&program, fionbio_request), SECCOMP_RET_ALLOW);
let tiocgpgrp_request = FakeSeccompData::new(IOCTL).with_arg(1, TIOCGPGRP as u64);
assert_eq!(run_bpf(&program, tiocgpgrp_request), SECCOMP_RET_ALLOW);
let tiocspgrp_request = FakeSeccompData::new(IOCTL).with_arg(1, TIOCSPGRP as u64);
assert_eq!(run_bpf(&program, tiocspgrp_request), SECCOMP_RET_ALLOW);
let tiocsti_request = FakeSeccompData::new(IOCTL).with_arg(1, 0x5412);
assert_eq!(run_bpf(&program, tiocsti_request), SECCOMP_RET_TRAP);
let high_bits_set = FakeSeccompData::new(IOCTL).with_arg(1, (1_u64 << 32) | TCGETS as u64);
assert_eq!(run_bpf(&program, high_bits_set), SECCOMP_RET_TRAP);
}
#[test]
fn test_futex_constraint_allows_only_whitelisted_ops() {
let program = program_for_profile(SeccompProfile::Essential);
let futex_wait = FakeSeccompData::new(FUTEX).with_arg(1, FUTEX_WAIT as u64);
assert_eq!(run_bpf(&program, futex_wait), SECCOMP_RET_ALLOW);
let futex_wake = FakeSeccompData::new(FUTEX).with_arg(1, FUTEX_WAKE as u64);
assert_eq!(run_bpf(&program, futex_wake), SECCOMP_RET_ALLOW);
let futex_wait_private = FakeSeccompData::new(FUTEX).with_arg(1, FUTEX_WAIT_PRIVATE as u64);
assert_eq!(run_bpf(&program, futex_wait_private), SECCOMP_RET_ALLOW);
let futex_wake_private = FakeSeccompData::new(FUTEX).with_arg(1, FUTEX_WAKE_PRIVATE as u64);
assert_eq!(run_bpf(&program, futex_wake_private), SECCOMP_RET_ALLOW);
let futex_wait_bitset_private =
FakeSeccompData::new(FUTEX).with_arg(1, FUTEX_WAIT_BITSET_PRIVATE as u64);
assert_eq!(
run_bpf(&program, futex_wait_bitset_private),
SECCOMP_RET_ALLOW
);
let futex_requeue = FakeSeccompData::new(FUTEX).with_arg(1, 3);
assert_eq!(run_bpf(&program, futex_requeue), SECCOMP_RET_TRAP);
let futex_cmp_requeue = FakeSeccompData::new(FUTEX).with_arg(1, 4);
assert_eq!(run_bpf(&program, futex_cmp_requeue), SECCOMP_RET_TRAP);
let high_bits_set =
FakeSeccompData::new(FUTEX).with_arg(1, (1_u64 << 32) | FUTEX_WAIT as u64);
assert_eq!(run_bpf(&program, high_bits_set), SECCOMP_RET_TRAP);
}
#[test]
fn test_clone_constraint_blocks_namespace_flags() {
let program = program_for_profile(SeccompProfile::EssentialWithFork);
let sigchld_only = FakeSeccompData::new(CLONE).with_arg(0, 17);
assert_eq!(run_bpf(&program, sigchld_only), SECCOMP_RET_ALLOW);
let newnet = FakeSeccompData::new(CLONE).with_arg(0, 0x4000_0000);
assert_eq!(run_bpf(&program, newnet), SECCOMP_RET_TRAP);
let namespace_mask = FakeSeccompData::new(CLONE).with_arg(0, CLONE_NAMESPACE_MASK as u64);
assert_eq!(run_bpf(&program, namespace_mask), SECCOMP_RET_TRAP);
let high_bits_set = FakeSeccompData::new(CLONE).with_arg(0, 1_u64 << 32);
assert_eq!(run_bpf(&program, high_bits_set), SECCOMP_RET_TRAP);
}
#[test]
fn test_mmap_constraint_allows_safe_flags() {
let program = program_for_profile(SeccompProfile::Essential);
let private_anon =
FakeSeccompData::new(MMAP).with_arg(3, (MAP_PRIVATE | MAP_ANONYMOUS) as u64);
assert_eq!(run_bpf(&program, private_anon), SECCOMP_RET_ALLOW);
let shared_anon =
FakeSeccompData::new(MMAP).with_arg(3, (MAP_SHARED | MAP_ANONYMOUS) as u64);
assert_eq!(run_bpf(&program, shared_anon), SECCOMP_RET_ALLOW);
let private_fixed_anon = FakeSeccompData::new(MMAP)
.with_arg(3, (MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS) as u64);
assert_eq!(run_bpf(&program, private_fixed_anon), SECCOMP_RET_ALLOW);
}
#[test]
fn test_mmap_constraint_blocks_no_private_nor_shared() {
let program = program_for_profile(SeccompProfile::Essential);
let no_flags = FakeSeccompData::new(MMAP).with_arg(3, 0);
assert_eq!(run_bpf(&program, no_flags), SECCOMP_RET_TRAP);
}
#[test]
fn test_mmap_constraint_blocks_high_bits() {
let program = program_for_profile(SeccompProfile::Essential);
let high_bits = FakeSeccompData::new(MMAP).with_arg(3, (1_u64 << 32) | MAP_PRIVATE as u64);
assert_eq!(run_bpf(&program, high_bits), SECCOMP_RET_TRAP);
}
#[test]
fn test_connect_constraint_allows_reasonable_addrlen() {
let program = program_for_profile(SeccompProfile::Network);
let sockaddr_in = FakeSeccompData::new(CONNECT).with_arg(2, 16);
assert_eq!(run_bpf(&program, sockaddr_in), SECCOMP_RET_ALLOW);
let sockaddr_storage = FakeSeccompData::new(CONNECT).with_arg(2, BIND_MAX_ADDRLEN as u64);
assert_eq!(run_bpf(&program, sockaddr_storage), SECCOMP_RET_ALLOW);
}
#[test]
fn test_connect_constraint_blocks_oversized_addrlen() {
let program = program_for_profile(SeccompProfile::Network);
let oversized_addrlen =
FakeSeccompData::new(CONNECT).with_arg(2, BIND_MAX_ADDRLEN as u64 + 1);
assert_eq!(run_bpf(&program, oversized_addrlen), SECCOMP_RET_TRAP);
let addrlen_256 = FakeSeccompData::new(CONNECT).with_arg(2, 256);
assert_eq!(run_bpf(&program, addrlen_256), SECCOMP_RET_TRAP);
}
#[test]
fn test_connect_constraint_blocks_high_bits() {
let program = program_for_profile(SeccompProfile::Network);
let high_bits_set = FakeSeccompData::new(CONNECT).with_arg(2, 1_u64 << 32);
assert_eq!(run_bpf(&program, high_bits_set), SECCOMP_RET_TRAP);
}
#[test]
fn test_sendto_constraint_allows_reasonable_addrlen() {
let program = program_for_profile(SeccompProfile::Network);
let sockaddr_in = FakeSeccompData::new(SENDTO).with_arg(5, 16);
assert_eq!(run_bpf(&program, sockaddr_in), SECCOMP_RET_ALLOW);
let sockaddr_storage = FakeSeccompData::new(SENDTO).with_arg(5, BIND_MAX_ADDRLEN as u64);
assert_eq!(run_bpf(&program, sockaddr_storage), SECCOMP_RET_ALLOW);
}
#[test]
fn test_sendto_constraint_blocks_oversized_addrlen() {
let program = program_for_profile(SeccompProfile::Network);
let oversized_addrlen =
FakeSeccompData::new(SENDTO).with_arg(5, BIND_MAX_ADDRLEN as u64 + 1);
assert_eq!(run_bpf(&program, oversized_addrlen), SECCOMP_RET_TRAP);
let addrlen_256 = FakeSeccompData::new(SENDTO).with_arg(5, 256);
assert_eq!(run_bpf(&program, addrlen_256), SECCOMP_RET_TRAP);
}
#[test]
fn test_sendto_constraint_blocks_high_bits() {
let program = program_for_profile(SeccompProfile::Network);
let high_bits_set = FakeSeccompData::new(SENDTO).with_arg(5, 1_u64 << 32);
assert_eq!(run_bpf(&program, high_bits_set), SECCOMP_RET_TRAP);
}
#[test]
fn test_recvfrom_constraint_allows_reasonable_addrlen() {
let program = program_for_profile(SeccompProfile::Network);
let sockaddr_in = FakeSeccompData::new(RECVFROM).with_arg(5, 16);
assert_eq!(run_bpf(&program, sockaddr_in), SECCOMP_RET_ALLOW);
let sockaddr_storage = FakeSeccompData::new(RECVFROM).with_arg(5, BIND_MAX_ADDRLEN as u64);
assert_eq!(run_bpf(&program, sockaddr_storage), SECCOMP_RET_ALLOW);
}
#[test]
fn test_recvfrom_constraint_blocks_oversized_addrlen() {
let program = program_for_profile(SeccompProfile::Network);
let oversized_addrlen =
FakeSeccompData::new(RECVFROM).with_arg(5, BIND_MAX_ADDRLEN as u64 + 1);
assert_eq!(run_bpf(&program, oversized_addrlen), SECCOMP_RET_TRAP);
let addrlen_256 = FakeSeccompData::new(RECVFROM).with_arg(5, 256);
assert_eq!(run_bpf(&program, addrlen_256), SECCOMP_RET_TRAP);
}
#[test]
fn test_recvfrom_constraint_blocks_high_bits() {
let program = program_for_profile(SeccompProfile::Network);
let high_bits_set = FakeSeccompData::new(RECVFROM).with_arg(5, 1_u64 << 32);
assert_eq!(run_bpf(&program, high_bits_set), SECCOMP_RET_TRAP);
}
}