Skip to content
LinuxDeep Dive Published Updated 3 min readViews unavailable

Seccomp Filters on Linux: Reducing the System-Call Surface Without Creating a Sandbox Myth

A precise guide to Linux seccomp-BPF actions, filter layering, argument limits, no_new_privs, observability, and the controls a real sandbox still needs.

Linux seccomp filters restrict which system calls a thread may make. They are a powerful way to reduce kernel attack surface after a program has initialized, but they do not virtualize files, hide processes, mediate arbitrary pathnames, or repair a process that already has dangerous file descriptors. Calling a seccomp profile “the sandbox” hides the rest of the security boundary.

The filter sees a narrow record

Seccomp filtering runs classic BPF-style logic over struct seccomp_data: the system-call number, architecture, instruction pointer, and six raw argument values. It cannot safely dereference a userspace pointer to inspect a pathname because that memory could change after the check. Path policy belongs in mechanisms such as mount namespaces, Landlock, or an LSM.

Always validate the audit architecture before interpreting the syscall number. Different ABIs can assign different numbers or calling conventions, and compatibility ABIs may exist on the same kernel. A filter that checks only a number can accidentally authorize a different operation on another architecture.

The kernel combines installed filters; later filters can make policy more restrictive but cannot usefully restore permission rejected by an earlier layer. This supports a staged design: a launcher installs a baseline, the application initializes, then a worker narrows itself further.

Actions are part of the API contract

A matching rule returns an action such as allow, log, errno, trap, user notification, kill thread, or kill process. Availability depends on the kernel. SECCOMP_RET_ERRNO is useful when software has a tested fallback; returning an arbitrary error can also send the program down a code path nobody exercised. For a policy violation that indicates a bug, killing the process is often safer than allowing partial operation.

User notification lets a supervisor decide selected calls, but it adds a privileged protocol with race and lifetime concerns. The supervisor must validate identifiers, handle task exit, avoid confused-deputy behavior, and understand that notification is not a universal syscall emulator.

Unprivileged installation normally pairs PR_SET_NO_NEW_PRIVS with seccomp(SECCOMP_SET_MODE_FILTER, ...). no_new_privs prevents a later execve() from gaining privilege through set-user-ID or file capabilities, which keeps an unprivileged filter from manipulating a more-privileged execution path.

Build policy from observed requirements, then subtract

Start with distinct program phases and architectures. Trace representative workloads in a controlled environment, but do not turn one trace directly into an allowlist: error handling, locale loading, DNS, thread creation, signals, and shutdown may exercise calls absent from a happy path. Read library and runtime requirements, test failure paths, and minimize by functional groups.

A useful rollout sequence is:

  1. Reject an intentionally forbidden harmless syscall in a test binary to prove the profile is attached.
  2. Run the real suite with logging or a controlled ERRNO action.
  3. Exercise startup, steady state, reload, degradation, and shutdown.
  4. Switch unexpected calls to the final fail-closed action.
  5. Export profile/version telemetry without leaking syscall arguments.

Avoid a giant cross-distribution syscall list that silently grants everything a future library might need. Pin the runtime environment or generate reviewed profiles per supported target. Conversely, never default-allow unknown future syscalls: kernel expansion would expand authority without a policy change.

Complete the boundary

Drop capabilities, close inherited descriptors, set resource limits, isolate mounts and networks, constrain writable paths, choose a nonprivileged identity, and apply an LSM policy where appropriate. A process allowed only read and write can still exfiltrate through a socket inherited before the filter was installed.

Finally, test on every supported architecture and kernel, verify behavior after fork, threads, and exec, and make filter-load failure fatal when confinement is required. Seccomp is successful when it removes unnecessary kernel interfaces inside a broader, observable containment design—not when its presence becomes a security label.

Related:

Sources:

Comments