Apply a seccomp-BPF syscall allowlist by default

Derived from Podman's default profile, stripped of capability-conditional
rules (we never grant capabilities), argument filters, and the explicit
EPERM block. Dangerous syscalls (mount, unshare, ptrace, bpf,
perf_event_open, io_uring_*, keyctl, kexec_*, ...) fall through to the
default ENOSYS action, which also keeps glibc's clone3 -> clone fallback
working. x86_64 and aarch64 are supported; other archs error out.

Toggle with --seccomp / --no-seccomp or seccomp = <bool> in config.
This commit is contained in:
2026-04-08 08:34:34 +02:00
parent 5f3b139457
commit 12644ae31e
11 changed files with 772 additions and 0 deletions

View File

@@ -879,3 +879,88 @@ fn mask_nonexistent_path_becomes_tmpfs() {
"tmpfs writes should not leak to host"
);
}
#[test]
fn seccomp_on_by_default_blocks_unshare() {
let output = sandbox(&[])
.args(["--", "unshare", "--user", "--map-root-user", "/bin/true"])
.output()
.expect("agent-sandbox binary failed to execute");
assert!(
!output.status.success(),
"expected unshare(2) to be blocked by default seccomp filter, but it succeeded"
);
}
#[test]
fn seccomp_off_allows_blocked_syscall() {
let output = sandbox(&["--no-seccomp"])
.args(["--", "unshare", "--user", "--map-root-user", "/bin/true"])
.output()
.expect("agent-sandbox binary failed to execute");
assert!(
output.status.success(),
"expected unshare(2) to succeed without seccomp, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn seccomp_dry_run_emits_seccomp_arg() {
let output = sandbox(&["--dry-run"])
.args(["--", "/bin/true"])
.output()
.expect("agent-sandbox binary failed to execute");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("--seccomp"),
"expected --seccomp in dry-run output, got: {stdout}"
);
}
#[test]
fn seccomp_dry_run_no_seccomp_omits_arg() {
let output = sandbox(&["--dry-run", "--no-seccomp"])
.args(["--", "/bin/true"])
.output()
.expect("agent-sandbox binary failed to execute");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!stdout.contains("--seccomp"),
"expected no --seccomp in dry-run output with --no-seccomp, got: {stdout}"
);
}
#[test]
fn seccomp_normal_workload_succeeds() {
let output = sandbox(&[])
.args(["--", "bash", "-c", "ls /etc > /dev/null && date"])
.output()
.expect("agent-sandbox binary failed to execute");
assert!(
output.status.success(),
"expected normal workload to succeed under default seccomp, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn seccomp_bash_pthread_fallback_works() {
// Verifies the ENOSYS-not-EPERM choice for clone3 doesn't break libc's
// clone3 -> clone fallback path that bash uses internally.
let output = sandbox(&[])
.args(["--", "bash", "-c", "true"])
.output()
.expect("agent-sandbox binary failed to execute");
assert!(
output.status.success(),
"expected bash to succeed under default seccomp (clone3 fallback), stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}