- CVE-2026-31431 Copy Fail permite que un usuario local sin privilegios obtenga una shell
root, y dentro de contenedores rootless de Podman también puede elevar privilegios arootdentro del contenedor - Los contenedores rootless de Podman combinan namespaces de usuario, separación de UID y Linux capabilities para mapear el
rootdentro del contenedor a un usuario sin privilegios en el host y restringir los permisos en el host - En las pruebas, el usuario
foode un contenedor rootless non-root pudo convertirse enrootdentro del contenedor tras ejecutar Copy Fail, pero sus permisos quedaron limitados al alcance permitido para el usuario sin privilegiosbardel host, y no pudo leer archivos del host propiedad deroot - Si se aplica
--security-opt=no-new-privilegeso--cap-drop=all, incluso después de ejecutar Copy Fail la shell se mantiene comofooy con capabilitiesnone, lo que impide obtener de inmediato una shellrooty elevar capabilities - El efecto de Copy Fail puede persistir más allá del ciclo de vida del contenedor, por lo que se requiere aplicar el parche del kernel y reiniciar; además, se debe implementar defensa en profundidad con sistema de archivos raíz de solo lectura, límites de recursos con cgroups, imágenes runtime ligeras y firewall
Alcance de exposición de Copy Fail y los contenedores rootless de Podman
- CVE-2026-31431 fue divulgada el 29 de abril en copy.fail, y al ejecutar el script de Python publicado, un usuario local sin privilegios puede obtener una shell
root - Copy Fail también puede explotarse dentro de contenedores Linux, y en contenedores rootless de Podman es posible obtener una shell
rootdentro del contenedor - En las pruebas, el
rootdel contenedor quedó limitado, a nivel del host, al rango de permisos del usuario sin privilegiosbarque ejecutó el contenedor - La implementación rootless de Podman combina namespaces de usuario, separación de UID y Linux capabilities para restringir los permisos en el host de los procesos del contenedor
- Copy Fail demuestra que los contenedores rootless no son inmunes a la vulnerabilidad, pero que la configuración de Podman puede reducir el alcance del ataque después del compromiso
Cómo funcionan los contenedores rootless
-
Ejemplo básico: el usuario sin privilegios
barejecuta un servidor HTTP- El entorno de ejemplo consiste en un usuario sin privilegios
bar, con UID1001, que construye con Podman una imagen basada enubuntu:latesty ejecutapython3 -m http.server - Visto desde el host con
ps, el procesopython3se ejecuta como propiedad del usuariobar - Podman usa un modelo fork/exec, por lo que los procesos del contenedor se convierten en descendientes del proceso
podman run, y la separación de UID habitual permite aislar los procesos del contenedor delrootdel host o de otros usuarios - En una configuración típica de Docker, aunque un usuario sin privilegios ejecute
docker run, el cliente de Docker se comunica con un daemon con privilegios de root, y como ese daemon finalmente crea los procesos del contenedor, estos pueden verse comorooten el host
- El entorno de ejemplo consiste en un usuario sin privilegios
-
Rootless rootful
- Si la imagen del contenedor no tiene una instrucción
USERexplícita ni un flag--user, normalmente ejecuta el comando del contenedor comorootinterno - En la salida de
podman top, el proceso del servidor HTTP aparece mapeado al usuario1001del host, pero dentro del contenedor se ejecuta comoroot - Esta configuración se ejecuta como usuario sin privilegios en el host, pero como
rootdentro del contenedor, es decir, un estado rootless rootful
- Si la imagen del contenedor no tiene una instrucción
-
Namespaces de usuario
- Los contenedores rootless de Podman usan namespaces de usuario para mapear de forma distinta los UID/GID dentro y fuera del contenedor
- En el ejemplo, el
rootcon UID0dentro del contenedor se mapea al UID1001del host, correspondiente abar - La configuración
bar:165536:65536en/etc/subuiddefine el rango de UID que pueden asignarse a los procesos en el namespace debar - En el ejemplo, además del UID
1001debar, se pueden asignar a sus procesos UID desde165536hasta231072 - Si se ejecuta
sleepcomo el usuariowww-datadentro del contenedor, internamente aparece comowww-data, pero en el host se muestra como165568 - Al entrar al namespace de usuario con
podman unshare, el directorio home que en el host pertenece abar:barse ve comoroot:rootdentro del namespace - Docker también soporta namespaces de usuario, pero requiere configuración adicional y solo permite un namespace de usuario, mientras que Podman ejecuta los contenedores rootless de cada usuario UNIX dentro del namespace de ese usuario
-
Operaciones privilegiadas y Linux capabilities
- Podman usa Linux capabilities para otorgar a los procesos del contenedor privilegios de root de forma granular
- Durante la construcción de la imagen, tareas como
apt installse vuelven posibles mediante una combinación de capabilities comochown,dac_override,fowner,setgid,setuid,net_bind_serviceysys_chroot - Si se eliminan todas las capabilities con
podman build --cap-drop=all,aptfalla en operaciones comosetgroups,setegid,seteuidychown, y la construcción de la imagen no se completa - También es posible agregar solo las capabilities necesarias; en el ejemplo, se añaden
CAP_SETUID,CAP_SETGID,CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNERpara realizar la instalación de paquetes - En su estado de ejecución predeterminado, el servidor HTTP se ejecuta como
rootdentro del contenedor y tiene muchas effective capabilities, comoCHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT - Como el servidor HTTP no necesita esos privilegios, se pueden eliminar todas las capabilities con
podman run --cap-drop=all; en ese caso,podman topmuestra las effective capabilities comonone
-
Rootless non-root
- Para ejecutar el servidor HTTP también como usuario sin privilegios dentro del contenedor, se puede usar un usuario existente en
/etc/passwd, comowww-data, o crear un usuario dedicado durante la construcción de la imagen - En el ejemplo, se crea el usuario y grupo
foocon UID1002, se otorgan permisos de lectura a/var/www/htmly luego se configuraUSER foo:foo - Si esta imagen se ejecuta con
--cap-drop=all, el proceso queda comofoodentro del contenedor, UID166537en el host y effective capabilitiesnone - Los procesos del contenedor deben ejecutarse con el mínimo privilegio necesario; por ejemplo, si
foonecesita enlazarse al puerto privilegiado80, se debe añadir--cap-add=CAP_NET_BIND_SERVICE - Las formas de ejecutar un contenedor se dividen en cuatro tipos
- usuario
rootdel host + contenedorroot: root rootful - usuario
rootdel host + usuario sin privilegios en el contenedor: root non-root - usuario sin privilegios del host + contenedor
root: rootless rootful - usuario sin privilegios del host + usuario sin privilegios en el contenedor: rootless non-root
- usuario
- Podman facilita la ejecución de contenedores rootless rootful, y si es posible ejecutar los procesos del contenedor como usuario sin privilegios, también es relativamente fácil crear una configuración rootless non-root
- Para ejecutar el servidor HTTP también como usuario sin privilegios dentro del contenedor, se puede usar un usuario existente en
Montajes bind y aislamiento de UID
- Cuando se monta un directorio del host en el contenedor, la posibilidad de acceder a archivos propiedad de
rootdel host,bardel host yfoodel namespace cambia según el mapeo de UID - En el ejemplo, se crean en el directorio
/var/lib/bar/testlos archivosroot.txt, propiedad derootdel host, ybar.txt, propiedad debardel host, y se montan en el contenedor como/testcon permisos de lectura/escritura - Si el contenedor se ejecuta como
foo, el archivo propiedad debardel host se ve dentro del contenedor comoroot:root, y el archivo propiedad derootdel host aparece comonobody:nogroupporque no está mapeado al namespace foodentro del contenedor no puede leerbar.txtniroot.txt, y un rootless non-root ofrece aislamiento adicional frente a un rootless rootfulfoo.txt, creado porfooen el directorio montado, aparece en el host como propiedad del UID166537, y el usuariobardel host no puede leer su contenido- Si el contenedor se ejecuta como
rootinterno, elrootdel namespace puede leer el archivo propiedad debardel host y el archivo propiedad defoo, pero no puede leer el archivoroot.txtpropiedad derootdel host - Si se ejecuta como
rootinterno con--cap-drop=all, tampoco puede leer el archivo defooy solo puede leer el archivo propiedad debardel host
Prueba de Copy Fail
-
Condiciones de prueba
- En la prueba de Copy Fail se usa la versión del exploit del commit publicado originalmente
8e918b5 - La imagen de contenedor de ejemplo agrega
curla una imagen existente de servidor HTTP para poder descargar el script del exploit desde dentro del contenedor - La imagen se construye con el nombre
copyfail - El kernel de prueba es
6.12.74+deb13+1-amd64de Debian, y en Debian se considera que las versiones recientes por debajo de6.12.85aún pueden usarse como kernels no parchados - Normalmente, si el usuario sin privilegios
foollama asu, se le solicita la contraseña deroot - En cada prueba, el usuario del contenedor descarga el script de Copy Fail en
/tmpy lo ejecuta; si obtiene una shell deroot, llama asleep - Como Copy Fail persiste más allá del ciclo de vida del contenedor, la VM se reinicia antes de cada prueba
- En la prueba de Copy Fail se usa la versión del exploit del commit publicado originalmente
-
Resultados en rootless rootful
- Si el contenedor se ejecuta con
--user=root, el proceso dentro del contenedor ya esroot - En este estado, al ejecutar el script de Copy Fail y llamar a
su, se obtiene una shelluid=0(root), pero como el usuariorootya puede abrir otra shell de root consusin contraseña, en la práctica Copy Fail no agrega nada - En
podman top,/bin/bash,python3 copy_fail_exp.py,suysleepaparecen todos comorootdentro del contenedor y como usuario1001en el host - Se mantiene el mismo conjunto de capabilities, y aparecen
CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT - El
rootinterno puede leerbar.txtyfoo.txten el/testmontado, pero no puede leerroot.txt, propiedad derootdel host
- Si el contenedor se ejecuta con
-
Resultados en rootless non-root
- Después de ejecutar el contenedor como
foo, al correr el script de Copy Fail y llamar asu, se elevan privilegios arootdentro del contenedor - El
idde la shell resultante aparece comouid=0(root) gid=1002(foo) groups=1002(foo) - En
podman top, el/bin/bashinicial, el proceso que ejecuta el exploit y la llamada asuaparecen con UID166537en el host, usuariofooen el contenedor y capabilitiesnone - Tras la elevación de privilegios,
[sh]ysleepaparecen como usuario1001en el host y usuariorooten el contenedor, obteniendo el mismo conjunto de capabilities que en rootless rootful - Incluso el
rootdel contenedor con privilegios elevados no puede leerroot.txt, propiedad derootdel host - En este estado, el contenedor queda comprometido, pero el alcance del ataque se limita al contenedor y a lo que puede hacer el usuario sin privilegios
baren el host
- Después de ejecutar el contenedor como
-
Resultados al aplicar
no-new-privileges- Podman permite usar
--security-opt=no-new-privilegespara impedir que el proceso del contenedor obtenga más privilegios que los que tenía al iniciarse - Si se aplica esta opción a un contenedor rootless non-root y se ejecuta Copy Fail, la shell se abre, pero sigue en estado
uid=1002(foo) - En
podman top, todos los procesos se mantienen con UID166537en el host, usuariofooen el contenedor y capabilitiesnone - También en el
/testmontado,foosolo puede leer su propio archivo y no puede leerbar.txtniroot.txt - El contenedor queda comprometido, pero sigue limitado al usuario interno sin privilegios
fooy a un estado sin capabilities
- Podman permite usar
-
Resultados al aplicar
--cap-drop=all- Aunque se ejecute un contenedor rootless non-root con
--cap-drop=all,foooriginalmente no tiene capabilities - En este estado, al ejecutar Copy Fail y llamar a
su, la shell abierta sigue siendouid=1002(foo) - En
podman top,/bin/bash, la ejecución del exploit,su, la shell ysleeppermanecen todos comofooy con capabilitiesnone - El exploit no logra obtener una shell de
root, yfoosolo puede leer su propio archivo en/test - Este resultado es similar al de la prueba con
no-new-privileges, y ambas medidas pueden usarse juntas para reducir eficazmente la exposición a capabilities
- Aunque se ejecute un contenedor rootless non-root con
-
Persistencia del exploit
- Aunque
no-new-privilegeso--cap-drop=allpudieron impedir la obtención inmediata de una shell derooty de capabilities, el efecto del exploit en sí permanece - Si después se ejecuta un contenedor nuevo sin restricciones de capabilities, el usuario sin privilegios
foodel contenedor puede convertirse enrootdel contenedor con solo llamar asu - Por lo tanto, sigue siendo necesario aplicar el parche del kernel y reiniciar
- Aunque
Estrategias de defensa en profundidad
-
Imágenes de solo lectura
- Si agregas
--read-onlyapodman run, el sistema de archivos raíz del contenedor se monta como solo lectura - Por defecto, Podman monta algunos directorios como escribibles, como
/tmp,/runy/var/tmp, así que para dejarlo completamente en solo lectura también hay que agregar--read-only-tmpfs=false - Si un contenedor de solo lectura es comprometido, no se permiten escrituras en el sistema, lo que puede limitar algunos ataques posteriores a la explotación
- Aun así, como la salida de
curlpuede enviarse por tubería apython3, configurar solo lectura por sí sola no impide la ejecución del exploit - El servidor HTTP de
python3del ejemplo no necesita escribir en el sistema de archivos, por lo que esta opción puede usarse de forma segura - Muchas imágenes preconstruidas asumen acceso de escritura a ciertos directorios, por lo que pueden no funcionar correctamente con un sistema de archivos raíz de solo lectura
- Un sistema de archivos raíz de solo lectura es independiente de los volúmenes escribibles conectados al contenedor, y en caso de compromiso aún se podrá escribir en esos directorios montados
- Si agregas
-
Límites de recursos
- Docker y Podman pueden usar cgroups para limitar los recursos disponibles para los contenedores
- Un contenedor no necesita memoria, CPU ni PID ilimitados
- Puedes revisar el uso de recursos del contenedor con
podman statsy aplicar límites en consecuencia
-
Limitar los binarios disponibles
- El ejemplo usa la imagen
ubuntupor simplicidad, pero la imagenubuntuincluye muchos binarios que un atacante podría usar si el contenedor es comprometido - Para ejecutar el servidor HTTP no se necesita la mayoría de esos binarios
- Conviene que la imagen de runtime sea lo más liviana posible
- Puedes usar compilaciones multietapa para separar el entorno de build del entorno de runtime
- También puedes basarte en imágenes específicas para un propósito como python3, variantes
-slimde Debian o distribuciones más pequeñas comoalpine - Si es compatible con el proceso del contenedor, puedes usar distroless images o
scratchpara crear un runtime sin shell, administrador de paquetes ni utilidades del sistema
- El ejemplo usa la imagen
-
Firewall
- Puedes usar
iptablesonftablespara restringir con firewall los procesos del contenedor - Solo se deben permitir las conexiones entrantes y salientes estrictamente necesarias para el proceso del contenedor
- En el ejemplo del servidor HTTP no se necesitan conexiones a DNS ni a servidores locales o remotos, así que se puede limitar para permitir solo paquetes
tcpprovenientes de conexiones entrantes ya establecidas
- Puedes usar
Implicaciones operativas
- Los contenedores rootless estándar de Podman ofrecen por defecto un mejor aislamiento que una configuración estándar de contenedores Docker
- Docker también permite ejecución rootless y uso de namespaces de usuario sin privilegios, pero requiere más trabajo de configuración que Podman y también influyen las diferencias de arquitectura
- Docker sigue siendo ampliamente usado, y herramientas de self-hosting como Dokku, Kamal, Coolify y Dokploy también usan Docker por defecto
- Si ejecutas imágenes de Docker Hub sin revisarlas lo suficiente o sin aplicar medidas de bloqueo, el servicio puede quedar expuesto con una superficie de ataque mayor a la necesaria
- Hay que entender los detalles de implementación de las imágenes de contenedor
- Debes saber con qué usuario o usuarios se ejecutan los procesos del contenedor
- Debes saber de qué directorios del sistema de archivos raíz dependen los procesos del contenedor
- Debes distinguir entre las capabilities de Linux necesarias y las que no lo son
- Al combinar los distintos mecanismos que ofrecen Podman y los contenedores, se puede reforzar el contenedor y reducir el radio de impacto en caso de compromiso
- Según la carga de trabajo, no se debe depender del contenedor como único límite de seguridad
- Usar contenedores junto con máquinas físicas o virtuales separadas puede proporcionar un aislamiento efectivo
- Podman ofrece una forma de aislar cargas de trabajo incluso dentro del mismo host ejecutando cada una con un usuario sin privilegios distinto y su propio namespace de usuario
1 comentarios
Opiniones en Lobste.rs
Hay que enfocarse en el comportamiento primitivo que hace posible la vulnerabilidad, más que en el exploit publicado
Esta vulnerabilidad permite escribir en la page cache sin importar si es de solo lectura, así que un contenedor malicioso puede alterar páginas que pertenecen a archivos de la imagen base de overlayfs, y según cómo se desplieguen los contenedores, el impacto puede propagarse a otros contenedores
En una configuración rootless como esta, los objetivos serían otros contenedores que se ejecuten con el mismo usuario en el sistema host
Otra forma de explotación sería ejecutar o localizar un contenedor basado en una imagen base que ya se sepa que está en uso, alterar la page cache dentro de ese contenedor y luego hacer que otro contenedor que comparta el mismo runtime y los mismos datos de overlayfs ejecute ese código
Rootless y los user namespaces son importantes, pero aquí no ayudan mucho y, como dice el sitio de copy.fail, en contenedores habría que considerar bloquear con seccomp la llamada al sistema
socket(AF_ALG, ...)Estaría bien explicar un poco más qué significa exactamente “según cómo se desplieguen los contenedores”
Una ventaja de Podman rootless es que, dependiendo de la carga de trabajo, no hace falta ejecutar los contenedores con el mismo usuario en el host
Si te refieres al caso de correr varios contenedores rootless con el usuario principal de la estación de trabajo, estoy de acuerdo, pero en servidores cada uno puede aislarse con un usuario distinto y la misma imagen de contenedor puede ejecutarse con diferentes usuarios no privilegiados
Eso es bastante distinto del valor predeterminado de Docker, que ejecuta la mayoría de las cosas como
root, pero también escribí al final del texto que eso no constituye el límite de seguridad definitivo, y que repartir contenedores rootless entre varios usuarios sin privilegios depende del caso de usoAlgunas cargas de trabajo específicas sí las aíslo con VM
Me pregunto si con que rootless y los user namespaces no ayudan aquí te refieres a impedir la explotación
Aún no he usado seccomp con políticas explícitas en contenedores, así que no lo traté, pero es un buen motivo para investigarlo más
Me gustan Podman y los contenedores rootless, pero al ver CopyFail llegué a la misma conclusión que el comentario hermano
Incluso con las ventajas extra de control de acceso de
podman+rootless, al final vuelve a confirmarse el consejo clásico de que los contenedores no son un límite de seguridad, y un solo exploit del kernel puede abrirlo todoSolo administro sistemas como hobby, pero como tendencia nueva en este espacio vi el backend libkrun para crun con podman
La promesa es que puede manejar la mayoría de las cargas de trabajo en contenedores tal cual, pero ejecutándolas internamente en una MicroVM con un kernel invitado aparte; no sé bien qué tan madura está, cuánto uso real tiene ni el nivel de auditoría de seguridad, y parte de eso se ve bastante de punta
Como las MicroVM están siendo adoptadas activamente por herramientas de programación con LLM, puede que ese estado no dure mucho
podman machinetambién me pareció prometedor, pero por desgracia parece pensado solo para estaciones de trabajo de desarrolladores y con un modelo de una sola VM de ejecución de contenedores por sistema hostAun así, me parece que decir “los contenedores no son un límite de seguridad” es demasiado simplista. Claramente sí son un límite de seguridad, solo que no tan fuerte como quisiéramos creer
En despliegues locales esa línea es un poco más difusa
Desde el punto de vista del hardware, una VM no es intrínsecamente más segura que un proceso, pero hay tres razones por las que el límite es más defendible
Escapar de una VM es menos común que explotar una system call, así que hay más margen para aplicar mitigaciones de side channels sin castigar el rendimiento
La interfaz host de una VM es mucho más simple. Un dispositivo de bloques tiene una interfaz de lectura y escritura por bloques, y un dispositivo de red envía y recibe frames
La llamada
setsockoptque Linux o *BSD exponen en sockets es una superficie de ataque mucho mayor que la mayoría de los drivers emulados o paravirtualizados, y aun así eso es solo una parte muy pequeña de toda la superficie de ataque del kernelLa interfaz de una VM también tiene mucho menos estado. Hay transacciones en curso en anillos de un modelo de solicitud-respuesta, pero fuera de eso casi nada
Cosas como credenciales, UID, GID y tablas de descriptores de archivo añaden complejidad basada en estado dentro del kernel, y si hay bugs un proceso puede explotarlos
La dificultad de la variante de estación de trabajo es que vuelve a introducir esa complejidad
Por ejemplo, la capa base del contenedor podría exponerse como un dispositivo de bloques con un sistema de archivos inmutable, pero los volúmenes y carpetas compartidas probablemente se montarían con 9pfs o VirtIO-FS, es decir, 9p o FUSE sobre VirtIO
Entonces la superficie de ataque vuelve a crecer
Con suerte, eso obligaría a una cadena de explotación
Estoy más familiarizado con FreeBSD, y normalmente ahí los componentes que proveen dispositivos paravirtualizados o emulados se aíslan con sandboxing usando Capsicum, así que primero habría que comprometer un proceso del host y después romper también el kernel para acceder a algo a lo que la VM no tenía permiso
Pero sin ese sandboxing adicional se vuelve al mundo en que escapar de un contenedor permite hacer todo lo que el usuario puede hacer, y en escritorio eso no es mucho mejor que comprometer
rootEn lo personal prefiero más gVisor. No es un runtime VMM, pero lleva años existiendo, también lo usan empresas como Tencent, y en mi entorno, donde todos los contenedores ya corren dentro de VM de Proxmox, encaja bien
Otra cosa que estoy probando es syd-oci, que parece haber recibido algo menos de atención frente a las recomendaciones habituales de MicroVM o gVisor
Gracias también por la referencia a libkrun, parece una posibilidad prometedora
También parece bastante probable que eso lleve a auditorías de seguridad