Skip to content
intermediate Phase 17 · Container and Kubernetes Security

Container Runtime Hardening

Restrict syscalls, capabilities, and filesystem access for running containers.

55m
0 problems
Topic Progress 0%

Seccomp Profiles: Filtering Syscalls

Seccomp Profiles: Filtering Syscalls

Every process running inside a container makes system calls (syscalls) to the kernel — opening files, creating network sockets, spawning processes, changing permissions. A typical application uses only a fraction of the 300+ syscalls available on Linux. The syscalls your application never uses are an attack surface: if an attacker exploits a vulnerability, they can only use syscalls that are permitted.

Seccomp (Secure Computing Mode) is a Linux kernel feature that filters which syscalls a process can invoke. Docker and containerd apply a default seccomp profile that blocks known-dangerous syscalls, but you can create custom profiles for finer control.

How seccomp works

A seccomp profile is a JSON file that defines which syscalls are allowed, denied, or handled with specific actions:

{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "names": ["chmod", "fchmod", "fchmodat"],
      "action": "SCMP_ACT_ERRNO"
    },
    {
      "names": ["mount", "umount2"],
      "action": "SCMP_ACT_ERRNO"
    }
  ]
}

The actions:

  • SCMP_ACT_ALLOW: Permit the syscall
  • SCMP_ACT_ERRNO: Block the syscall and return an error (the application gets EPERM)
  • SCMP_ACT_KILL: Kill the process that attempts the syscall
  • SCMP_ACT_LOG: Log the syscall attempt (useful for building profiles)

Building a custom seccomp profile

The most practical approach is to start with the default Docker profile and add restrictions:

# Run a container with syscall logging to build a custom profile
docker run --security-opt seccomp=unconfined \
  --security-opt seccomp-log \
  myapp:latest 2>&1 | grep "syscall"

# Or use strace to capture syscalls
strace -f -e trace=all python3 app.py 2>&1 | grep "^(" | sort -u

