seccomp: filter syscalls

RuntimeDefault and curated Localhost profiles.

Advanced14 min · lesson 11 of 24

A container is just a process on the host, and the host kernel is one syscall away — a kernel bug reachable through an exotic syscall is a direct route out of the container. seccomp (secure computing mode) is the kernel feature that filters which syscalls a process may make, and it is the cheapest meaningful reduction of that surface. Container runtimes ship a sane default profile that blocks dozens of dangerous calls (mount, ptrace, kernel module loading), but Kubernetes runs pods seccomp-unconfined unless you explicitly ask for a profile.

BUILD A REAL SECCOMP ALLOWLIST
1Run underaudit profiledefaultAction SCMP_ACT_LO…2Exercise everycode pathMake the app hit all its…3Read syscallsfrom audit logSee exactly which calls…4Flip toenforceSCMP_ACT_ERRNO + allow…
Observe, don't guess. RuntimeDefault already covers most workloads without a hand-written profile.

The one-line, high-value win is RuntimeDefault: it turns the runtime’s curated filter back on for the pod, blocking the risky syscalls with near-zero chance of breaking a normal application. You can set it on a single pod, or enforce it fleet-wide, and it is exactly what the restricted Pod Security Standard requires.

pod-seccomp.yaml
apiVersion: v1
kind: Pod
metadata: { name: web, namespace: payments }
spec:
securityContext:
seccompProfile:
type: RuntimeDefault # the runtime’s curated allowlist
containers:
- name: app
image: registry.internal/web:1.4.2
# a container-level seccompProfile here would override the pod default

A custom profile lives on the node

To go tighter than the default, you ship a JSON profile to every node under the kubelet’s seccomp directory and reference it with type: Localhost and a path relative to that directory. A profile has a default action (block everything, or log everything) and a list of explicitly allowed syscalls. The kubelet looks for it at /var/lib/kubelet/seccomp/, so localhostProfile: profiles/audit.json resolves to /var/lib/kubelet/seccomp/profiles/audit.json.

/var/lib/kubelet/seccomp/profiles/audit.json
{
"defaultAction": "SCMP_ACT_LOG", // log every syscall instead of blocking
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": []
}
// referenced from the pod:
// seccompProfile: { type: Localhost, localhostProfile: profiles/audit.json }

How to build a real allowlist

You do not guess the syscalls an app needs — you observe them. Run it under an audit profile (defaultAction SCMP_ACT_LOG), exercise every code path, and read which syscalls it actually made from the audit log; then flip the default action to SCMP_ACT_ERRNO and list exactly those calls. That log-first workflow is the whole trick, and it is why the audit profile above is the starting point, not the destination.

terminal
# watch the syscalls the container makes under the audit profile
$ sudo grep -i seccomp /var/log/syslog | tail
audit: type=1326 ... comm="app" syscall=41 ... # 41 = socket, keep it
audit: type=1326 ... comm="app" syscall=105 ... # 105 = setuid, investigate
RuntimeDefault is the 80/20
Most workloads never need a hand-written profile — turning on RuntimeDefault cluster-wide blocks the dangerous syscalls with almost no risk of breaking the app. Reach for a Localhost profile only where the threat model justifies the maintenance, and remember a container-level profile overrides the pod-level one.