QUESTION
Does dropping root privilege automatically reduce a process's syscall attack surface?
UIDs and capabilities participate in kernel permission checks that decide which privileged operations are allowed. A seccomp filter evaluates the syscall number and some raw arguments, then handles the call with allow, errno, trap, kill, notify, or another action. The mechanisms complement each other but are not substitutes.
Because installing a filter can block a required open/socket syscall, acquire resources first and pass fds or use a broker. no_new_privs prevents new privilege through exec and is a prerequisite for unprivileged filter installation.
STRUCTURE
Structure diagram
Capabilities and seccomp answer different questions. Capabilities govern permission for operations; seccomp restricts syscall entry itself.
CALL PATH
Call path
Separate the initialization phase from the steady-state syscall set after restriction. A filter validates architecture and ABI, and starts from default deny while measured required calls are added.
This diagram is not for memorizing function names. Follow which return value, file descriptor, memory mapping, or wait queue is passed to the next stage.
SOURCE COORDINATES
Linux 6.18.37 LTS source locations
Go beyond the glibc function to the files where the syscall implementation meets kernel objects. Each link points to the original file at the same tag.
| File | Function / structure | What to inspect |
|---|---|---|
| kernel/seccomp.c | do_seccomp(), seccomp_run_filters() | Install a filter and choose an action at syscall entry |
| kernel/capability.c | cap_capable(), capable() | The generic path for capability permission checks |
| kernel/sys.c | prctl_set_seccomp(), set_user() | Change process attributes and credentials |
COMPLETE PROGRAM
Complete runnable example
The code below is not pseudocode with explanatory lines omitted. It is a minimal example that can be built and run as a file.
cc -std=c17 -Wall -Wextra -O2 seccomp_strict.c -o seccomp_strict01#define _GNU_SOURCE
02#include <linux/seccomp.h>
03#include <stdio.h>
04#include <sys/prctl.h>
05#include <sys/syscall.h>
06#include <unistd.h>
07
08int main(void)
09{
10 const char message[] = "strict mode allows write\n";
11 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
12 return 1;
13 if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT) < 0)
14 return 1;
15
16 write(STDOUT_FILENO, message, sizeof(message) - 1);
17 syscall(SYS_exit, 0);
18 __builtin_unreachable();
19}
CODE NOTES
Code notes
const char message[]Prepares required data before strict mode is installed. After the filter, calls such as malloc, stdio, and open may not be on the allowlist.
PR_SET_NO_NEW_PRIVSA one-way attribute preventing this thread and later children from acquiring new privilege from setuid/file capabilities through exec.
SECCOMP_MODE_STRICTA fixed mode allowing only read, write, _exit, and sigreturn. A practical service uses BPF filter mode with libseccomp.
write(STDOUT_FILENOUses a syscall on the strict allowlist. Be careful with APIs whose glibc wrappers internally call additional syscalls.
syscall(SYS_exit, 0)The glibc _exit wrapper may use exit_group, but strict mode permits only SYS_exit. The raw syscall terminates the current single thread.
DETAILS
Detailed behavior
A syscall number cannot inspect pointer contents
Classic seccomp BPF can see raw syscall-argument values, but cannot safely dereference pathname strings or mutable memory behind pointers. Combine open-path policy with preopened dirfds, namespaces, a broker, or an LSM.
For 64-bit arguments, handle endianness and word splitting correctly for each architecture.
There are several kinds of capability set
The permitted, effective, inheritable, bounding, and ambient sets participate in exec rules. Do not assume one capset call removes every future privilege path.
Record supplementary groups, securebits, user namespaces, and file capabilities as well.
Filter deployment needs an observation mode
An allowlist omission may appear only on a rare error path in a normal workload. Collect real syscalls with SECCOMP_RET_LOG, audit, and a test corpus, including per-architecture ABIs.
SIGSYS/core information includes the syscall number and architecture and helps diagnose failures.
OBJECTS
Objects and lifetimes
| Object | Creation and release | Values to inspect |
|---|---|---|
seccomp filter | Installed on a thread and synchronizable across a thread group with TSYNC; it cannot be removed | BPF program, action precedence |
credential/cap sets | Copied by fork and changed by set*id/capset/exec | effective/permitted/bounding/ambient |
pre-opened fd | Acquired before restriction and used like a capability by the service loop | access mode, CLOEXEC, owner |
FAILURE PATH
Failure conditions and common misconceptions
| Observed symptom | Likely causes | How to verify |
|---|---|---|
| SIGSYS or immediate termination | An allowlist omission or architecture mismatch | audit log, siginfo syscall/arch |
| More privilege remains than expected | Capability bounding/ambient sets or groups were not cleared | /proc/PID/status Cap*, Groups |
| Initialization fails after the filter | A required resource was opened too late | Separate the syscall timeline into phases |
LAB
Verify it yourself
- Call getpid after strict mode and inspect process termination from an isolated shell.
- Use a libseccomp filter permitting roughly read/write/exit/futex and add missing syscalls observed through strace/audit.
- Record capability sets before and after UID change in a table using capsh --print and /proc/self/status.
./seccomp_strictstrace -e trace=prctl,write,exit_group ./seccomp_strictPRIMARY REFERENCES