Learn Seccomp - Kernel Interface & prctl
Episode 16 of 23

Learn Seccomp - Kernel Interface & prctl

Dissecting seccomp's two entry points at the kernel level: prctl with PR_SET_SECCOMP and the seccomp syscall with SECCOMP_SET_MODE_FILTER. Including the no_new_privs prerequisite, additive filter chaining, filter inheritance through fork and exec, and its interaction with user namespaces.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Introduction

In episode 15 you learned to observe filters from the outside: audit logs, SECCOMP_RET_LOG, and denial-rate telemetry. All of that observes an already-installed filter. But there's a fundamental question we've been postponing: how does a filter actually reach the kernel?

The answer lies in two interfaces: prctl(2) — a multipurpose syscall that also sets process flags — and seccomp(2), a dedicated syscall born later. Both are "entry points" into the seccomp mechanism, and understanding them isn't mere trivia. These interfaces are where the constraints you must memorize come from: filters are additive, inherited by child processes, cannot be removed, and interact subtly with user namespaces.

The analogy: prctl and seccomp are two official gates for entering the seccomp building. You already know the rules inside the building — now it's time to know how to enter, and what happens to your access card when a process forks or execs. Let's begin.

Main Discussion

Two Entry Points: prctl and seccomp

prctl(PR_SET_SECCOMP, ...) is the oldest interface. Since kernel 2.6.12, prctl has been used to enable strict seccomp, and since 3.5 to install BPF filters. Old code and many simple C programs still use it.

The seccomp(2) syscall was born in kernel 3.17 as a dedicated interface. It's more expressive: it supports flags like SECCOMP_SET_MODE_FILTER and SECCOMP_SET_MODE_STRICT, plus query operations like SECCOMP_GET_ACTION_AVAIL and SECCOMP_GET_FILTER to inspect installed filters.

Aspectprctl(PR_SET_SECCOMP)seccomp(2)
Available sinceKernel 2.6.12 (strict), 3.5 (filter)Kernel 3.17
Modesstrict and filterstrict, filter, plus query operations
Extra featuresNoneSECCOMP_FILTER_FLAG_TSYNC, SECCOMP_GET_FILTER
Common usageOld code, minimal examplesModern code and libseccomp

In practice, you rarely call either directly — libseccomp picks seccomp(2) when available and falls back to prctl on old kernels. But understanding the difference helps when reading container runtime C code and when debugging.

no_new_privs: The Prerequisite and the Right Order

The kernel rule to memorize: an unprivileged process may only install a filter if the no_new_privs flag is already active — or if it holds CAP_SYS_ADMIN. This isn't a recommendation; it's a kernel check: without either condition, filter installation is rejected with EPERM.

The installation order also follows a fixed pattern, and swapping it is a classic mistake:

LinuxEnable strict mode via prctl
#define _GNU_SOURCE
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <stdio.h>
#include <unistd.h>
 
int main(void) {
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
        perror("no_new_privs");
        return 1;
    }
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT) != 0) {
        perror("seccomp");
        return 1;
    }
    printf("strict mode active, seccomp installed\n");
    return 0;
}
no_new_privs first, then the filter — the correct order

Note the order above: PR_SET_NO_NEW_PRIVS is called before PR_SET_SECCOMP. Strict mode only allows read, write, _exit, and sigreturn — far too narrow for modern applications, but perfect for illustrating how the interface works. In real code, the mode used is almost always a BPF filter via libseccomp, which in turn calls seccomp(2).

Important

no_new_privs isn't just a formality to get a filter accepted — it's a safety net preventing the filter from being undone by privilege escalation. A process that sets this flag, and all its descendants, will never gain new privileges from setuid binaries, file capabilities, or ambient capabilities. Always set it, in the first position.

Filter Chaining: The Additive Nature You Can't Ignore

Since kernel 4.14, a process can install more than one filter. Every seccomp(SECCOMP_SET_MODE_FILTER, ...) call adds one more filter to the list. When a syscall is made, the kernel executes all filters in sequence — and the most restrictive decision wins.

