Mapping dangerous syscalls like mount, ptrace, process_vm_writev, kexec_load, and userfaultfd that often become bridges for kernel exploits and container escapes. Including a table of blocking reasons, the attack vectors that exploit them, and a ready-to-use blocklist profile example.

In the previous episode 12 you wove four layers of defense: namespaces, capabilities, seccomp, and read-only root. Each layer is in place, capabilities are dropped, and the seccomp filter is installed. But let's step back one level: of the several hundred syscalls a process can call, which ones should truly be blocked?
This question isn't academic. It's where the concept of attack surface reaches the syscall level. Imagine the Linux kernel as a huge building with hundreds of doors, and each door is a syscall. Your application only needs to pass through a handful of doors — reading files, writing output, opening sockets. Thousands of other doors remain installed, locked or not, and every one of them is a potential way in for an attacker. Seccomp is a firm decision about which doors get permanently locked.
In this episode we'll dissect the syscalls attackers weaponize most often: mount, umount2, ptrace, process_vm_writev, kexec_load, userfaultfd, and perf_event_open. For each, we look at its normal function, why it's dangerous, and the attack vectors that exploit it — then weave them into a ready-to-use blocklist profile. Let's begin.
The foundation to hold onto since the early episodes: all processes on Linux share one kernel. A container isn't a virtual machine — there's no hypervisor wall between container and host. The same kernel serves both host processes and in-container processes, and any bug, however small, in one syscall is a door into the entire system.
That's why the attack surface at the syscall level matters so much. A container escape basically works like this: an attacker exploits a bug in a syscall handler — say a use-after-free bug in mount or a race condition in userfaultfd — to gain kernel memory access, then jumps out of the container namespace onto the host. Seccomp cuts this chain from the source: if the syscall is blocked, the bug in its handler is never touched.
Important
Remember the principle: seccomp doesn't fix kernel bugs — it prevents the bug from being triggered. A 0-day in the mount syscall is meaningless if mount can't be called at all. Blocking attack surfaces is a form of insurance that's actively working even before a vulnerability is known.
Here's a map of the syscalls most often used as entry points. Memorize the pattern: nearly all of them are "power user" syscalls a normal application never needs.
| Syscall | Normal function | Why it's dangerous |
|---|---|---|
mount | Mounts filesystems | The main container escape bridge; with CAP_SYS_ADMIN it can mount host filesystems |
umount2 | Unmounts filesystems | mount's partner; can unmount filesystems the system is using |
ptrace | Cross-process debugging | Can read and write another process's memory, including processes outside the container |
process_vm_writev | Writes to another process's memory | Writes directly into another process's address space without the ptrace mechanism |
kexec_load | Loads a new kernel image | Can replace the running kernel — full control over the host |
userfaultfd | Handles page faults in user space | Often used to win race conditions and build kernel exploit primitives |
perf_event_open | Monitors performance counters | Leaks kernel data and has been an information exploitation vector |
setns | Enters an existing namespace | The classic bridge for jumping into a host or another container's namespace |
mount is the syscall that mounts filesystems, and umount2 is its partner that unmounts them. In an ideal container, processes never need to call either — the runtime has already arranged the filesystems.
The danger: with CAP_SYS_ADMIN (a capability almost always dropped in episode 12), mount can mount host filesystems into the container. The most well-known scenario: mounting the host's /proc into the container in writable mode, then overwriting files on the host — or mounting a host filesystem to an accessible path, exposing the entire host system. Blocking mount and umount2 in seccomp closes this entire attack class even if a capability accidentally slips through.
ptrace is the syscall debuggers like gdb use to control another process — reading registers, reading and writing memory, stopping execution. If an attacker can call ptrace on a process outside the container, they can steal credentials, modify application logic, or inject code into host processes.
process_vm_writev is sneakier: it writes directly into another process's address space without going through the ptrace mechanism. With CAP_SYS_PTRACE or a permission gap, this syscall provides memory injection without debugging traces. Neither is ever needed by a web application or database — block both.
kexec_load loads a new kernel image into memory for a fast reboot. The impact if abused is extreme: an attacker holding enough rights could replace the running kernel with their own, giving themselves total control over the host without needing to escape the container at all.
From a defense-in-depth perspective, kexec_load is a syscall with almost no legitimate reason inside a container. Always block it.
userfaultfd gives an application control over page fault handling — a process can "stall" another thread in the middle of a memory access. This is a legitimate feature for checkpoint/restore and certain libraries, but it's a menace in an attacker's hands.
The reason: many kernel bugs are race conditions — an operation checks a condition, then uses its data, and between those two steps there's a gap. With userfaultfd, an attacker can stop the victim thread right at that gap, alter the data, and win the race — the same pattern used in TOCTOU (time-of-check to time-of-use) attacks and several famous kernel exploits. Blocking userfaultfd removes the timing primitive attackers use most.
perf_event_open opens performance counters for profiling. The data it produces — kernel addresses, counters, samples — can leak information about the kernel's memory layout that greatly helps an attacker assemble an exploit. On some kernel versions, this syscall has even been an active exploitation vector.
Production applications never call perf_event_open directly. Monitoring is done from outside the container (via an agent or node exporter), not from inside the workload. Block this syscall and you cut one source of internal information.
The three main vectors exploiting the syscalls above deserve a summary because their mindsets differ:
Kernel exploitation — an attacker triggers a bug in a syscall handler to gain arbitrary read or arbitrary write in the kernel, then uses it for root on the host. The targets are the syscalls the workload calls. Every callable syscall is a potential surface; trimming the unneeded ones directly trims the possibilities.
Container escape — an attacker jumps from inside a container to the host. The bridge is almost always a power-user syscall: mount to open host filesystems, ptrace or process_vm_writev to touch host processes, setns to enter the host namespace. Seccomp blocks these bridges one by one.
Race condition — an attacker needs timing precision. userfaultfd is the most accurate stopwatch ever made for this purpose. Without that syscall, many race exploits become far harder — even impossible — to win.
Now let's weave it into a blocklist profile. Since we're only blocking specific syscalls, this profile uses defaultAction: SCMP_ACT_ALLOW with a deny list — the same pattern as Docker's default profile, and safe to use as a defense-in-depth layer.
{
"defaultAction": "SCMP_ACT_ALLOW",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": [
"mount",
"umount2",
"ptrace",
"process_vm_writev",
"kexec_load",
"userfaultfd",
"perf_event_open",
"setns"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1
}
]
}You'll notice I added setns to the list: it's the syscall for switching namespaces, and nsenter — the tool used to enter namespaces — relies on it. It's another escape bridge worth blocking.
Note
The profile above is a deny-list, meaning it only blocks this list and allows every other syscall. It isn't a complete main policy — it's an always-on base layer. A strict, allow-list-based main policy is the topic of episode 17 (advanced filter design), after you master observation in episode 15.
An unverified filter is just hope. These two quick checks confirm the profile is truly installed and truly working:
docker run --rm \
--security-opt seccomp=./profile-blocklist.json \
alpine sh -c 'grep Seccomp /proc/self/status'docker run --rm \
--security-opt seccomp=./profile-blocklist.json \
alpine sh -c 'mount -t tmpfs tmpfs /mnt; echo exit=$?'The second test shows seccomp's power vividly: even though you run the container as root, the mount call is still denied with EPERM — because the syscall gate rejects before any rights are considered. This is why a seccomp deny-list is so valuable as a backup layer.
1. Assuming the blocklist is complete. A deny-list blocks the list you know is dangerous — it's never complete. Treat it as an additional layer, not a replacement for an allow-list.
2. Blocking syscalls the workload turns out to need. Some applications — Java, Go, Node.js — occasionally call unexpected syscalls, including some on this list under certain conditions. Always test the workload in staging with LOG mode first (episode 15) before applying a deny.
3. Copying profiles across architectures. SCMP_ARCH_X86_64 doesn't apply to an ARM64 host. Make sure the architecture list matches your platform, or use a multi-arch list.
4. Forgetting ptrace for tooling. Debuggers, profilers, and observability agents use ptrace from outside. Blocking ptrace inside a workload is fine, but don't block it on systems that must be debugged from within.
In episode 13 you mapped the attack surface at the syscall level. We dissected why mount, umount2, ptrace, process_vm_writev, kexec_load, userfaultfd, and perf_event_open are attackers' favorite targets — from container escapes via mount and setns, memory injection via ptrace and process_vm_writev, to race conditions won with userfaultfd and information leakage via perf_event_open. You also wove them into a blocklist profile and verified the filter truly rejects.
Key points to take with you:
There's one deeper follow-up question: how strong is the filter you compose, really? In the next episode 14, we'll discuss CVE awareness & security patches — three libseccomp 2.6.1 security advisories released in July 2026, proving that an apparently strong filter can be weak, and how to keep your filter-composing library current. See you then!