FlawOpen

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

● CVE-2024-21626 · CVSS 8.6[1], [5] · Alta
Investigación · FlawOpen

CVE-2024-21626: cómo runc corrigió el escape Leaky Vessels

CVE-2024-21626 combinó una fuga interna de descriptores en runc con la falta de una comprobación del límite después de chdir. Según la ruta de inicio, el contenedor podía comenzar con un directorio de trabajo en el namespace del host o alcanzar archivos del host mediante una ruta relacionada con exec.[1], [2], [3]

💡 Explicación en Lenguaje Sencillo (ELI5)

runc prepara una habitación aislada para el contenedor. Un handle interno filtrado podía hacer que el directorio de trabajo elegido apuntara fuera de esa habitación; la comprobación final del límite y el cierre de handles internos bloquean ese paso.[1], [2]

Conceptos Clave y Términos

runc
El runtime OCI que crea e inicia contenedores.[1]
Descriptor de archivo interno
Handle del sistema operativo para un archivo o directorio abierto. Si un descriptor filtrado sigue abierto al ejecutar un proceso hijo, puede conservar acceso entre procesos.[1], [3]
cwd
Directorio de trabajo del proceso. En Linux, Getwd puede devolver ENOENT cuando queda fuera de la raíz del namespace de montaje.[1], [2]
Escape del contenedor
Ruta por la que un proceso del contenedor alcanza recursos del host que deberían quedar fuera de su aislamiento.[1]

Mapeos de marcos

  • Referencia CWE para la debilidad descrita en esta guía: CWE-403

Análisis de Causa Raíz

Antes de runc 1.1.12, algunos descriptores internos podían filtrarse a runc init. Eran internos de runc init; el proceso del contenedor no los heredaba simplemente. En los ataques 1 y 2, el directorio de trabajo configurado o elegido podía resolverse mediante un handle del namespace de montaje del host; runc no comprobaba después de chdir que el directorio final siguiera dentro de la raíz del contenedor. Variantes distintas usaban el manejo de rutas de execve para sobrescribir un binario del host. La versión 1.1.12 añadió cuatro capas: comprobar cwd con el comportamiento Getwd/ENOENT de Linux, cerrar descriptores internos justo antes de execve, corregir fugas concretas y marcar como close-on-exec los descriptores que no son de stdio antes de iniciar runc init.[1], [2], [3], [4]

CVE-2024-21626: variantes de ataque y protección aplicada en runc 1.1.12

Variante del avisoCondiciones y límite necesariosCorrección o medida temporal pertinente
Ataque 1 — imagen maliciosa mediante runc runEs necesario engañar a un usuario privilegiado para que inicie una imagen no confiable. El directorio de trabajo configurado puede resolverse fuera del namespace de montaje del contenedor.La comprobación unix.Getwd() posterior a chdir falla si cwd queda fuera de la raíz del namespace. Como medida temporal, permita solo / como cwd para imágenes no confiables.
Ataque 2 — proceso del contenedor con runc execUn proceso dentro del contenedor debe anticipar el cwd distinto de / que elegirá un administrador con runc exec --cwd y preparar la ruta pertinente antes de la ejecución.La misma comprobación de cwd después de chdir impide la ejecución fuera del contenedor. Hasta actualizar, limite --cwd a /.
Ataque 3a — sobrescritura de binario del host al iniciar una imagenAdapta el ataque 1 y mantiene el requisito de que un usuario privilegiado inicie la imagen maliciosa; esta variante cruza el límite de execve hasta un binario del host.Cierre los descriptores internos de runc justo antes de execve, corrija las fugas identificadas y active O_CLOEXEC. Actualice a runc 1.1.12 o a una versión corregida por el proveedor.
Ataque 3b — sobrescritura de binario del host mediante runc execEsta variante adapta el ataque 2 y parte de un proceso malicioso en un contenedor activo que el atacante puede afectar; aun así, un administrador con privilegios debe ejecutar una operación normal de runc exec en ese contenedor. El aviso indica que requiere menos interacción del usuario que la ruta mediante imagen.Las protecciones de cierre antes de execve y close-on-exec cubren esta ruta distinta. Los mantenedores no indican una solución práctica alternativa; actualice.

Flujo de Ataque Paso a Paso

Paso 1

Fuga de handle interno

Durante la preparación, un descriptor interno de runc que apuntaba al namespace de montaje del host podía seguir disponible para runc init.[1], [3]

Paso 2

Ruta de directorio alcanzable

Una imagen maliciosa o un flujo runc exec con condiciones específicas podía hacer que el cwd solicitado quedara fuera de la raíz del contenedor.[1], [2]

Paso 3

Fallo del límite

El código anterior no comprobaba el cwd final tras chdir; otras variantes abusaban del manejo de rutas de execve para sobrescribir un binario del host.[2], [3]

Paso 4

Impacto y condiciones

El aviso documenta acceso al sistema de archivos y control del host en cuatro variantes. Los ataques mediante imagen requieren que un usuario con privilegios ejecute una imagen no confiable. El ataque 3b aún requiere que un administrador realice una operación normal de runc exec en un contenedor que el atacante pueda afectar.[1]

Código Fuente: Vulnerable vs. Seguro

IMPLEMENTACIÓN VULNERABLE
// Comportamiento pre-fix simplificado; no es código literal de runc
if err := os.Chdir(config.Cwd); err != nil {
    return err
}
// Faltaba verificar que el cwd final quedara dentro de la raíz del namespace
PARCHE SEGURO Y ROBUSTO
// Comprobación de límite de runc 1.1.12 (fragmento upstream)
if wd, err := unix.Getwd(); errors.Is(err, unix.ENOENT) {
    return errors.New("cwd fuera de la raíz del namespace de montaje del contenedor")
} else if err != nil {
    return fmt.Errorf("no se pudo comprobar la seguridad de cwd: %w", err)
} else if !filepath.IsAbs(wd) {
    return fmt.Errorf("cwd no es absoluto: %q", wd)
}

Lista de Verificación de Seguridad para Ingeniería

Fuentes