Seccomp, Capabilities & Syscall Filtering
Chapter 18: Seccomp, Capabilities & Syscall Filtering
Section titled “Chapter 18: Seccomp, Capabilities & Syscall Filtering”Learning Objectives
Section titled “Learning Objectives”- Understand seccomp and how it restricts system calls
- Know how Docker and Kubernetes use seccomp profiles
- Write custom seccomp profiles
- Understand the relationship between capabilities and seccomp
What is this? (Beginner On-Ramp)
Section titled “What is this? (Beginner On-Ramp)”PAM (Pluggable Authentication Modules) handles how users log in, and Capabilities divide up ‘root’ powers. Instead of giving a program full root access (which is dangerous), you can give it just one specific Capability, like the ability to open network ports, keeping the system much safer.
18.1 Seccomp: System Call Filtering
Section titled “18.1 Seccomp: System Call Filtering”seccomp (Secure Computing Mode) filters which system calls a process can make. Even if an attacker achieves code execution, they’re limited to a small set of syscalls.
Seccomp Architecture ─────────────────────
Application calls: open("/etc/shadow", O_RDONLY) │ ▼ glibc syscall: openat(AT_FDCWD, "/etc/shadow", O_RDONLY) │ ▼ Kernel seccomp filter ┌─────────────────────────────────────────────────────┐ │ Is 'openat' in the allowed syscall list? │ │ │ │ YES → syscall proceeds │ │ NO → SIGKILL or SIGSYS sent to process │ │ (depends on filter action) │ └─────────────────────────────────────────────────────┘
Seccomp uses BPF (Berkeley Packet Filter) programs to evaluate syscalls — extremely efficient.Seccomp Modes
Section titled “Seccomp Modes” seccomp Modes: ─────────────── SECCOMP_MODE_DISABLED: No filtering (default)
SECCOMP_MODE_STRICT: Only allow read(), write(), exit(), sigreturn() - Extremely restrictive, almost unusable
SECCOMP_MODE_FILTER: Use BPF filter to allow/deny specific syscalls - What Docker, Kubernetes, and sandboxes use18.2 Docker’s Default Seccomp Profile
Section titled “18.2 Docker’s Default Seccomp Profile”Docker ships with a default seccomp profile that blocks ~44 dangerous syscalls:
// /etc/docker/seccomp.json (simplified excerpt){ "defaultAction": "SCMP_ACT_ALLOW", "syscalls": [ { "names": [ "keyctl", // Access kernel keyrings "add_key", // Add key to kernel keyring "request_key", // Request key from keyring "ptrace", // Trace other processes "mbind", // NUMA memory policy "move_pages", // Move pages between NUMA nodes "get_mempolicy", "set_mempolicy", "userfaultfd", // User-space page fault handling "perf_event_open", // Performance monitoring "kcmp", // Compare process file structures "mount", // Mount filesystems "umount2", "swapon", "swapoff", "kexec_load", // Load new kernel "kexec_file_load", "reboot", // Reboot system "init_module", // Load kernel module "finit_module", "delete_module", "clock_settime", // Set system clock "settimeofday", "stime", "adjtimex" ], "action": "SCMP_ACT_ERRNO" // Return EPERM } ]}# Run container with default seccomp profile (auto-applied by Docker)docker run nginx
# Run with custom seccomp profiledocker run --security-opt seccomp=/path/to/custom.json nginx
# Run without seccomp (dangerous! only for debugging)docker run --security-opt seccomp=unconfined nginx
# Verify seccomp status inside a containercat /proc/$$/status | grep Seccomp# Seccomp: 2 (0=disabled, 1=strict, 2=filter)18.3 Writing Custom Seccomp Profiles
Section titled “18.3 Writing Custom Seccomp Profiles”// minimal-webserver.json — Minimal profile for a web server{ "defaultAction": "SCMP_ACT_ERRNO", "archMap": [ { "architecture": "SCMP_ARCH_X86_64", "subArchitectures": ["SCMP_ARCH_X86", "SCMP_ARCH_X32"] } ], "syscalls": [ { "names": [ "accept4", // Accept connections "bind", // Bind socket "brk", // Expand heap "clock_gettime", // Get time "close", // Close fd "connect", // Connect socket "epoll_create1", // Create epoll "epoll_ctl", // Control epoll "epoll_pwait", // Wait for events "exit", // Exit process "exit_group", // Exit all threads "fcntl", // File control "fstat", // File stats "futex", // Fast mutex "getcwd", // Get working dir "getdents64", // Read directory "getpid", // Get PID "getsockname", // Get socket name "getsockopt", // Get socket options "listen", // Listen on socket "lseek", // Seek in file "mmap", // Memory mapping "mprotect", // Memory protection "munmap", // Unmap memory "nanosleep", // Sleep "open", // Open file "openat", // Open file (at) "read", // Read "recvfrom", // Receive "recvmsg", // Receive message "rt_sigaction", // Signal handling "rt_sigprocmask",// Signal mask "rt_sigreturn", // Signal return "sendmsg", // Send message "sendto", // Send to "setsockopt", // Set socket options "shutdown", // Shutdown socket "socket", // Create socket "stat", // File stat "write", // Write "writev" // Write vector ], "action": "SCMP_ACT_ALLOW" } ]}18.4 Kubernetes Seccomp
Section titled “18.4 Kubernetes Seccomp”# Kubernetes pod with seccomp profileapiVersion: v1kind: Podmetadata: name: secure-podspec: securityContext: seccompProfile: type: RuntimeDefault # Use container runtime's default profile # type: Localhost # Use a locally stored custom profile # localhostProfile: profiles/myapp.json # type: Unconfined # No seccomp (avoid!) containers: - name: myapp image: myapp:latest securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 capabilities: drop: ["ALL"] # Drop ALL capabilities add: [] # Add back only what's needed18.5 Using strace to Build Seccomp Profiles
Section titled “18.5 Using strace to Build Seccomp Profiles”# Find which syscalls your application actually usesstrace -f -e trace=all ./myapp 2>&1 | \ grep "^[a-z]" | cut -d'(' -f1 | sort -u
# More complete: trace for several minutes during normal operationstrace -f -e trace=all -o /tmp/syscalls.txt ./myapp &APP_PID=$!sleep 60 # Let it run normallykill $APP_PIDcat /tmp/syscalls.txt | grep "^[a-z]" | cut -d'(' -f1 | sort -u > /tmp/needed_syscalls.txt
# Use oci-seccomp-bpf-hook for automatic profile generation# Generates a seccomp profile from an actual container run:podman run --annotation \ io.containers.trace-syscall=/tmp/output-profile.json \ nginx
# The generated profile can be applied to subsequent runs18.6 Complete Security Layering Example
Section titled “18.6 Complete Security Layering Example” Layered Security for a Production Container ─────────────────────────────────────────────
Kubernetes Pod Security: ┌─────────────────────────────────────────────────────────┐ │ SecurityContext: │ │ • runAsNonRoot: true (not root) │ │ • runAsUser: 1000 (specific UID) │ │ • readOnlyRootFilesystem: true (immutable FS) │ │ • allowPrivilegeEscalation: false │ │ • capabilities.drop: ALL (no Linux capabilities) │ │ • seccompProfile: RuntimeDefault │ │ │ │ AppArmor Profile: (via annotation) │ │ • Path-based access control │ │ • Can only read /app/*, /etc/ssl/* │ │ • Can write /tmp/*, /var/log/app/* │ │ │ │ Seccomp Profile: │ │ • Only ~30 necessary syscalls allowed │ │ • No mount, kexec, init_module, ptrace, etc. │ │ │ │ Network Policy: │ │ • Only port 8080 ingress │ │ • Only database port 5432 egress │ └─────────────────────────────────────────────────────────┘
Even with full application code execution: → Cannot escalate to root (no capabilities) → Cannot read sensitive files (AppArmor) → Cannot use dangerous syscalls (seccomp) → Cannot talk to unexpected services (NetworkPolicy) → Cannot modify filesystem (readOnlyRootFilesystem)18.7 Interview Questions
Section titled “18.7 Interview Questions”Q1: What is seccomp and how is it different from AppArmor/SELinux?
Answer: Seccomp (Secure Computing Mode) restricts which system calls a process can make using BPF filters. It operates at the syscall level — if a process tries to call
mount()and that’s not in the allowed list, the kernel kills the process or returns an error. AppArmor and SELinux restrict what resources a process can access (files, capabilities, network) after syscalls are made. They’re complementary: seccomp limits the syscall attack surface (fewer ways to interact with the kernel), while AppArmor/SELinux limits resource access. Docker uses both: seccomp to block ~44 dangerous syscalls, and optionally AppArmor for file access control.
Q2: Why would you use defaultAction: SCMP_ACT_ERRNO instead of SCMP_ACT_KILL?
Answer:
SCMP_ACT_KILLkills the process immediately if it makes a disallowed syscall.SCMP_ACT_ERRNOreturns an error (like EPERM) to the calling process without killing it.SCMP_ACT_ERRNOis generally preferred because: (1) It allows graceful handling — the application might have error handling for failed syscalls. (2) Easier to debug — the application logs the error instead of dying silently. (3) Some applications probe for optional features by trying syscalls and handling failures. UseSCMP_ACT_KILLonly for syscalls that are never acceptable and where you want to prevent even error-path execution.
18.8 Summary
Section titled “18.8 Summary”| Layer | Tool | Restricts |
|---|---|---|
| Syscalls | seccomp | Which kernel calls can be made |
| Capabilities | drop: ALL | Which privileges process has |
| File access | AppArmor | Which paths can be accessed |
| Resource access | SELinux | Type-based resource control |
| Network | NetworkPolicy | Which endpoints can be reached |
Next Chapter: Chapter 19: auditd, Audit Framework & Security Logging
Section titled “Next Chapter: Chapter 19: auditd, Audit Framework & Security Logging”Prerequisites
Section titled “Prerequisites”Chapter 14 (Security Fundamentals).
Advantages & Disadvantages
Section titled “Advantages & Disadvantages”Advantages: Modular authentication (easy to add 2FA), Capabilities avoid giving full root to binaries. Disadvantages: Misconfiguring PAM can lock everyone out of the server permanently.
Common Mistakes
Section titled “Common Mistakes”- Using SUID root instead of assigning specific capabilities.
- Misconfiguring PAM modules causing login lockouts.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Diagnosis | Fix |
|---|---|---|---|
| Cannot ping as standard user | Missing cap_net_raw | getcap /bin/ping | setcap cap_net_raw+ep /bin/ping |
| User locked out of system | PAM faillock triggered | faillock --user <username> | faillock —user |
Hands-on Lab
Section titled “Hands-on Lab”Objective: Work with capabilities and PAM.
# 1. View capabilities of a binarygetcap /usr/bin/ping
# 2. Grant a capability to a binary# sudo setcap cap_net_bind_service+ep /path/to/my_webserver
# 3. Check PAM configurationcat /etc/pam.d/sshdExercises
Section titled “Exercises”- Remove the SUID bit from a custom binary that needs to bind to port 80. Use
setcapto grant it onlycap_net_bind_service. - Configure PAM to require a minimum password length of 12 characters using
pam_pwquality.
Revision Notes
Section titled “Revision Notes”- Capabilities break down root privileges into distinct units.
- PAM (Pluggable Authentication Modules) handles authentication dynamically.
- Seccomp restricts which system calls a process can make.
Further Reading
Section titled “Further Reading”man capabilitiesman pam
Related Chapters
Section titled “Related Chapters”- Chapter 35 — Docker/K8s security using capabilities and seccomp
Last Updated: July 2026