>samit_hota
Back to research
CLOUD SECURITY

Demystifying Container Escapes: Mechanics of Privilege and How to Block Them

Samit Hota·
#cloud-security#containers#linux#hardening

The Fallacy of Container Isolation

A container is not a virtual machine. It is a group of standard Linux processes running on a shared host kernel, bounded only by kernel primitives: Namespaces, Control Groups (cgroups), Capabilities, and mandatory access control frameworks like AppArmor or SELinux. When a container escape occurs, it is rarely the result of an obscure zero-day kernel exploit; the vast majority of real-world escapes trace back to operational misconfigurations that explicitly tear down these Linux boundaries.

When operators pass flags like --privileged or mount sensitive host directories into a workload, they are not granting permission within a sandbox—they are dismantling the sandbox entirely. Understanding how these misconfigurations interact with the underlying kernel is essential for engineering resilient container infrastructure.

The Mechanism: Host Filesystem Mounting

The most direct path from a container process to host compromise occurs when host filesystem paths are mounted into the container namespace, such as through Docker volume binds (-v /:/host) or Kubernetes hostPath volumes.

When the host root filesystem is exposed inside a container filesystem namespace, the Linux Virtual File System (VFS) translates directory traversals directly to the host disk block device. From the perspective of the containerized process, accessing /host/etc/shadow or /host/etc/crontab targets the exact inode on the host system.

If the container process runs as root (UID 0), permission checks succeed at the kernel layer because the kernel evaluates the process’s effective user ID against file ownership on the mounted volume. This permits direct modifications to host runtime logic, such as appending entries to host scheduled tasks, writing public keys to /host/root/.ssh/authorized_keys, or registering new systemd service units.

The Role of Linux Capabilities and CAP_SYS_ADMIN

By default, container runtimes drop dangerous kernel capabilities. However, flags like --privileged grant the container process nearly all native Linux kernel capabilities, most notably CAP_SYS_ADMIN.

CAP_SYS_ADMIN acts as a catch-all high-privilege capability in Linux, enabling over 30 distinct administrative operations including raw device mounting, filesystem configuration, and cgroup management.

Consider how cgroup v1 notification handlers operate when CAP_SYS_ADMIN is active:

  1. Cgroup Mounts: With CAP_SYS_ADMIN, a process inside the container can mount a cgroup controller hierarchy (e.g., mount -t cgroup -o memory cgroup /tmp/cgroup).
  2. Release Agent Configuration: Cgroup v1 includes a mechanism where the kernel executes a configured binary—the release_agent—whenever all processes within a cgroup terminate.
  3. Execution Context: The path assigned to release_agent is evaluated relative to the host namespace, because the host kernel itself initiates the binary execution upon process termination.

When unrestricted capabilities are granted to a container, process isolation collapses back to standard host process privileges.

Locking Down the Runtime: A Hardened Baseline

Defending against container boundary bypasses requires enforcing defense-in-depth settings at the container engine and orchestrator layers. Each setting closes specific privilege vectors.

1. Stripping Linux Capabilities

Never run containers with --privileged. Drop all capabilities by default and explicitly reinstate only the minimum required capabilities if demanded by application logic.

Docker CLI flag:

docker run --cap-drop=ALL ...

Kubernetes Pod SecurityContext:

securityContext:
  capabilities:
    drop:
      - ALL

Removing CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_SYS_MODULE, and CAP_DAC_OVERRIDE blocks direct interaction with kernel subsystem interfaces and device nodes.

2. Disabling Privilege Escalation

Processes inside a container can attempt to gain elevated rights using setuid binaries or child process inheritance. Enforce the no-new-privileges flag to block execution-time privilege gains.

Docker CLI flag:

docker run --security-opt no-new-privileges:true ...

Kubernetes Pod SecurityContext:

securityContext:
  allowPrivilegeEscalation: false

This sets the PR_SET_NO_NEW_PRIVS flag on the process via prctl(), ensuring that operations calling execve() on setuid binaries do not elevate effective execution privileges.

3. Restricting System Calls with Seccomp

Seccomp (Secure Computing Mode) filters the system calls a containerized process can issue to the kernel. A robust seccomp profile restricts sensitive system calls such as unshare, clone (with namespace creation flags), and kexec_load.

Docker CLI flag:

docker run --security-opt seccomp=default ...

Kubernetes Pod SecurityContext:

securityContext:
  seccompProfile:
    type: RuntimeDefault

Seccomp acts as a syscall firewall, blocking high-risk kernel interfaces even if a process retains root UID status within the container namespace.

4. Non-Root Execution and Read-Only Root Filesystems

To prevent runtime modification of local binaries or configuration files, combine non-root execution with read-only filesystems.

Docker CLI flags:

docker run --read-only --user 10001 ...

Kubernetes Pod SecurityContext:

securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  readOnlyRootFilesystem: true

Executing under a non-zero UID ensures that even if a host mount is present, standard POSIX file permission checks prevent host file modification.

Continuous Enforcement and Pipeline Guardrails

Configuring secure runtime defaults must be enforced systematically across deployment pipelines rather than left to developer preference.

Within Kubernetes clusters, deploy Policy-as-Code engines such as Kyverno or Open Policy Agent (OPA/Gatekeeper) paired with Kubernetes Pod Security Standards at the Restricted enforcement level. This prevents non-compliant pod definitions from reaching the API server.

In CI/CD automation, run static analysis tooling like kube-linter or trivy config on deployment manifests and Helm templates to flag instances of --privileged, dangerous hostPath definitions, or missing capability drop configurations prior to deployment.

Want a second set of eyes on your security posture?

Let's talk about where your real exposure is.

Book an advisory call