This property is called additive. Imagine three security officers at the same door: you must pass all three, and one officer refusing means the whole group is refused. Practically:

  • A syscall is allowed only if all filters allow it.
  • A deny from any one filter immediately kills that syscall, no matter what another filter says.
  • Installation order doesn't change the outcome — what matters is that the entire filter list is evaluated.

This opens up a composition pattern we'll leverage in episode 17: a base filter blocked everywhere (e.g. deny mount), plus per-application filters narrowing syscalls to the workload. Both run side by side, reinforcing each other, without conflict.

Kernel Constraints You Must Memorize

There are several constraints that define filter behavior across a process's entire lifecycle:

Filters are inherited by children on fork and clone. Every descendant process inherits the parent's entire filter list. This is automatic and can't be turned off — no need to reinstall filters in every child.

Filters survive execve. Unlike other process flags that reset on exec, seccomp filters stay installed. A process can't escape its filter by executing another program.

Filters cannot be removed. Once installed, there's no official operation to remove a filter. The only "reset" is the process exiting. (The SECCOMP_GET_FILTER operation is read-only, not a delete.)

Threads follow the process. In a multithreaded process, the filter applies to all threads. The SECCOMP_FILTER_FLAG_TSYNC flag ensures a new filter is synchronized to all threads at once when installed.

The consequence is felt clearly in containers: once the runtime installs a filter, every process inside the container — applications, workers, any subprocess — lives under the same filter. That's why filter design must account for all of an application's operating modes, not just the main path.

User Namespaces and Their Interaction

The interaction between seccomp and user namespaces is a frequent source of confusion. Inside a user namespace, an unprivileged process can consider itself root — with an important consequence:

  • Root inside a user namespace still needs no_new_privs (or CAP_SYS_ADMIN in that namespace) to install a filter.
  • An installed filter remains in effect across namespaces. Moving a process to another namespace — via setns or unshare — doesn't remove an already-installed filter.
  • Container runtimes exploit this combination: root inside a container (actually unprivileged on the host) can still install a filter, and that filter keeps following the process wherever it moves.

This is one reason no_new_privs has become the gold standard: it makes seccomp work consistently across all namespace scenarios, without depending on whoever happens to hold CAP_SYS_ADMIN.

Practice: Checking a Process's Seccomp Status

The fastest way to confirm a filter is installed from outside is reading a field in /proc:

Check the seccomp status from outside
grep Seccomp /proc/self/status
Seccomp: 2 means filter mode is active, 0 means no filter

The value 2 in the Seccomp field means filter mode is active; 1 means strict mode; 0 means no filter. For another process, replace self with the PID — for example grep Seccomp /proc/1234/status. This check belongs in the production filter verification checklist (discussed in episode 19).

Common Mistakes

1. Installing a filter before no_new_privs. The call is rejected with EPERM on an unprivileged process. Order it: flag first, filter then.

2. Thinking a filter can be removed or reset on exec. Filters survive execve and have no removal operation. Design the filter once, and make sure it's right from the start.

3. Forgetting the additive nature. Adding a new filter never "replaces" an old one — both are evaluated together, and a deny from anywhere wins. Expand filters carefully.

4. Using strict mode for real applications. Strict mode only allows four syscalls. For modern applications, use filter mode with an allow-list measured from the workload profile.

Conclusion

In episode 16 you understood seccomp's lowest layer: the kernel interface. prctl(PR_SET_SECCOMP) as the classic entry point and the seccomp(2) syscall as the modern interface, the no_new_privs prerequisite that must be set first, the additive filter nature inherited through fork and exec, the constraint that filters can't be removed, and the interaction with user namespaces that lets container runtimes work the way they do.

Key points to take with you:

  • prctl and seccomp(2) are two interfaces; libseccomp picks whichever is available.
  • no_new_privs is both a prerequisite and a safeguard — set it first.
  • Filters are additive, inherited by descendants, survive exec, and can't be removed.
  • User namespace interaction lets container root still install a binding filter.

Now you know the interfaces and their constraints. The next question is the art of designing them: how to compose a precise, efficient, and secure filter for a specific workload. In the next episode 17, we'll discuss advanced filter design — BPF optimization, precise argument rules, layered filter composition, and minimal syscall set patterns measured with strace. See you then!

Learn Seccomp - Kernel Interface & prctl | Learn Seccomp