Leaky Vessels is the name for a cluster of container breakouts rooted in the same mistake: the runtime leaves a file descriptor or a path that refers to the host filesystem accessible to the container process at the moment it starts. In runc, an internal file descriptor to a host directory was not closed before the container process executed, so a container whose working directory was set to /proc/self/fd/<n> landed in a host directory rather than the container's. The container then read and wrote host files, and because the process ran during container setup with host context, it escalated to host root. Related issues affected the Docker build tooling, where a WORKDIR or mount path resolved against the host.
Confirm the runtime version window:
runc --version 2>/dev/null # pre-1.1.12 runc is affected
docker version 2>/dev/null | grep -i buildkit
Technique#
# A malicious image sets its working directory to a leaked host-fd path.
# runc leaks an fd to a host directory; /proc/self/fd/<n> resolves to the host.
WORKDIR /proc/self/fd/7
# The entry process now starts with its cwd on the host filesystem:
RUN cd / && cat etc/shadow && \
echo 'ssh-ed25519 AAAA... a' >> root/.ssh/authorized_keys
At build or run time the container process begins with its current directory inside the host filesystem through the leaked descriptor, so ordinary relative-path file operations touch host files. Writing a cron job, sudoers entry, or SSH key completes the escape; the exact leaked descriptor number is discovered by enumerating /proc/self/fd for one whose target is a host path.
ls -l /proc/self/fd/ # find a descriptor pointing at a host directory
Exploitation notes#
- The escape is delivered through a crafted image or build, so it is a risk wherever untrusted Dockerfiles or images are built or run, including CI systems that build user-supplied repositories.
- The leaked descriptor is the pivot: enumerate
/proc/self/fdat process start and identify one resolving outside the container rootfs, then use relative paths from it. - Fixes close the offending descriptors with
O_CLOEXECand validate working-directory resolution; patchedruncand BuildKit defeat the technique.