Introducción
El socket /var/run/docker.sock concede acceso root a cualquier usuario en el grupo docker. Un escape de contenedor o una vulnerabilidad en el daemon escala a control total del host sin intermediarios. Rootless Docker resuelve ese vector específico: dockerd corre como usuario no privilegiado, el socket queda en $XDG_RUNTIME_DIR/docker.sock (típicamente /run/user/1000/docker.sock) y cada usuario obtiene su propio daemon. El problema es que la solución introduce un trade-off que pocos equipos evalúan antes de migrar a producción: las user namespaces reabren interfaces del kernel que históricamente solo un root confiable podía invocar, y las flags seccomp=unconfined y apparmor=unconfined —requisito operativo para BuildKit rootless— desactivan dos capas de mitigación que el propio Docker aplica por defecto.
Qué ocurrió
Rootless Docker no elimina la necesidad de CAP_SYS_ADMIN para crear namespaces PID, mount y network. Lo que hace es encajar esa capacidad dentro de una user namespace. El helper RootlessKit arranca el daemon dentro de ese namespace y configura mapeos UID/GID mediante newuidmap y newgidmap, binarios instalados con el bit setuid. El proceso interno se ve a sí mismo como UID 0, pero el kernel lo traduce al UID real del host (por ejemplo, 1000). Los UIDs 1–65535 del namespace se mapean a un rango declarado en /etc/subuid. BuildKit opera con el mismo patrón: buildkitd se levanta dentro de la user namespace y crea sus child namespaces (PID, mount, UTS) para cada RUN de un Dockerfile.
El resultado práctico es que un proceso comprometido dentro de un build rootless no posee privilegios reales sobre el host. No puede escribir en /etc, no accede a /proc/sys del host, no manipula interfaces de red fuera de su netfilter. La superficie de daño queda contenida al rango de UIDs asignados. Hasta ahí, la mejora es concreta y medible.
Impacto para DevOps / Infraestructura / Cloud / Seguridad
La contención de UID es real, pero la superficie de ataque se desplaza hacia el kernel. Al entrar en una user namespace, el proceso obtiene un conjunto completo de capabilities dentro de ese scope: CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_SYS_MODULE (si el kernel lo permite). Estas capabilities exponen la API de networking, el subsistema de mounts, el motor de reglas de iptables/nftables y rutas de código que durante décadas asumieron un único llamador: root. Cuando un desarrollador del kernel escribe un handler para setsockopt, mount o una extensión de netfilter, valida los parámetros bajo la premisa de que quien llama ya tiene permisos totales y «sabe lo que hace». La user namespace elimina esa premisa. Un buffer overflow en el stack de networking, un use-after-free en el manejo de mounts o una lógica incorrecta en iptables dejan de requerir acceso root previo y pasan a ser explotables por cualquier usuario local que pueda crear una user namespace.
En 2025, Qualys divulgó tres métodos para evadir las restricciones de Ubuntu sobre namespaces no privilegiados, obteniendo capacidades administrativas completas. Adicionalmente, varios CVEs reportados entre 2025 y 2026 dependieron de exploits vía user namespace para comprometer el sistema. Andy Lutomirski, desarrollador del kernel con trabajo extensivo en namespaces, resumió el riesgo en una frase que se volvió referencia: «I’ll eat my hat if there are no privilege escalations in there.» No es una advertencia teórica; es una predicción que los incidentes posteriores confirmaron.
Para equipos que corren pipelines CI/CD en runners de GitLab, GitHub Actions self-hosted o nodos de Azure Kubernetes Service (AKS) con Docker-in-Docker rootless, el impacto se multiplica: cada build es un proceso que puede invocar esas interfaces, y un artefacto malicioso en una imagen base activa el vector sin intervención humana.
Detalles técnicos
Mecanismo de mapeo y privilegios. RootlessKit invoca unshare(CLONE_NEWUSER) para crear la user namespace. Dentro de ella, el proceso tiene CAP_SYS_ADMIN, lo que le permite anidar namespaces de tipo PID, mount, network, IPC y UTS. El kernel permite este anidamiento porque la user namespace actúa como frontera de confianza. Los helpers newuidmap/newgidmap (con setuid, owner root) escriben en /proc/
Qué desactiva cada flag unconfined. Seccomp es un filtro BPF del kernel que intercepta llamadas al sistema (open, execve, socket, mount, etc.) y descarta las que no están en una whitelist. El perfil default de Docker bloquea aproximadamente 44 syscalls. –security-opt seccomp=unconfined elimina ese filtro: el proceso puede invocar cualquier syscall disponible, incluyendo kexec_load, reboot, bpf y ptrace sobre procesos del host si las capabilities lo permiten. AppArmor restringe recursos por perfil: rutas de archivos, direcciones de red, capabilities específicas. –security-opt apparmor=unconfined desactiva la evaluación del perfil, dejando al proceso sin control sobre qué archivos puede abrir o qué puertos puede escuchar fuera de su network namespace.
Requisito operativo. BuildKit rootless dentro de un contenedor (DinD) necesita ambas flags porque buildkitd invoca mount con tipos como overlayfs y bind, y manipula cgroup v2, operaciones que los perfiles default de seccomp y AppArmor bloquean. No es un ajuste opcional: sin esas flags, los builds fallan con EPERM.
Limitaciones funcionales. Rootless Docker no soporta –privileged, no puede usar cgroups v1 (requiere cgroups v2 con delegate=yes en el usuario), y el rendimiento de red baja al usar slirp4netns o vpnkit en lugar de veth + bridge con CAP_NET_ADMIN real.
Qué deberían hacer los administradores y equipos técnicos
Restringir la creación de user namespaces a nivel kernel. En Ubuntu 22.04+/24.04 y kernels ≥ 5.11, verificá y fijá el parámetro:
# Verificar estado actual
cat /proc/sys/kernel/unprivileged_userns_clone # 1 = habilitado
cat /proc/sys/user/max_user_namespaces # límite global
# Deshabilitar para usuarios no privilegiados (requiere root)
echo «kernel.unprivileged_userns_clone=0» >> /etc/sysctl.d/99-hardening.conf
echo «user.max_user_namespaces=0» >> /etc/sysctl.d/99-hardening.conf
sysctl –system
Si necesitás user namespaces para contenedores rootless, asigná un límite estricto (user.max_user_namespaces=16) en lugar de dejarlo en el default (que suele ser INT_MAX/2).
Mantener seccomp activo fuera de BuildKit. No apliques seccomp=unconfined al daemon ni a contenedores de runtime. Si tu pipeline requiere DinD rootless, ejecutá el build en un nodo efímero con red aislada y eliminá el contenedor al finalizar. Para producción, preferí BuildKit en modo rootless directo sobre el host (sin contenedor intermedio) donde no necesitás las flags unconfined.
Auditar /etc/subuid y /etc/subgid. Confirmá que solo los usuarios que requieren Docker tengan entradas asignadas. Cada rango de 65.536 UIDs es un pool que un proceso comprometido puede usar para crear archivos con ownership variado.
# Listar asignaciones actuales
cat /etc/subuid
cat /etc/subgid
# Verificar que ningún usuario tenga rangos excesivos
awk -F: ‘{print $1, $2, $3}’ /etc/subuid | sort -k3 -rn | head
Parchear con urgencia. Aplicá los updates de kernel que corrigen los CVEs de user namespace divulgados por Qualys. En Ubuntu, verificá que el kernel en uso sea ≥ 5.15.0-119 (22.04) o ≥ 6.8.0-31 (24.04), que incluyen los fixes para las tres técnicas de bypass. En AKS, actualizá el node image pool a la versión que incluya el parche del kernel de Microsoft (Mariner 2.0 ≥ 20240901 o Azure Linux 3.0 ≥ 20250101).
Monitoreo. Activá auditd con reglas sobre unshare, setns y escritura a /proc/*/uid_map. Un evento de creación de user namespace desde un proceso que no es RootlessKit o buildkitd es una señal de anomalía.
Conclusión
Rootless Docker reduce significativamente el blast radius de un escape de contenedor: un proceso comprometido queda atrapado en un rango de UIDs sin acceso a APIs privilegiadas del host. Pero «sin root» no significa «sin superficie de ataque». Las user namespaces habilitan código del kernel que no fue escrito para recibir llamadas de usuarios no confiables, y las flags unconfined que BuildKit necesita desactivan las dos capas de mitigación que Docker provee por defecto. El equipo de infraestructura debe tratar rootless como una mitigación parcial: útil, necesaria, pero que exige hardening adicional a nivel kernel, políticas de seccomp personalizadas y un ciclo de parcheo del kernel que no puede relajarse.
Fuentes
- https://www.kenmuse.com/blog/rootless-docker-and-its-hidden-security-trade-offs/