Then create a profile that allows only the syscalls your application actually uses:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "syscalls": [
    {
      "names": [
        "accept", "accept4", "access", "arch_prctl", "bind",
        "brk", "clock_gettime", "close", "connect", "epoll_create1",
        "epoll_ctl", "epoll_wait", "execve", "exit_group", "fstat",
        "futex", "getcwd", "getdents64", "getpeername", "getrandom",
        "getsockname", "getsockopt", "ioctl", "listen", "lseek",
        "madvise", "mmap", "mprotect", "munmap", "newfstatat",
        "openat", "poll", "read", "readlink", "recvfrom",
        "rt_sigaction", "rt_sigprocmask", "sendto", "setitimer",
        "setsockopt", "shutdown", "sigaltstack", "socket", "stat",
        "sysinfo", "tgkill", "time", "uname", "wait4", "write",
        "writev"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

This profile uses defaultAction: SCMP_ACT_ERRNO (deny by default) and explicitly allows only the syscalls the application needs. Any syscall not in the list returns EPERM.

Applying seccomp profiles

# Docker
docker run --security-opt seccomp=/path/to/profile.json myapp:latest

# Kubernetes — via securityContext (Kubernetes 1.24+)
apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/myapp.json
  containers:
  - name: app
    image: myapp:latest

The default Docker seccomp profile

Docker's default profile blocks approximately 44 syscalls that are known to be dangerous or unnecessary, including:

  • mount, umount2 — filesystem mounting
  • reboot — system reboot
  • kexec_load — kernel loading
  • open_by_handle_at — can bypass file permissions
  • ptrace — process debugging (used in container escapes)

The default profile is a good starting point, but custom profiles provide defense in depth by restricting to only what your specific application needs.

Linux Capabilities and Filesystem Restrictions

Linux Capabilities and Filesystem Restrictions

Linux capabilities split the privileges of the root user into distinct units. Instead of running as root (which has all capabilities), a process can be granted only the specific capabilities it needs. Containers inherit a default set of capabilities from the runtime — most of which are unnecessary for typical applications.

The default capability set and why it's too broad

Docker's default capability set includes 14 capabilities. A typical web application needs almost none of them:

Capability What it allows Needed by web apps?
CHOWN Change file ownership Rarely
DAC_OVERRIDE Bypass file read/write/execute permissions No
FSETID Set file capabilities No
FOWNER Bypass permission checks on files owned by the process No
MKNOD Create device nodes No
NET_RAW Use raw sockets (ping, packet crafting) No
SETGID Change group ID No
SETUID Change user ID No
SETFCAP Set file capabilities No
SETPCAP Transfer capabilities No
NET_BIND_SERVICE Bind to ports < 1024 Only for port 80/443
SYS_CHROOT Change root directory No
KILL Send signals to any process No
AUDIT_WRITE Write to audit log No

Dropping all capabilities

The most secure approach is to drop all capabilities and add back only what's needed:

# Kubernetes pod spec
apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  containers:
  - name: app
    image: myapp:latest
    securityContext:
      capabilities:
        drop:
          - ALL
        add:
          - NET_BIND_SERVICE  # Only if binding to port 80/443
# Docker
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp:latest

If your application doesn't bind to ports below 1024, you don't even need NET_BIND_SERVICE. Drop everything.

Read-only root filesystem

A read-only root filesystem prevents any process from writing to the container's filesystem. This stops attackers from:

  • Downloading and executing malicious binaries
  • Modifying application code or configuration
  • Writing persistence mechanisms (cron jobs, startup scripts)
  • Tampering with or deleting logs
# Kubernetes
securityContext:
  readOnlyRootFilesystem: true
# Docker
docker run --read-only --tmpfs /tmp --tmpfs /var/tmp myapp:latest

Applications that need to write files should use:

  • emptyDir volumes for temporary files (Kubernetes)
  • tmpfs mounts for writable directories (Docker)
  • Persistent volumes for data that must survive container restarts

Resource limits

Resource limits prevent a compromised container from consuming all host resources:

# Kubernetes
resources:
  requests:
    memory: "128Mi"
    cpu: "250m"
  limits:
    memory: "256Mi"
    cpu: "500m"

Without limits, a compromised container can:

  • Consume all available memory (OOM killer terminates other containers)
  • Use all CPU cycles (starving other workloads)
  • Fill the disk with logs or downloaded files
  • Open thousands of network connections (exhausting file descriptors)

Putting it all together: a hardened pod spec

apiVersion: v1
kind: Pod
metadata:
  name: hardened-app
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 999
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: myapp:latest
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
          - ALL
    resources:
      requests:
        memory: "128Mi"
        cpu: "250m"
      limits:
        memory: "256Mi"
        cpu: "500m"
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir: {}

This pod spec implements every runtime hardening control: non-root user, seccomp, no privilege escalation, read-only filesystem, dropped capabilities, and resource limits. An attacker who compromises this container has minimal capabilities, cannot write files, cannot escalate privileges, and cannot consume excessive resources.

Practice Problems

0 / 2 solved
Create a Seccomp Profile for a Python Web App

A Python web application uses Flask, connects to a PostgreSQL database, and serves static files. It does not use raw sockets, mount filesystems, or change file ownership. Write a seccomp profile that allows common web app syscalls but blocks dangerous ones.

Solution
```json
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "names": ["mount", "umount2", "reboot", "kexec_load", "ptrace", "open_by_handle_at"],
      "action": "SCMP_ACT_ERRNO"
    },
    {
      "names": ["socket", "socketpair"],
      "action": "SCMP_ACT_ALLOW",
      "args": [
        {
          "index": 0,
          "value": 2,
          "op": "SCMP_CMP_EQ"
        }
      ]
    }
  ]
}
```
This profile allows most syscalls by default but blocks known-dangerous ones (mount, reboot, ptrace, etc.). The socket restriction ensures only AF_INET (IPv4) sockets are created, blocking AF_PACKET (raw sockets). For a production profile, use strace to capture the exact syscalls the application uses and build an allowlist.
Harden a Running Container

A container currently runs as root with all capabilities, a writable filesystem, and no resource limits. Write the Docker run command and Kubernetes pod spec to harden it.

Solution
Docker:
```bash
docker run \
  --user 999:999 \
  --read-only \
  --tmpfs /tmp \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --memory 256m \
  --cpus 0.5 \
  myapp:latest
```

Kubernetes:
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: hardened-app
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 999
  containers:
  - name: app
    image: myapp:latest
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: [ALL]
    resources:
      requests:
        memory: "128Mi"
        cpu: "250m"
      limits:
        memory: "256Mi"
        cpu: "500m"
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir: {}
```

Quiz

1. A container runs with the default Docker seccomp profile. An attacker exploits a vulnerability and attempts to use the `mount` syscall to mount a malicious filesystem. What happens?

Question 1 options

2. Why is dropping all Linux capabilities more secure than keeping the default Docker capability set?

Question 2 options

3. A container has a read-only root filesystem but the application needs to write temporary files. What is the correct approach?

Question 3 options

Flashcards

Question

What is seccomp and how does it improve container security?

Answer

Seccomp (Secure Computing Mode) is a Linux kernel feature that filters which syscalls a process can invoke. By restricting syscalls to only what the application needs, it limits what an attacker can do even after compromising the container.

Question

What is the most secure approach to Linux capabilities in containers?

Answer

Drop all capabilities (`capabilities.drop: [ALL]`) and add back only what the application specifically needs. Most applications need zero capabilities.

Question

Why use a read-only root filesystem for containers?

Answer

It prevents attackers from writing malicious binaries, modifying configuration, installing persistence mechanisms, or tampering with logs — even after gaining code execution inside the container.

Question

What does `allowPrivilegeEscalation: false` prevent?

Answer

It prevents a process from gaining more privileges than its parent process. This blocks setuid binaries and other privilege escalation techniques inside the container.

Revision Notes

Key Takeaways

  • 1. Seccomp profiles restrict syscalls to only what the application needs, limiting attacker capabilities
  • 2. Dropping all Linux capabilities follows least privilege — most applications need zero capabilities
  • 3. Read-only root filesystems prevent attackers from writing files even after code execution
  • 4. Resource limits contain compromises by preventing resource exhaustion attacks

Interview Tips

  • • Use a concrete example: 'We built a custom seccomp profile using strace that reduced available syscalls from 300+ to 45, blocking entire attack categories.'
  • • Explain the layered approach: 'Seccomp filters syscalls, capabilities limit privileges, read-only filesystem prevents writes, and resource limits contain resource exhaustion — each layer addresses a different attack vector.'
  • • Discuss the trade-off: 'Custom seccomp profiles require testing to avoid blocking legitimate syscalls, but the security benefit is significant.'

Cheat Sheet

Container Runtime Hardening Cheat Sheet

Phase: Container and Kubernetes Security | Level: intermediate

Seccomp profiles:

  • Filter which syscalls a container process can invoke
  • Docker default blocks ~44 dangerous syscalls (mount, reboot, ptrace, etc.)
  • Custom profiles: start with default, add application-specific restrictions
  • Build with strace or seccomp-log to capture actual syscall usage

Linux capabilities:

  • Default Docker set includes 14 capabilities — most are unnecessary
  • Drop ALL, add back only what's needed
  • Most applications need zero capabilities
  • NET_BIND_SERVICE only needed for ports < 1024

Read-only root filesystem:

  • Prevents writing binaries, modifying config, installing persistence
  • Use tmpfs/emptyDir for writable directories
  • Application code and config remain immutable

Resource limits:

  • Prevent compromised containers from consuming all host resources
  • Set requests (guaranteed) and limits (maximum)
  • Memory, CPU, and ephemeral storage

Complete hardened pod spec:

securityContext:
  runAsNonRoot: true
  runAsUser: 999
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  seccompProfile:
    type: RuntimeDefault
  capabilities:
    drop: [ALL]
resources:
  limits:
    memory: "256Mi"
    cpu: "500m"

Continue Learning

Before You Start

  • No prerequisites listed.

Next Topics