Problema

En entornos que ejecutan Docker o Kubernetes, el proceso containerd actúa como motor de bajo nivel para crear y gestionar contenedores. Cuando una imagen contiene archivos de usuarios o grupos extremadamente grandes, el parser interno de moby/sys/user intenta leerlos sin aplicar límites de tamaño. El consumo de RAM crece rápidamente, el kernel dispara el OOM killer y termina con containerd. La API de runtime desaparece, lo que impide crear nuevos contenedores y, en clústers, deja fuera de línea al nodo de control de Kubernetes. El impacto es de disponibilidad: los pods que dependan de ese nodo fallan, y el plano de control pierde capacidad de programar workloads hasta que se reinicie el proceso o el nodo.

Este patrón no es exclusivo de una versión concreta; cualquier runtime que delegue la deserialización de /etc/passwd y /etc/group dentro de la capa de la imagen está expuesto. Por eso, aunque el CVE‑2026‑47262 está catalogado con CVSS 6.5, la repercusión práctica puede ser crítica en despliegues de alta disponibilidad.

Causa

Parsing sin límites

El código de moby/sys/user recorre los archivos de usuarios y grupos extraídos de la capa de la imagen para construir la tabla de UID/GID dentro del contenedor. No hay verificación de longitud ni de número de entradas, lo que permite que una imagen con miles de líneas provoque una asignación de memoria proporcional.

Falta de validación en la cadena de suministro

  • Imágenes no firmadas: los registries públicos pueden alojar imágenes creadas por actores malintencionados.
  • Importación directa: herramientas como ctr images import o docker load aceptan cualquier tarball sin inspección previa.
  • CI/CD sin escaneo: pipelines que construyen imágenes a partir de dependencias externas pueden arrastrar este tipo de payload sin notarlo.

Configuración predeterminada del runtime

  • containerd se ejecuta como proceso del sistema sin restricciones de cgroup específicas, lo que permite que su consumo de memoria alcance el total disponible.
  • Los sistemas basados en systemd no definen un slice con límites de RAM para el daemon, por lo que el OOM killer actúa solo cuando el nodo está bajo presión.

Solución

1. Aplicar los parches oficiales

Los proyectos Docker y containerd publicaron versiones corregidas que introducen límites de tamaño al parser:

Versión afectada Versión corregida
1.7.x ≥ 1.7.33
2.0.x ≥ 2.0.10
2.1.x ≥ 2.1.9
2.2.x ≥ 2.2.5
2.3.x ≥ 2.3.2

En RHEL/OpenShift la actualización se distribuye mediante RHSA‑2026:35111. Ejecutar el gestor de paquetes del SO es suficiente:

sudo dnf update containerd docker-ce

En distribuciones basadas en Debian/Ubuntu:

sudo apt-get update && sudo apt-get install --only-upgrade containerd docker-ce

2. Restringir la superficie de importación

  • Whitelist de registries: configura Docker y Kubernetes para aceptar imágenes solo de dominios de confianza (/etc/docker/daemon.json y imagePolicyWebhook).
  • Firma de imágenes: utiliza Notary v2 o cosign para validar la integridad antes de la importación.
  • Admission controller: en Kubernetes, habilita ImagePolicyWebhook que rechaza imágenes que superen un umbral de tamaño de capas o que contengan archivos de usuarios mayores a X KB.

3. Limitar recursos del daemon

Crear un slice de systemd con límite de RAM evita que containerd agote toda la memoria del host:

sudo mkdir -p /etc/systemd/system/containerd.service.d
cat <<'EOF' | sudo tee /etc/systemd/system/containerd.service.d/override.conf
[Service]
MemoryMax=2G
EOF
sudo systemctl daemon-reload
sudo systemctl restart containerd

Ajusta MemoryMax según la carga típica del nodo; 2 GB es un punto de partida razonable para la mayoría de clusters.

