advanced27 min studyLesson 346 in Engineering

Seccomp filters: inspect syscall arguments without inventing pathname knowledge

Define the data available to a Linux seccomp BPF filter, its action composition, inheritance, ABI checks, and limitations around pointed-to memory.

On this page

What you’ll understand

  • Distinguish register argument values from memory referenced by a pointer.
  • Explain architecture/ABI checks, filter composition, and thread coverage.
  • Test a narrowly stated decision fragment without claiming it is a complete sandbox.

A seccomp filter can reject a system call before the kernel performs it, reducing the interface exposed to a workload. It does not become a filesystem authorization engine just because one syscall argument points to a pathname. This lesson concerns Linux 6.12 filter-mode seccomp BPF with supported actions on the target kernel, not strict mode and not an unspecified userspace broker. The runtime, architecture, ABI, thread setup, and chosen action all affect the observable result, so a portable policy begins with an explicit execution contract.

The filter sees metadata and scalar argument values

The kernel supplies the syscall number, architecture identifier, instruction pointer, and argument values through seccomp_data. A filter can compare values such as a descriptor number, flags, or an operation selector. It cannot dereference the pathname pointer passed to openat or inspect an arbitrary structure behind another pointer. Equal pointer addresses also do not imply equal contents across calls. A rule saying that raw seccomp BPF permits only a particular path string therefore claims information the filter does not possess.

Interpret syscall numbers within the correct architecture and calling convention. Compat ABIs, multiplexed calls, argument widths, sign extension, and kernel truncation can change the meaning of the same numeric bits. Check the architecture and supported ABI path explicitly before matching a native syscall table. A descriptor-number rule is also not permanent object authorization: descriptor three can be closed and reused for a different object. A policy fragment must state whether that identity can change and what other calls the workload may perform.

D=(arch⁡,nr⁡,ip⁡,a0,…,a5)D=(\operatorname{arch},\operatorname{nr},\operatorname{ip},a_0,\ldots,a_5)
This schematic lists the metadata and six argument values available for a seccomp decision. Values are machine-level identifiers, addresses, or scalar arguments in the stated ABI, not automatically decoded strings or pointed-to structures. Actual layout and supported actions are kernel interfaces, not defined by this notation.↔ Scroll wide equations horizontally.

Installation and action precedence are part of the policy

Installing an unprivileged filter normally requires no_new_privs; the documented alternative is the appropriate capability in the relevant user namespace. Filters are inherited across allowed fork/clone and exec paths. Existing sibling threads are not automatically covered by changing only one thread; use the runtime's correct installation sequence or supported synchronization mechanism and verify its success. A setup failure must not silently fall back to running an unfiltered process merely because the application still starts.

Stacked filters are combined by documented action precedence, not by letting the newest allow override an earlier deny. Distinguish returning an errno, trapping, terminating a thread, and terminating a process. A kill action reports SIGSYS under the documented contract, which differs from a generic SIGKILL diagnosis. Userspace notification is a separate mechanism with a trusted supervisor and its own race and object-handling requirements. Checking mutable tracee memory and then continuing a syscall unchanged can introduce TOCTOU issues; it does not make ordinary BPF capable of dereferencing memory.

Adirect allow=⋂j=1kAjA_{\rm direct\ allow}=\bigcap_{j=1}^{k}A_j
For k stacked filters, A_j is the set of syscall invocations that filter j returns direct ALLOW for. An invocation directly allowed by all filters belongs to their intersection. Other return actions follow kernel precedence; this equation is not a complete model of notification or tracing behavior.↔ Scroll wide equations horizontally.

Trace representative paths without equating a trace with completeness

Build policy evidence from application behavior across startup, steady work, shutdown, error paths, architecture variants, and library changes. A trace collected only after warmup can miss allocation, dynamic loading, resolver, or crash-reporting calls. Keep a deny test for a deliberately excluded operation and check the actual resulting errno or signal. Logging can help explain a rejection but does not itself deny it. File permissions, LSM policy, namespaces, and device controls still determine resource access for calls the filter allows.

Use the probability lab only for a hypothetical coverage exercise. Reset, explicitly set p=0.10 and n=10, predict the zero-detection bar, then Play or Step. For a separate run Reset and explicitly set p=0.10 and n=20. Assume independent equal-probability detection of a missing policy path; real executions need not satisfy that assumption. The diagram neither executes syscalls nor evaluates BPF, architecture tags, pointer arguments, or action precedence. A passing sample is not proof that every permitted syscall behavior is safe.

Evaluate a fragment with a descriptor argument

Consider a decision fragment for one specified native ABI: allow write only when its first raw argument is exactly descriptor one or two; reject other write calls. This is not a complete runnable policy. Evaluate write calls using descriptors one, two, and three, plus a claimed pathname-based openat rule.

  1. The two exact scalar comparisons accept write's descriptor-one and descriptor-two cases in this fragment. Descriptor three fails the stated write predicate, regardless of the file it currently references.

  2. The fragment distinguishes two accepted descriptor values among the three test inputs. It says nothing about exit, memory management, loading, or other calls required for a complete application policy.

  3. A raw openat pointer argument cannot be compared as a pathname string by seccomp BPF. Resource pathname authorization needs an appropriate separate mechanism; a more complex trusted broker must obey its own object and race rules.

The result

Scalar syscall arguments can support exact narrow checks. Those checks neither inspect pointed-to path bytes nor grant a complete sandbox contract.

Explore the model

Interactive lab

Interactive lab · probability

Test-path coverage is an assumption

Use separate Reset → explicitly set both p and n → predict → Play or Step runs at (0.10,10) and, after Reset, (0.10,20). Success is hypothetical detection of a missed policy path. The binomial model has no syscalls, BPF program, ABI dispatch, or pointer inspection.

Test-path coverage is an assumptionThese are exact binomial probabilities for independent trials with one fixed success probability. Playback tours the distribution; it does not resample outcomes. Current readings: Mean · np: 5; Variance · np(1−p): 2.5; Total probability: 1. All parameters are adjustable with the labeled controls below.03581000.070.150.220.3Successes kP(X = k)k = 0 · P = 9.77e-4
Exact probabilityHighlighted outcomeMean np
Mean · np
5
Variance · np(1−p)
2.5
Total probability
1
t = 0 s

These are exact binomial probabilities for independent trials with one fixed success probability. Playback tours the distribution; it does not resample outcomes. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forSampled traces can inform a policy, but direct filter semantics and complementary resource controls establish what the policy actually enforces.

Check your understanding

An openat argument contains a pointer to a pathname. What can a raw seccomp BPF filter do with that pointer under its normal interface?

Try it yourself

One filter directly allows ten hypothetical invocation cases and another allows seven. Their allowed-case sets overlap in four cases. How many are directly allowed by both? If the second returns ALLOW for a case the first denies, does installation order erase the denial?

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Use intersection for the direct-ALLOW subset and the documented precedence rule for other actions.

Compare with the solution

Four cases belong to both direct-ALLOW sets. Installing another filter does not let its ALLOW override an earlier more restrictive action under the kernel's precedence rules. The exact resulting action depends on the actions involved, not merely a yes/no count. The case inventory is hypothetical and does not prove coverage of all argument values, compat ABI paths, inherited threads, or user-notification behavior. Verify policy installation and actual actions on the target kernel and runtime.

Take it with you

  • Seccomp BPF sees scalar syscall metadata, not arbitrary pointed-to contents.
  • ABI checks, thread coverage, and action precedence are essential policy semantics.
  • A syscall filter complements resource authorization; it does not replace it.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.