POSIX capabilities
Traditional Unix has one privileged user (root, UID 0) who can do anything. Linux capabilities (since 2.2) split root's powers into ~40 named bits. A few common ones:
CAP_NET_BIND_SERVICE: bind to ports below 1024.CAP_NET_RAW: open raw sockets (ping, packet capture).CAP_SYS_ADMIN: the catch-all "still very privileged" bit (mount, set hostname, etc.).CAP_DAC_OVERRIDE: bypass file permission checks.
You can grant a binary one capability without making it setuid root. setcap cap_net_bind_service=+ep /usr/sbin/myserver lets it bind port 80 with no other root powers. Containers strip most caps from PID 1 by default.
SELinux vs AppArmor
Both are Mandatory Access Control (MAC) systems, but they differ in granularity:
- SELinux (Red Hat, Fedora, Android): type-based. Every process and every file gets a security context like
system_u:object_r:httpd_sys_content_t:s0. Policy rules say which types can perform which actions on which other types. Powerful, verbose, hard to debug. Used in security-conscious distros and Android. - AppArmor (Ubuntu, Debian, SUSE): path-based profiles. A profile lists which paths a binary can read/write/exec. Simpler to write and reason about. Profiles in
/etc/apparmor.d/.
Both run in enforcing or permissive mode. Permissive logs would-be denials without blocking, useful for tuning before enforcement.
The seven Linux namespaces
Namespaces give processes their own view of system resources. The set as of recent kernels:
- mnt: mount points. Each namespace sees its own filesystem layout.
- pid: process IDs. The container's PID 1 is its init; outside it has a different PID.
- net: network interfaces, routing tables, sockets. Each namespace has its own loopback.
- uts: hostname and domain name.
- ipc: System V IPC and POSIX message queues.
- user: UID and GID mappings. The container's root maps to a non-root UID on the host.
- cgroup: cgroup root view.
The newer time namespace (5.6+) virtualizes the system clock for things like sleeping containers. A "container" is typically a process group joined into a new instance of each of these.
seccomp-bpf
seccomp ("secure computing") restricts which syscalls a process may make. The original "strict" mode allows only read, write, exit, and sigreturn. The much more useful seccomp-bpf mode runs a Berkeley Packet Filter on every syscall and decides per call.
Docker's default seccomp profile blocks ~50 syscalls including kexec_load, open_by_handle_at, userfaultfd (used in many container escapes), and perf_event_open. Custom profiles can be tighter, allowing only the syscalls the workload genuinely needs.
The pattern: container runtimes layer namespaces (for resource isolation) + capability drops (for privilege reduction) + seccomp (for syscall reduction) + a MAC system (SELinux/AppArmor for policy). Each layer is independently bypassable; together they raise the bar substantially.
Container escapes happen here
Real CVE classes:
- Kernel vulnerabilities the container shares with the host (CVE-2022-0185, Dirty Pipe).
- runc / containerd bugs in the runtime that allow escaping from the namespace.
- Misconfigured capability drops that leave dangerous capabilities, or running with
--privilegedwhich disables most of the above. - Mount escapes where the container can mount the host filesystem (often via
CAP_SYS_ADMIN).
Defense: run containers as non-root user, drop all capabilities and add back only what is needed, set readOnlyRootFilesystem: true, disable privilege escalation, run a seccomp profile, and patch the host kernel promptly.