The System Call Vulnerability
In a standard Linux environment, an application relies on the kernel to do almost everything. If a web server needs to read a file from the hard drive, it issues an open() system call (syscall). If it needs to allocate memory, it issues an mmap() syscall. The Linux kernel has over 300 different system calls available.
This massive attack surface is a security nightmare. A simple Nginx web server only needs a few dozen syscalls to function (like read, write, accept, bind). It has absolutely no legitimate reason to execute ptrace() (used for debugging other processes) or kexec_load() (used to reboot the server). However, if a hacker exploits a vulnerability in Nginx and gains remote code execution, they can leverage those exact dangerous syscalls to escalate their privileges to root and take over the entire server.
To prevent this, Linux introduced Seccomp (Secure Computing Mode), specifically Seccomp-BPF. Seccomp allows system administrators to define strict profiles that act as a firewall for the kernel. You can explicitly whitelist the 30 syscalls Nginx needs and unconditionally block the remaining 270. If a compromised Nginx process attempts to execute a blocked syscall, the kernel will instantly and violently terminate the process with a SIGSYS kill signal before the syscall even executes.
Step 1: Understanding the Seccomp Architecture
Seccomp profiles are most commonly deployed in containerized environments (like Docker or Kubernetes), but they can be applied to standard systemd services running directly on the host.
To see if your kernel supports Seccomp (which practically all modern kernels do), check the configuration:
grep SECCOMP /boot/config-$(uname -r)
You should see CONFIG_SECCOMP=y and CONFIG_SECCOMP_FILTER=y.
Step 2: Profiling the Application (strace)
Before you can build a whitelist, you must know exactly which syscalls your application actually uses. If you guess incorrectly, you will break the application.
You can use the strace utility to trace a running application and count its syscalls. Let’s profile a simple Python script.
strace -c python3 my_app.py
The -c flag generates a summary table. When the application finishes, strace will output a list of every syscall used (e.g., mmap, read, fstat, brk). This list becomes your Seccomp whitelist.
Step 3: Creating a Seccomp Profile (JSON)
In modern environments like Docker, Seccomp profiles are defined using simple JSON documents.
Create a file named strict_profile.json:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64"
],
"syscalls": [
{
"names": [
"read",
"write",
"openat",
"close",
"fstat",
"mmap",
"mprotect",
"brk",
"rt_sigaction",
"exit_group"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
Understanding the Profile:
- defaultAction: “SCMP_ACT_ERRNO” – This is the default-deny rule. If a syscall is not in the whitelist, the kernel will block it and return a “Permission Denied” error to the application (which is gentler than instantly killing it).
- action: “SCMP_ACT_ALLOW” – This explicitly permits the specific syscalls listed in the
namesarray.
Step 4: Applying the Profile in Docker
To launch a container securely using this exact profile, you simply pass the JSON file directly to the Docker runtime using the --security-opt flag.
docker run --rm -it --security-opt seccomp=/path/to/strict_profile.json ubuntu bash
The container launches successfully because bash uses standard syscalls. However, if a hacker manages to compromise that container and attempts to run a sophisticated exploit that relies on the unshare() syscall (often used for container breakouts), the Linux kernel will intercept the request, realize unshare is missing from the JSON whitelist, and instantly block the attack at the lowest possible level of the operating system.
Applying Seccomp to Systemd Services
If you are not using Docker, you can apply Seccomp directly to standard Linux services via their Systemd unit files.
Edit the service file (e.g., sudo systemctl edit nginx.service) and add the SystemCallFilter directive:
[Service]
SystemCallFilter=@system-service @network-io
SystemCallErrorNumber=EPERM
Systemd uses predefined groups (like @network-io) to make whitelisting easier, automatically compiling them into raw BPF Seccomp instructions and locking down the service before it even boots.