What Is seccomp? Linux System Call Filtering, Explained
seccomp is a Linux kernel feature that restricts which system calls a process can make. How seccomp-bpf filters work and how containers use them.
seccomp (short for secure computing mode) is a Linux kernel feature that limits which system calls a process is allowed to make. Every meaningful thing a program does — opening a file, creating a socket, spawning a process, mapping memory — eventually goes through a system call into the kernel. seccomp lets you install a filter in front of that boundary, so a compromised or misbehaving process simply cannot ask the kernel for operations it was never supposed to need.
It’s one of the quiet layers that makes containers, browsers, and sandboxed runtimes safer than plain processes.
Why filter system calls at all?
The kernel exposes a few hundred system calls. A typical web server uses a small fraction of them. The rest — loading kernel modules, rebooting the machine, changing the system clock, manipulating kernel keyrings — are attack surface. If an attacker gets code execution inside your process, every reachable syscall is a potential path to privilege escalation, and kernel bugs in rarely used syscalls have historically been a reliable way out of a sandbox.
seccomp applies the principle of least privilege at the lowest level available: instead of trusting the application to behave, you tell the kernel what the application is allowed to request.
The two modes
Strict mode is the original design. Once enabled, the process can only call read, write, _exit, and sigreturn. Anything else kills it. That’s useful for pure computation on already-open file descriptors, but far too restrictive for most real software.
Filter mode, usually called seccomp-bpf, is what everyone uses today. The process installs a small program written in classic BPF (Berkeley Packet Filter) bytecode. The kernel runs that program on every system call the process makes, and the program returns a verdict.
The filter can inspect:
- The system call number (which syscall is being made).
- The architecture the call was made for (important, because syscall numbers differ between architectures).
- The raw argument values, as integers.
It cannot dereference pointers. If an argument is a pointer to a filename string, the filter sees the address, not the path. This is deliberate: reading user memory from the filter would create time-of-check-to-time-of-use races, where another thread changes the string after the check. Path-based policy belongs to other mechanisms such as AppArmor, SELinux, or Landlock.
What a filter can decide
Each time the filter runs, it returns an action. The common ones are:
| Action | Effect |
|---|---|
ALLOW | The syscall proceeds normally |
ERRNO | The syscall is skipped and returns a chosen error code, such as EPERM |
KILL_PROCESS / KILL_THREAD | The process or thread is terminated immediately |
TRAP | The kernel sends SIGSYS, which the process can catch and handle |
LOG | The syscall is allowed but logged, useful when building a profile |
USER_NOTIF | A supervisor process in user space is asked to decide |
TRACE | A ptrace-based tracer is notified |
Returning ERRNO is often the friendliest choice for container runtimes: a program that probes for an optional feature gets a normal “permission denied” and falls back gracefully instead of crashing.
Rules that make seccomp safe
A few properties of the design are what make it trustworthy as a security boundary:
- Filters are one-way. Once installed, a filter cannot be removed or loosened. A process can add more filters, and all of them run; the most restrictive verdict wins.
- Filters are inherited. Children created with
forkand programs started withexeccarry the parent’s filters. You can lock down a launcher and everything it starts stays locked down. no_new_privsis required for unprivileged use. Before a normal process can install a filter, it must set theno_new_privsflag, which guarantees that it can never gain privileges through setuid binaries. Without that rule, an attacker could filter syscalls in a way that confuses a privileged program into misbehaving.
How containers use seccomp
seccomp is a core part of why containers are more than just cgroups and namespaces. Namespaces change what a process can see; cgroups limit how much it can consume; seccomp limits what it can ask the kernel to do. All containers on a host share one kernel, so reducing the reachable kernel surface matters a great deal — it’s a major reason containers and VMs have different isolation properties.
Docker and other OCI runtimes ship a default seccomp profile: a JSON allowlist that permits the syscalls ordinary applications need and denies dangerous or rarely needed ones, such as those for mounting filesystems, loading kernel modules, rebooting, and manipulating kernel keyrings. You get it automatically unless you opt out. A custom profile can be supplied when starting a container:
docker run --security-opt seccomp=./my-profile.json my-image
Running with seccomp=unconfined disables filtering entirely. It’s a common quick fix when something “doesn’t work in a container,” and a common reason a later bug becomes a container escape. Combined with --privileged, it removes most of what keeps a container contained.
In Kubernetes, the setting lives in the pod or container securityContext:
securityContext:
seccompProfile:
type: RuntimeDefault
RuntimeDefault applies the container runtime’s built-in profile, Localhost points at a custom profile on the node, and Unconfined turns filtering off. Depending on cluster configuration, workloads that don’t specify anything may run unconfined, so setting RuntimeDefault explicitly — or enforcing it through pod security standards — is a cheap, high-value hardening step.
Building a custom profile
The default profile is a good baseline, but a tighter profile tailored to one application shrinks the attack surface further. The usual workflow:
- Observe. Run the application under realistic load with a profile whose default action is
LOG, or trace it with a tool that records syscalls, and collect the set it actually uses. - Allowlist. Write a profile whose default action is
ERRNOorKILL_PROCESSand that explicitly allows only the observed syscalls. - Test the edges. Error paths, signal handling, DNS resolution, and startup often use syscalls the happy path never touches. Exercise them before shipping.
- Version it. Treat the profile like code. Language runtime and libc upgrades can introduce new syscalls, and a missing one shows up as a mysterious failure.
Most applications that build their own filters use libseccomp, a library that compiles readable rules into BPF and handles architecture differences, rather than writing BPF by hand.
seccomp is also a natural fit for sandboxing AI agents and other code you don’t fully trust: run the tool inside a container with a strict profile, and even a successful prompt-injection attack has far fewer kernel interfaces to abuse.
What seccomp does not do
seccomp is a syscall gate, not a complete sandbox. It does not decide which files can be opened, does not restrict network destinations, and does not protect against bugs in the syscalls you allow. It works best layered with namespaces, dropped Linux capabilities, a non-root user, read-only filesystems, and a mandatory access control system. Think of it as narrowing the doorway, while other controls decide who walks through it and where they can go.
The takeaway
seccomp lets a Linux process — or the runtime launching it — declare which system calls it may make, enforced by the kernel through a small BPF filter that cannot be removed once installed. Containers get a sensible default profile for free; the biggest mistake is turning it off with unconfined. Keep the runtime default on everywhere, set RuntimeDefault explicitly in Kubernetes, and build tighter allowlists for high-risk workloads.
Tagged
Keep reading
Chisato · · 4 min read What Is a Container Escape?
A container escape is when code running inside a container breaks out to access the host system, defeating the isolation containers are meant to provide.
Chisato · · 5 min read Code Signing Explained: How Software Proves Its Origin
Code signing attaches a digital signature to software so users can verify who published it and that it hasn't been altered. How it works and its limits.
The Lycoris Team · · 5 min read Dockerfile CMD vs ENTRYPOINT: Differences and When to Use
CMD sets a container's default command or arguments; ENTRYPOINT sets the fixed executable. How CMD and ENTRYPOINT interact, exec vs shell form, and PID 1.