Choosing sandbox profiles
Every task runs under a seccomp sandbox profile that decides which system
calls it may make. Pick it per task with sandbox_profile; tasks that do not
set one get the server’s default (normally compile_v2).
The profiles
Section titled “The profiles”| Profile | Kind | Processes and threads | Sockets | Unlisted syscalls |
|---|---|---|---|---|
compile_v2 | Allowlist | Allowed | Unix, IPv4, IPv6, route netlink (all confined to the empty network namespace) | Fail with ENOSYS |
native_v2 | Allowlist | Denied | Denied | Fail with ENOSYS |
compile_v1 | Denylist | Allowed | As compile_v2 | Allowed |
native_v1 | Denylist | Denied | Denied | Allowed |
Every profile denies the interfaces a sandboxed program has no business
using: namespace creation and joining (unshare, setns, clone with
namespace flags), mounting (mount, pivot_root, the new mount API),
kernel modules, kexec, reboot, swap, keyrings, bpf,
perf_event_open, ptrace, process_vm_readv/writev, kcmp,
userfaultfd, pidfd_getfd, handle-based opens, acct, quotactl and
syslog. clone3 and the io_uring calls are not denied but fail with
ENOSYS under every profile, so that runtimes fall back to clone and
epoll.
A denied syscall kills the process that made it with SIGSYS, which it
cannot catch. When that is the task’s own process, the result shows
outcome: policy_violation, termination_signal: 31 and exit_code: 159.
An unlisted syscall under a v2 profile is not fatal: it returns ENOSYS
(“Function not implemented”), as on a kernel too old to have it. Most
programs fall back or report an error.
Which to use
Section titled “Which to use”| Workload | Profile |
|---|---|
Compilers, build tools, make, shells | compile_v2 |
| Interpreters and managed runtimes (Python, Node.js, Java, .NET, …) | compile_v2: most start threads or helper processes |
| Go programs | compile_v2: the Go runtime always starts threads |
| Single-threaded native programs (typical C, C++, Rust submissions) | native_v2 |
| Multithreaded native programs | compile_v2 |
Use the narrowest profile that works. For a compiled submission, native_v2
means the program cannot fork, spawn threads, run a shell or open a socket,
which removes most of what a hostile program would try.
A program that starts a thread or a process under a native profile is
killed with policy_violation. If your problems allow threads, run
submissions under compile_v2.
What the server allows
Section titled “What the server allows”The operator decides which profiles requests may use:
| Variable | Default |
|---|---|
DEFAULT_SANDBOX_PROFILE | compile_v2 |
ALLOWED_SANDBOX_PROFILES | compile_v2,native_v2 |
Asking for a profile outside ALLOWED_SANDBOX_PROFILES fails the request with
422 and code profile_not_allowed. GET /info lists the default and the
allowed profiles:
"sandbox_profiles": { "default": "compile_v2", "allowed": ["compile_v2", "native_v2"] }Diagnosing
Section titled “Diagnosing”policy_violation. The task made a denied syscall. Either it is doing
something the sandbox forbids on purpose, or it started a thread or process
under a native profile. Switch to compile_v2 for the latter; the former
cannot be allowed.
Strange failures, ENOSYS, “Function not implemented”. A toolchain used a
syscall that is not on the v2 allowlist. To confirm, run the same command
under compile_v1 on an instance that allows it: if it works there, the
allowlist is missing a syscall. Find it with strace -f outside Faber and
report it. Operators can allow the v1 profiles as a stopgap; they run
anything that is not explicitly dangerous, which is weaker.
Before relying on a new image, run every language you serve through the default profile. The toolchain test script does that for each language in the Dockerfile builder catalog.
Go deeper
Section titled “Go deeper”Seccomp profiles: how the filters stack, the full denylist and argument rules, how the allowlists were traced, and how x86_64 and ARM64 differ.