FlawOpen

flawopen.com/Teardowns/cve-2024-21626-runc-container-breakout-leaky-vessels

● CVE-2024-21626 · CVSS 8.6[1], [5] · High
FlawOpen Security Research

CVE-2024-21626: How runc fixed the Leaky Vessels escape

CVE-2024-21626 combined an internal runc file-descriptor leak with a missing post-chdir boundary check. Depending on the launch path, a container could start with a host-namespace working directory or use a related exec path to reach host files.[1], [2], [3]

💡 Plain English Explainer (ELI5)

A container launch is like a building handoff: runc carries temporary keys while preparing the rooms. The bug let a requested room (working directory) resolve through one of those internal keys into the host building; the container process did not simply inherit the keys. The fix checks the final room is inside the container and closes internal handles before handing over control.[1], [2]

Core Concepts & Subsystem Terms

runc
The OCI runtime that creates and starts containers.[1]
Internal file descriptor
An operating-system handle for an open file or directory. A leaked descriptor can carry access across a process boundary if it remains open when a child is executed.[1], [3]
cwd
The process working directory. Linux can report ENOENT from Getwd when cwd is outside the process mount namespace root.[1], [2]
Container breakout
A path from a container process to host resources that should remain outside its isolation boundary.[1]

Framework mappings

  • CWE reference for the weakness described in this guide: CWE-403

Root Cause Analysis

Before runc 1.1.12, internal file descriptors could leak into runc init. The descriptors were internal to runc init, not simply inherited by the container process. In attacks 1 and 2, a configured or selected working directory could resolve through a leaked host-mount-namespace handle; runc did not verify after chdir that the final cwd was inside the container root. Separate variants used execve path handling to overwrite a host binary. The 1.1.12 release added four layers: verify cwd with Linux Getwd/ENOENT behavior, close internal descriptors immediately before execve, fix identified descriptor leaks (including O_CLOEXEC for the cgroup handle), and mark non-stdio descriptors close-on-exec before starting runc init.[1], [2], [3], [4]

CVE-2024-21626: attack variants and the runc 1.1.12 protection that closes each path

Advisory variantRequired conditions and boundaryRelevant fix or workaround
Attack 1 — malicious image via runc runA privileged user must be induced to start an untrusted image. Its configured working directory can resolve outside the container mount namespace.The post-chdir unix.Getwd() check fails when cwd is outside the namespace root. As a temporary workaround, permit only / as cwd for untrusted images.
Attack 2 — container process plus runc execA process already inside the container must anticipate an administrator’s non-root runc exec --cwd choice and arrange the relevant path before exec.The same post-chdir cwd check blocks execution outside the container. Restrict --cwd to / until patched.
Attack 3a — host-binary overwrite through image launchThis adapts attack 1 and retains its requirement that a privileged user start the malicious image; the variant crosses the execve boundary to a host binary.Close internal runc descriptors immediately before execve, fix identified leaks, and set O_CLOEXEC. Upgrade to runc 1.1.12 or a vendor-fixed release.
Attack 3b — host-binary overwrite through runc execThis adapts attack 2 and starts from a malicious process in a running container the attacker can affect; a privileged administrator must still perform an ordinary runc exec on that container. The advisory says this needs less user interaction than the image path.The close-before-execve and close-on-exec protections address this distinct path. The maintainers report no practical workaround for this variant; upgrade.

Step-by-Step Attack Flow

Step 1

Internal handle leak

During setup, an internal runc descriptor referencing the host mount namespace could remain available to runc init.[1], [3]

Step 2

Reachable working-directory path

A malicious image or a carefully conditioned runc exec workflow could cause the requested cwd to resolve outside the container root.[1], [2]

Step 3

Boundary failure

The pre-fix code did not verify the final cwd after chdir; related variants abused execve path handling to overwrite a host binary.[2], [3]

Step 4

Impact and scope

The advisory documents host-filesystem access and host control across four variants. Image-based variants require a privileged user to run an untrusted image. Attack 3b still requires an administrator to perform an ordinary runc exec on a container the attacker can affect.[1]

Source Code: Flaw vs. Secure Implementation

UNPATCHED FLAW
// Simplified pre-fix behavior; not verbatim runc source
if err := os.Chdir(config.Cwd); err != nil {
    return err
}
// Missing: verify the final cwd is inside the container namespace root
HARDENED SECURE PATCH
// Upstream runc 1.1.12 boundary check (excerpt)
if wd, err := unix.Getwd(); errors.Is(err, unix.ENOENT) {
    return errors.New("cwd is outside the container mount namespace root")
} else if err != nil {
    return fmt.Errorf("cannot verify cwd safety: %w", err)
} else if !filepath.IsAbs(wd) {
    return fmt.Errorf("cwd is not absolute: %q", wd)
}

Engineering & System Hardening Checklist

Sources