Articles

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.

Chisato Chisato · · 6 min read
Padlock icon with streaking light trails

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:

ActionEffect
ALLOWThe syscall proceeds normally
ERRNOThe syscall is skipped and returns a chosen error code, such as EPERM
KILL_PROCESS / KILL_THREADThe process or thread is terminated immediately
TRAPThe kernel sends SIGSYS, which the process can catch and handle
LOGThe syscall is allowed but logged, useful when building a profile
USER_NOTIFA supervisor process in user space is asked to decide
TRACEA 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 fork and programs started with exec carry the parent’s filters. You can lock down a launcher and everything it starts stays locked down.
  • no_new_privs is required for unprivileged use. Before a normal process can install a filter, it must set the no_new_privs flag, 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:

  1. 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.
  2. Allowlist. Write a profile whose default action is ERRNO or KILL_PROCESS and that explicitly allows only the observed syscalls.
  3. Test the edges. Error paths, signal handling, DNS resolution, and startup often use syscalls the happy path never touches. Exercise them before shipping.
  4. 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.

Chisato 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.

#Security #Containers #DevOps