4. Monitoreo y alertas

  • Prometheus: añade la métrica containerd_process_resident_memory_bytes y crea una regla de alerta cuando supere el 80 % del límite configurado.
  • Logs: habilita containerd en modo debug (--log-level=debug) para capturar intentos de parsing fallidos.

5. Pruebas de regresión

Incluye en el pipeline de CI una prueba que construya una imagen con un archivo /etc/passwd de 10 MB y verifique que la creación del contenedor falla de forma controlada (se espera un error de “memory limit exceeded”). Esto garantiza que futuras actualizaciones no reintroduzcan la vulnerabilidad.

Cuándo aplicar esta solución

  • Síntomas: containerd desaparece del puerto 2375/2376, docker info muestra “Cannot connect to the Docker daemon”, o los pods del nodo entran en estado Pending con mensaje “FailedCreatePodSandBox”.
  • Entornos: cualquier clúster que use Docker como runtime, o que ejecute containerd directamente (por ejemplo, CRI‑O con compatibilidad).
  • No aplica: si el nodo ya está configurado con un runtime que no usa moby/sys/user (por ejemplo, cri-o sin la capa de parsing) y la versión del runtime está por debajo del rango vulnerable.

Código

# 1. Actualizar paquetes vulnerables
sudo dnf update containerd docker-ce   # RHEL / Fedora
# sudo apt-get update && sudo apt-get install --only-upgrade containerd docker-ce   # Debian / Ubuntu

# 2. Crear slice de systemd con límite de RAM
sudo mkdir -p /etc/systemd/system/containerd.service.d
cat <<'EOF' | sudo tee /etc/systemd/system/containerd.service.d/override.conf
[Service]
MemoryMax=2G
EOF
sudo systemctl daemon-reload
sudo systemctl restart containerd

# 3. Configurar whitelist de registries (Docker)
cat <<'EOF' | sudo tee /etc/docker/daemon.json
{
  "insecure-registries": [],
  "registry-mirrors": [],
  "allow-nondistributable-artifacts": [],
  "disable-legacy-registry": true,
  "disable-legacy-registry": true,
  "default-runtime": "runc",
  "runtimes": {
    "runc": {
      "path": "runc"
    }
  }
}
EOF
sudo systemctl restart docker

Verificación

  1. Comprobar versión

    containerd --version
    docker version --format '{{.Server.Version}}'
    

    La salida debe mostrar una versión igual o superior a la lista de correcciones.

  2. Validar límite de memoria

    systemctl show containerd -p MemoryCurrent -p MemoryMax
    

    MemoryMax debe reflejar el valor configurado (p. ej. 2G).

  3. Test de imagen maliciosa

    • Construye una imagen con un /etc/passwd de 20 MB.
    • Intenta crear un contenedor: docker run --rm malicious:latest.
    • El proceso debe fallar con un error de “memory limit exceeded” y containerd debe permanecer activo.
  4. Monitorizar

    • En Prometheus, verifica que la métrica containerd_process_resident_memory_bytes nunca supera el 80 % del límite.
    • Revisa los logs de containerd para asegurarte de que no aparecen mensajes de OOM.

Notas adicionales

  • Limitar la memoria del daemon protege al host, pero no sustituye la actualización del código vulnerable. Siempre aplique los parches tan pronto como estén disponibles.
  • La firma de imágenes añade una capa de confianza, pero requiere gestión de claves y políticas de rotación; evalúe el coste operativo antes de adoptarla.
  • En clusters con alta rotación de pods, considere habilitar PodSecurityAdmission para forzar runAsNonRoot y reducir la exposición a archivos de usuarios dentro de la imagen.
  • Si su infraestructura usa CRI‑O, revise su versión; a partir de 1.28 incluye la misma mitigación que containerd.
  • Mantenga una lista de “imágenes de base” aprobadas y escanee periódicamente con herramientas como Trivy o Clair; esto detecta no solo este patrón, sino también otras vulnerabilidades de capas.