CVE-2024-21626 / Leaky Vessels Landmark

CVE-2024-21626: runc 'Leaky Vessels' Container Breakout Teardown

How an internal /proc/self/cwd file descriptor leak in libcontainer/init_linux.go allowed container processes to break out to the host filesystem across Docker and Kubernetes.

💡 비유를 통한 쉬운 설명 (ELI5)

Imagine you are sent to a prison cell. The guard walks in, closes the heavy steel door, but forgets his master keychain hanging on the inside keyhole! You simply turn the key, walk through the door, and now you have the keys to every single cell and door in the entire prison fortress.

핵심 개념 및 서브시스템 용어 해설

runc
The universal low-level container runtime used by Docker, containerd, and Kubernetes to spawn and run OCI containers.
File Descriptor Leak
Failing to mark an open file handle with `O_CLOEXEC`, leaving it accessible to child processes after `execve()`.
/proc/self/cwd
A Linux virtual file path representing the current working directory of the process.
Container Breakout
Escaping the isolated container namespace to access and overwrite files on the underlying host operating system.

단계별 공격 실행 메커니즘

Step 1

1. Malicious Docker Image

The attacker crafts a container image setting WORKDIR /proc/self/fd/7.

Step 2

2. Container Execution

When runc initializes the container, internal file descriptor 7 still points to host /sys/fs/cgroup.

Step 3

3. Directory Traversal via Procfs

The container's entrypoint process uses ../../../../ relative traversal through the leaked handle.

Step 4

4. Host Filesystem Compromise

The container process lands directly in the host root directory (`/`), overwriting host cron jobs and binaries.

소스 코드 비교: 치명적 취약점 vs 보안 강화 패치

일반 개발자가 쉽게 이해할 수 있는 고수준 소스 코드로 제공 (어셈블리/바이너리 제외).

패치되지 않은 취약점
// VULNERABLE: libcontainer/init_linux.go before patch
func (l *linuxStandardInit) Init() error {
    // ROOT CAUSE:
    // runc opened /sys/fs/cgroup and internal procfs directories during setup,
    // but did not close them before invoking execve() into the user container!
    // File descriptors remained open and accessible inside the container!
    return syscall.Exec(l.config.Args[0], l.config.Args, os.Environ())
}
보안 강화 패치
// SECURE: libcontainer/init_linux.go patch
func (l *linuxStandardInit) Init() error {
    // 1. Unconditionally verify and close ALL internal file descriptors before exec
    if err := finalizeNamespace(l.config); err != nil {
        return err
    }
    
    // 2. Explicitly close any leaked descriptor indices (> 2)
    closeExecFds(l.config.PassedFiles)
    
    // 3. Verify current working directory is strictly inside container rootfs
    if err := verifyCwdInsideRootfs(l.config.Rootfs); err != nil {
        return fmt.Errorf("working directory escaped container rootfs: %w", err)
    }

    return syscall.Exec(l.config.Args[0], l.config.Args, os.Environ())
}

엔지니어링 보안 강화 체크리스트

← 전체 보안 디렉터리 보기 모든 플랫폼 보안 업데이트 →