Skip to content

Docker container escape

Severity: criticalApplies to: Docker Engine 27.x+Applies to: Docker Engine 29.x

The phrase “container escape” suggests an exploit. In practice, most escapes are configuration — the container was handed the means to leave before anyone attacked it.

Worth being precise about, because it changes what you defend against. There are two categories and they need different responses.

These need no vulnerability. Each is documented behaviour.

The socket. A container with /var/run/docker.sock mounted can ask the daemon — which is root — to start another container with / mounted:

Terminal window
# from inside the container that has the socket
docker run -v /:/host --privileged -it alpine chroot /host sh

That’s host root. The “escape” is an API call.

--privileged. No capability restrictions, no seccomp, no AppArmor, and host devices visible. The container mounts the host’s disk and writes to it:

Terminal window
fdisk -l && mount /dev/sda1 /mnt && echo key >> /mnt/root/.ssh/authorized_keys

The docker group. Same as the socket, from a shell instead of a container. Membership is root.

Host namespaces. --pid=host lets a container see and signal host processes; --net=host puts it on the host’s network stack, past your firewall rules.

None of these are bugs. They are why the pages on the socket and privileged mode sit at the top of this cluster — patching cannot help you here, and no CVE will ever be filed.

These are real bugs, and they are why runc matters as much as docker.

Leaky Vessels (January 2024) — a set of four:

CVE Component Effect
CVE-2024-21626 runc File descriptor leak → access to the host filesystem
CVE-2024-23651 BuildKit Race condition → container breakout
CVE-2024-23652 BuildKit Arbitrary file deletion on the host during build
CVE-2024-23653 BuildKit Breakout during image build

Fixed in runc 1.1.12 and BuildKit v0.12.5.

The point isn’t these specific CVEs — they’re patched everywhere by now. It’s where they lived: runc is the thing that actually creates containers, and a current Docker with a stale runc is a current Docker with an escape. Two of the four were in the build path, which means a hostile Dockerfile — from a dependency, a fork, a PR — was enough. If you build untrusted images in CI, BuildKit is part of your attack surface.

Check runc separately. Nothing about docker version tells you it’s current.