Security Error Guide: seccomp Blocked Syscall (Operation Not Permitted)
Fix seccomp-blocked syscalls in containers: read the SCMP_ACT_ERRNO denial, find the missing syscall with strace and audit logs, and add it to a least-privilege profile.
- #security
- #hardening
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
A seccomp (secure computing mode) filter restricts which Linux system calls a process may make. When a confined process invokes a syscall the profile does not permit, the kernel returns EPERM (Operation not permitted) or kills the process, depending on the filter’s default action. Unlike a normal permissions failure, the file ownership and capabilities are fine — the syscall itself was blocked before it ran. This surfaces most often in containers, systemd units with SystemCallFilter=, and sandboxed runtimes.
The application error is generic, but the block is visible when you correlate it with the syscall the process attempted:
runtime error: operation not permitted
syscall: clone3 (435), errno: 1 (EPERM)
With auditd enabled and SCMP_ACT_LOG/SECCOMP_RET_LOG in play, the kernel logs the denied syscall directly:
type=SECCOMP audit(1751990400.213:842): auid=0 uid=0 gid=0 ses=4 pid=2244 comm="node" exe="/usr/bin/node" sig=0 arch=c000003e syscall=435 compat=0 ip=0x7f... code=0x50000 (errno)
The fields that matter: syscall=435 (the blocked syscall number, here clone3), comm/exe (the process), and code=0x50000 (the seccomp action — errno returns EPERM, kill terminates). arch=c000003e is x86-64, which matters because syscall numbers differ per architecture.
Symptoms
- A container or sandboxed process fails with
Operation not permittedwhile running as root with the right capabilities. - The same binary works with
--security-opt seccomp=unconfined(Docker) or when the seccomp profile is removed. type=SECCOMPlines appear in the audit log, or the process dies withSIGSYS.- A workload breaks only after a runtime, kernel, or base-image upgrade that switched to a newer syscall (e.g.,
clone3,faccessat2).
sudo ausearch -m SECCOMP -ts recent 2>/dev/null | tail -5
type=SECCOMP ... pid=2244 comm="node" syscall=435 code=0x50000
dmesg | grep -i seccomp | tail -3
[ 912.44] audit: seccomp: pid 2244 comm "node" ... syscall 435
Common Root Causes
1. A newer glibc/runtime uses a syscall the profile predates
Libraries migrate to newer syscalls (clone3, faccessat2, close_range, statx). An older seccomp profile without them blocks the call even though the default Docker profile has since been updated upstream.
sudo ausearch -m SECCOMP -ts recent | grep -oE 'syscall=[0-9]+' | sort | uniq -c
7 syscall=435
Syscall 435 is clone3 on x86-64 — a classic gap in profiles written before it existed.
2. A custom or minimal profile is missing a syscall the app legitimately needs
A hand-written least-privilege profile (default SCMP_ACT_ERRNO) allowlists too few syscalls for the workload’s real behavior.
jq '.defaultAction, (.syscalls[].names | length)' myprofile.json
"SCMP_ACT_ERRNO"
54
A default-deny profile with a short allowlist will block anything not explicitly listed.
3. systemd SystemCallFilter is too tight
A hardened unit uses SystemCallFilter=@system-service (or a narrower set) and the service needs a syscall outside that group.
systemctl show myapp.service -p SystemCallFilter
SystemCallFilter=@system-service
4. Architecture mismatch in the profile
The profile only lists the x86_64 architecture but the container also issues x32/i386 syscalls, or vice versa, so calls on the un-listed arch are denied.
jq '.architectures' myprofile.json
["SCMP_ARCH_X86_64"]
5. A required syscall was deliberately removed for hardening but is actually needed
Someone dropped ptrace, mount, keyctl, or perf_event_open for hardening, but a debugger sidecar, FUSE mount, or profiler needs it.
Diagnostic Workflow
Step 1: Confirm seccomp is the cause
Re-run the workload unconfined; if it now works, seccomp is responsible.
docker run --rm --security-opt seccomp=unconfined myimage
# if this succeeds, a seccomp profile was blocking a syscall
For a container’s current mode:
docker inspect --format '{{ .HostConfig.SecurityOpt }}' <container>
grep Seccomp /proc/<pid>/status # 2 = filter mode active
Step 2: Identify the exact blocked syscall
Use the audit log first, then strace to catch the EPERM at the failing call:
sudo ausearch -m SECCOMP -ts recent | tail
strace -f -e trace=all ./failing-binary 2>&1 | grep -i 'EPERM\|SIGSYS' | tail
clone3({...}) = -1 EPERM (Operation not permitted)
Translate the syscall number to a name for the container’s architecture:
grep -w 435 /usr/include/asm-generic/unistd.h
ausyscall x86_64 435 # -> clone3
Step 3: Check the active profile
docker info --format '{{ .SecurityOptions }}' | tr ',' '\n' | grep -i seccomp
jq '.syscalls[].names' /path/to/profile.json | grep -c clone3
If the count is 0, the syscall is not allowlisted.
Step 4: Add the syscall to the profile (least privilege)
Extend the profile’s allowlist rather than disabling seccomp:
{
"names": ["clone3", "faccessat2", "close_range"],
"action": "SCMP_ACT_ALLOW"
}
Step 5: Re-run and confirm no further denials
docker run --rm --security-opt seccomp=./profile.json myimage
sudo ausearch -m SECCOMP -ts recent | tail
Example Root Cause Analysis
A Node.js service runs fine locally but crashes on a hardened host that applies a custom seccomp profile to every container. The app exits with Operation not permitted during startup. Running as root with full capabilities changes nothing.
The audit log names the culprit:
sudo ausearch -m SECCOMP -ts recent | grep node | tail -1
type=SECCOMP ... comm="node" exe="/usr/bin/node" syscall=435 arch=c000003e code=0x50000
ausyscall x86_64 435 resolves to clone3. The custom profile was authored before the base image’s newer glibc began using clone3 for thread creation, so the call is denied with EPERM and the runtime aborts.
Fix: add clone3 (and its companions the newer glibc uses) to the profile’s allowlist and reload:
{ "names": ["clone3", "rseq", "faccessat2"], "action": "SCMP_ACT_ALLOW" }
docker run --rm --security-opt seccomp=./profile.json myimage
sudo ausearch -m SECCOMP -ts recent | tail
(no new SECCOMP denials)
The service starts with seccomp still enforcing, now with the minimal additional syscall it actually needs.
Prevention Best Practices
- Derive profiles empirically: run the workload with
SCMP_ACT_LOG(log-only) under representative load, collect every syscall from the audit log, then set the allowlist — don’t guess. - Track upstream default profiles; keep custom profiles in sync when runtimes/kernels adopt new syscalls like
clone3,faccessat2, andclose_range. - List every architecture the container actually runs (
SCMP_ARCH_X86_64plusX86andX32on amd64 hosts) to avoid arch-mismatch denials. - Prefer extending the allowlist over
seccomp=unconfined; unconfined removes the whole protection layer for one missing call. - Test seccomp changes on a canary before fleet rollout, and alert on
type=SECCOMPaudit events so denials surface before an outage. The free incident assistant can group SECCOMP denials by syscall and suggest the allowlist entry to add.
Quick Command Reference
# Is the process under a seccomp filter?
grep Seccomp /proc/<pid>/status # 2 = filter mode
# Recent seccomp denials with syscall number
sudo ausearch -m SECCOMP -ts recent | tail
dmesg | grep -i seccomp | tail
# Translate a syscall number to a name for the arch
ausyscall x86_64 435 # -> clone3
# Catch the EPERM at the failing syscall
strace -f -e trace=all ./binary 2>&1 | grep -i 'EPERM\|SIGSYS'
# Prove seccomp is the cause (temporary)
docker run --rm --security-opt seccomp=unconfined myimage
# Inspect a profile's allowlist
jq '.defaultAction, .architectures, [.syscalls[].names]|flatten' profile.json
Conclusion
A seccomp-blocked syscall shows up as a generic Operation not permitted even though ownership and capabilities are correct — the kernel stopped the syscall itself. Read the block, don’t just retry the app:
- A newer glibc/runtime uses a syscall (
clone3,faccessat2) an older profile predates. - A custom least-privilege profile’s allowlist is missing a syscall the app needs.
- A tight
SystemCallFilter=group excludes a required call. - The profile omits an architecture the container actually uses.
- A syscall was dropped for hardening that a real workload component needs.
Confirm with unconfined, identify the exact syscall via audit/strace, then extend the allowlist minimally and re-enforce — keeping the filter in place beats running unconfined.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.