Conteneurs rootless de Podman et l’exploit Copy Fail
(garrido.io)- CVE-2026-31431 Copy Fail permet à un utilisateur local non privilégié d’obtenir un shell
root, et peut aussi permettre une élévation versrootà l’intérieur d’un conteneur rootless Podman - Les conteneurs rootless de Podman combinent les espaces de noms utilisateur, la séparation des UID et les Linux capabilities pour mapper le
rootdu conteneur vers un utilisateur non privilégié sur l’hôte et limiter les privilèges sur l’hôte - Lors des tests, l’utilisateur
food’un conteneur rootless non-root a pu devenirrootà l’intérieur du conteneur après l’exécution de Copy Fail, mais ses privilèges restaient limités à ce que l’utilisateur non privilégiébarpouvait faire sur l’hôte, sans pouvoir lire les fichiers appartenant àrootsur l’hôte - L’application de
--security-opt=no-new-privilegesou--cap-drop=allfait qu’après l’exécution de Copy Fail, le shell reste en tant quefooavec des capabilities ànone, ce qui empêche l’obtention immédiate d’un shellrootet l’élévation des capabilities - Les effets de Copy Fail peuvent persister au-delà du cycle de vie du conteneur, ce qui impose un correctif noyau et un redémarrage ; il faut aussi appliquer une défense en profondeur avec un système de fichiers racine en lecture seule, des limites de ressources via cgroups, des images de runtime minimales et un pare-feu
Portée de l’exposition de Copy Fail et des conteneurs rootless Podman
- CVE-2026-31431 a été rendu public le 29 avril sur copy.fail et l’exécution du script Python publié permet à un utilisateur local non privilégié d’obtenir un shell
root - Copy Fail peut aussi être exploité dans des conteneurs Linux, et permet également d’obtenir un shell
rootà l’intérieur des conteneurs rootless Podman - Lors des tests, le
rootdu conteneur restait limité, au niveau de l’hôte, au périmètre de privilèges de l’utilisateur non privilégiébarqui avait lancé le conteneur - L’implémentation rootless de Podman combine les espaces de noms utilisateur, la séparation des UID et les Linux capabilities pour limiter les privilèges des processus du conteneur sur l’hôte
- Copy Fail montre que les conteneurs rootless ne sont pas immunisés contre la vulnérabilité, mais que la configuration de Podman peut réduire la portée d’une attaque après compromission
Fonctionnement des conteneurs rootless
-
Exemple de base : l’utilisateur non privilégié
barexécute un serveur HTTP- Dans l’exemple, l’utilisateur non privilégié
bar, avec l’UID1001, construit avec Podman une image basée surubuntu:latestet exécutepython3 -m http.server - Vu depuis l’hôte avec
ps, le processuspython3s’exécute sous l’utilisateurbar - Podman utilise un modèle fork/exec, de sorte que les processus du conteneur deviennent des descendants du processus
podman run, et une séparation classique des UID permet d’isoler les processus du conteneur durootde l’hôte et des autres utilisateurs - Dans une configuration Docker classique, même si un utilisateur non privilégié exécute
docker run, le client Docker communique avec un démon disposant des privilèges root, et c’est ce démon qui crée finalement les processus du conteneur ; sur l’hôte, ceux-ci peuvent donc apparaître comme appartenant àroot
- Dans l’exemple, l’utilisateur non privilégié
-
Rootless rootful
- Une image de conteneur exécute généralement sa commande en tant que
rootinterne si aucune directiveUSERexplicite ni aucun flag--usern’est défini - Dans la sortie de
podman top, le processus du serveur HTTP est mappé sur l’utilisateur hôte1001, mais s’exécute commerootà l’intérieur du conteneur - Cette configuration correspond à un état rootless rootful : non privilégié sur l’hôte, mais
rootdans le conteneur
- Une image de conteneur exécute généralement sa commande en tant que
-
Espaces de noms utilisateur
- Les conteneurs rootless de Podman utilisent des espaces de noms utilisateur pour mapper différemment les UID/GID à l’intérieur et à l’extérieur du conteneur
- Dans l’exemple, l’UID
0durootà l’intérieur du conteneur est mappé sur l’UID1001debarsur l’hôte - Le paramètre
bar:165536:65536dans/etc/subuiddéfinit la plage d’UID pouvant être attribuée aux processus dans l’espace de noms debar - Dans l’exemple, en plus de l’UID
1001debar, les UID de165536à231072peuvent être attribués aux processus debar - Si l’on exécute
sleepavec l’utilisateur internewww-data, il apparaît commewww-datadans le conteneur mais comme165568sur l’hôte - En entrant dans l’espace de noms utilisateur avec
podman unshare, le répertoire personnel qui appartient àbar:barsur l’hôte apparaît comme appartenant àroot:rootà l’intérieur de l’espace de noms - Docker prend aussi en charge les espaces de noms utilisateur, mais cela demande une configuration distincte et n’autorise qu’un seul espace de noms utilisateur, alors que Podman exécute les conteneurs rootless de chaque utilisateur UNIX dans l’espace de noms de cet utilisateur
-
Opérations privilégiées et Linux capabilities
- Podman utilise les Linux capabilities pour accorder des privilèges root granulaires aux processus du conteneur
- Lors de la construction d’image, des opérations comme
apt installdeviennent possibles grâce à une combinaison de capabilities telles quechown,dac_override,fowner,setgid,setuid,net_bind_serviceetsys_chroot - Si l’on retire toutes les capabilities avec
podman build --cap-drop=all,aptéchoue sursetgroups,setegid,seteuid,chownet d’autres opérations, ce qui fait échouer la construction de l’image - Il est aussi possible d’ajouter uniquement les capabilities nécessaires ; dans l’exemple,
CAP_SETUID,CAP_SETGID,CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNERsont ajoutées pour effectuer l’installation des paquets - Dans son état d’exécution par défaut, le serveur HTTP tourne en tant que
rootà l’intérieur du conteneur et dispose de nombreuses capabilities effectives commeCHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT - Comme le serveur HTTP n’a pas besoin de ces privilèges, on peut supprimer toutes les capabilities avec
podman run --cap-drop=all; dans ce cas,podman topaffichenonepour les capabilities effectives
-
Rootless non-root
- Pour exécuter le serveur HTTP en tant qu’utilisateur non privilégié aussi à l’intérieur du conteneur, on peut utiliser un utilisateur existant dans
/etc/passwd, par exemplewww-data, ou créer un utilisateur dédié lors de la construction de l’image - Dans l’exemple, un utilisateur et un groupe
foo, avec l’UID1002, sont créés, des droits de lecture sont accordés sur/var/www/html, puisUSER foo:fooest défini - Si cette image est lancée avec
--cap-drop=all, le processus s’exécute commefoodans le conteneur, avec l’UID hôte166537et des capabilities effectives ànone - Les processus du conteneur doivent s’exécuter avec le minimum de privilèges nécessaire ; par exemple, si
foodoit se lier au port privilégié80, il faut ajouter--cap-add=CAP_NET_BIND_SERVICE - On peut distinguer quatre modes d’exécution des conteneurs
- utilisateur hôte
root+ conteneurroot: root rootful - utilisateur hôte
root+ utilisateur non privilégié dans le conteneur : root non-root - utilisateur hôte non privilégié + conteneur
root: rootless rootful - utilisateur hôte non privilégié + utilisateur non privilégié dans le conteneur : rootless non-root
- utilisateur hôte
- Podman facilite l’exécution de conteneurs rootless rootful, et si les processus du conteneur peuvent être exécutés sous un utilisateur non privilégié, une configuration rootless non-root reste relativement simple à mettre en place
- Pour exécuter le serveur HTTP en tant qu’utilisateur non privilégié aussi à l’intérieur du conteneur, on peut utiliser un utilisateur existant dans
Montages bind et isolation des UID
- Lorsque l’on monte un répertoire de l’hôte dans un conteneur, l’accès aux fichiers appartenant à
rootde l’hôte, àbarde l’hôte et àfoodu namespace varie selon le mappage des UID - Dans l’exemple, on crée dans le répertoire
/var/lib/bar/testunroot.txtappartenant àrootde l’hôte et unbar.txtappartenant àbarde l’hôte, puis on le monte en lecture/écriture dans le conteneur sur/test - Si le conteneur est exécuté en tant que
foo, le fichier appartenant àbarde l’hôte apparaît commeroot:rootdans le conteneur, tandis que le fichier appartenant àrootde l’hôte apparaît commenobody:nogroupcar il n’est pas mappé dans le namespace - Dans le conteneur,
foone peut lire nibar.txtniroot.txt, et un rootless non-root apporte une isolation supplémentaire par rapport à un rootless rootful - Le
foo.txtcréé parfoodans le répertoire monté apparaît sur l’hôte comme appartenant à l’UID166537, et l’utilisateur hôtebarne peut pas en lire le contenu - Si le conteneur est exécuté avec l’utilisateur interne
root, lerootdu namespace peut lire les fichiers appartenant àbarsur l’hôte ainsi que ceux appartenant àfoo, mais pas le fichier appartenant àrootsur l’hôte - Si l’on exécute avec l’utilisateur interne
roottout en appliquant--cap-drop=all, il ne peut plus lire non plus le fichier defooet ne peut lire que le fichier appartenant àbarsur l’hôte
Test de Copy Fail
-
Conditions de test
- Pour le test de Copy Fail, on utilise la version exploit du commit publié à l’origine
8e918b5 - L’image de conteneur d’exemple ajoute
curlà une image de serveur HTTP existante afin de pouvoir télécharger le script d’exploit depuis le conteneur - L’image est buildée sous le nom
copyfail - Le noyau testé est le
6.12.74+deb13+1-amd64de Debian, et côté Debian on considère qu’une version récente inférieure à6.12.85peut encore servir de noyau non corrigé - En temps normal, si l’utilisateur non privilégié
fooappellesu, le mot de passerootest demandé - Dans chaque test, l’utilisateur du conteneur télécharge le script Copy Fail dans
/tmp, l’exécute, puis appellesleepaprès avoir obtenu un shellroot - Copy Fail persistant au-delà du cycle de vie du conteneur, la VM est redémarrée avant chaque test
- Pour le test de Copy Fail, on utilise la version exploit du commit publié à l’origine
-
Résultats en rootless rootful
- Si le conteneur est lancé avec
--user=root, le processus dans le conteneur est déjàroot - Dans cet état, si l’on exécute le script Copy Fail puis que l’on appelle
su, on obtient un shelluid=0(root), mais comme l’utilisateurrootpeut déjà ouvrir un autre shell root avecsusans mot de passe, Copy Fail n’apporte concrètement rien de plus - Dans
podman top,/bin/bash,python3 copy_fail_exp.py,suetsleepapparaissent tous commerootdans le conteneur et comme utilisateur hôte1001 - Le même ensemble de capabilities est conservé, avec
CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT - Le
rootinterne peut lirebar.txtetfoo.txtdans le/testmonté, mais pasroot.txt, qui appartient àrootsur l’hôte
- Si le conteneur est lancé avec
-
Résultats en rootless non-root
- Si l’on exécute le conteneur en tant que
foo, puis que l’on lance le script Copy Fail et appellesu, les privilèges sont élevés vers lerootinterne au conteneur - Le
iddu shell obtenu s’affiche commeuid=0(root) gid=1002(foo) groups=1002(foo) - Dans
podman top, le/bin/bashinitial, le processus exécutant l’exploit et l’appel àsuapparaissent avec l’UID hôte166537, l’utilisateur conteneurfooet des capabilities ànone - Après l’élévation de privilèges,
[sh]etsleepapparaissent comme utilisateur hôte1001, utilisateur conteneurroot, et récupèrent le même ensemble de capabilities que le rootless rootful - Même ce
rootde conteneur ayant obtenu des privilèges ne peut pas lireroot.txt, qui appartient àrootsur l’hôte - Dans cet état, le conteneur est compromis, mais le périmètre d’attaque reste limité à ce qui est accessible au conteneur et à l’utilisateur non privilégié
barsur l’hôte
- Si l’on exécute le conteneur en tant que
-
Résultats avec
no-new-privileges- Podman permet, avec
--security-opt=no-new-privileges, d’empêcher un processus de conteneur d’obtenir plus de privilèges qu’au moment de son démarrage - Si l’on applique cette option à un conteneur rootless non-root et que l’on exécute Copy Fail, un shell s’ouvre mais reste en
uid=1002(foo) - Dans
podman top, tous les processus restent aussi avec l’UID hôte166537, l’utilisateur conteneurfooet des capabilities ànone - Dans le
/testmonté également,foone peut lire que son propre fichier, et nonbar.txtniroot.txt - Le conteneur est compromis, mais reste limité à l’utilisateur interne non privilégié
foosans capabilities
- Podman permet, avec
-
Résultats avec
--cap-drop=all- Même si l’on lance un conteneur rootless non-root avec
--cap-drop=all,foon’a de toute façon aucune capability au départ - Dans cet état, si l’on exécute Copy Fail puis que l’on appelle
su, le shell ouvert reste enuid=1002(foo) - Dans
podman top,/bin/bash, l’exécution de l’exploit,su, le shell etsleeprestent tous enfooavec des capabilities ànone - L’exploit échoue à obtenir un shell
root, etfoone peut lire que son propre fichier dans/test - Ce résultat est similaire au test
no-new-privileges, et ces deux mesures peuvent être utilisées ensemble pour réduire efficacement l’exposition aux capabilities
- Même si l’on lance un conteneur rootless non-root avec
-
Persistance de l’exploit
- Si l’obtention immédiate d’un shell
rootet de capabilities a pu être bloquée parno-new-privilegesou--cap-drop=all, les effets de l’exploit lui-même persistent - Si l’on lance ensuite un nouveau conteneur sans restriction de capabilities, l’utilisateur non privilégié
foodu conteneur peut devenirrootdu conteneur simplement en appelantsu - Un correctif du noyau et un redémarrage restent donc nécessaires
- Si l’obtention immédiate d’un shell
Stratégies de défense en profondeur
-
Images en lecture seule
- ajouter
--read-onlyàpodman runmonte le système de fichiers racine du conteneur en lecture seule - Podman monte par défaut certains répertoires comme
/tmp,/runet/var/tmpen écriture ; pour obtenir un mode entièrement en lecture seule, il faut donc aussi ajouter--read-only-tmpfs=false - si un conteneur en lecture seule est compromis, les écritures sur le système ne sont pas autorisées, ce qui peut limiter certaines attaques après l’exploit
- toutefois, comme il est possible de rediriger la sortie de
curlverspython3, le mode lecture seule à lui seul n’empêche pas l’exécution même de l’exploit - dans l’exemple, le serveur HTTP
python3n’a pas besoin d’écrire sur le système de fichiers, ce qui permet d’utiliser cette option en toute sécurité - beaucoup d’images préconstruites partent du principe qu’elles ont un accès en écriture à certains répertoires et peuvent donc ne pas fonctionner correctement avec un système de fichiers racine en lecture seule
- un système de fichiers racine en lecture seule est indépendant des volumes en écriture attachés au conteneur ; en cas de compromission, il reste donc possible d’écrire dans ces répertoires de montage
- ajouter
-
Limitation des ressources
- Docker et Podman peuvent utiliser les cgroups pour limiter les ressources fournies aux conteneurs
- un conteneur n’a pas besoin d’une quantité illimitée de mémoire, de CPU ou de PID
podman statspermet de vérifier l’utilisation des ressources du conteneur, puis d’appliquer des limites adaptées
-
Limiter les binaires disponibles
- l’exemple utilise l’image
ubuntupour simplifier, mais l’imageubuntucontient de nombreux binaires qu’un attaquant peut exploiter en cas de compromission - la plupart de ces binaires ne sont pas nécessaires pour exécuter un serveur HTTP
- il est préférable de garder les images d’exécution aussi minimales que possible
- les builds multi-étapes permettent de séparer l’environnement de build de l’environnement d’exécution
- vous pouvez partir d’images spécialisées comme python3, des variantes Debian
-slim, ou de distributions plus petites commealpine - si le processus du conteneur est compatible, vous pouvez utiliser des distroless images ou
scratchafin de créer un runtime sans shell, gestionnaire de paquets ni utilitaires système
- l’exemple utilise l’image
-
Pare-feu
- il est possible d’utiliser
iptablesounftablespour restreindre les processus de conteneur via un pare-feu - seules les connexions entrantes et sortantes strictement nécessaires au processus du conteneur doivent être autorisées
- dans l’exemple du serveur HTTP, aucune connexion vers DNS ni vers des serveurs locaux ou distants n’est nécessaire ; on peut donc le restreindre, par exemple, à n’autoriser que les paquets
tcpprovenant de connexions entrantes déjà établies
- il est possible d’utiliser
Implications opérationnelles
- les conteneurs rootless Podman standard offrent par défaut une meilleure isolation que la configuration standard des conteneurs Docker
- Docker prend aussi en charge l’exécution rootless et l’utilisation d’espaces de noms utilisateur non privilégiés, mais cela demande plus d’efforts de configuration que Podman, et les différences d’architecture jouent également
- Docker reste très largement utilisé, et des outils de self-hosting comme Dokku, Kamal, Coolify, Dokploy utilisent aussi Docker par défaut
- si vous exécutez des images Docker Hub sans les examiner soigneusement ou sans appliquer de mesures de verrouillage, vos services peuvent fonctionner avec une surface d’attaque plus large que nécessaire
- il faut comprendre les détails d’implémentation des images de conteneur
- vous devez savoir quel utilisateur, ou quels utilisateurs, exécutent les processus du conteneur
- vous devez savoir de quels répertoires du système de fichiers racine dépendent les processus du conteneur
- vous devez distinguer les capabilities Linux nécessaires de celles qui ne le sont pas
- en combinant les différents mécanismes fournis par Podman et par les conteneurs, il est possible de renforcer les conteneurs et de réduire le rayon d’impact en cas de compromission
- selon la charge de travail, il ne faut pas considérer le conteneur comme l’unique frontière de sécurité
- l’utilisation conjointe de conteneurs et de machines physiques ou virtuelles distinctes permet une isolation efficace
- Podman offre aussi un moyen d’isoler les workloads sur un même hôte en exécutant chacun avec un utilisateur non privilégié distinct et son propre espace de noms utilisateur
1 commentaires
Avis sur Lobste.rs
Il faut se concentrer sur le comportement primitif rendu possible par la vulnérabilité, plutôt que sur l’exploit publié
Cette vulnérabilité permet d’écrire dans le page cache qu’un fichier soit en lecture seule ou non, ce qui permet à un conteneur malveillant de modifier des pages appartenant à des fichiers de l’image de base d’overlayfs, avec un impact possible sur d’autres conteneurs selon la manière dont ils sont déployés
Dans la configuration rootless ici, les cibles seraient d’autres conteneurs exécutés sur l’hôte avec le même utilisateur
Une autre voie d’exploitation consiste à lancer ou trouver un conteneur basé sur une image de base déjà connue pour être utilisée, puis à altérer le page cache à l’intérieur de ce conteneur afin qu’un autre conteneur partageant le même runtime et les mêmes données overlayfs exécute ce code
Le mode rootless et les user namespaces sont importants, mais n’aident pas beaucoup ici, et comme le dit le site copy.fail, il faudrait envisager de bloquer dans les conteneurs l’appel système seccomp
socket(AF_ALG, ...)J’aimerais bien plus de précisions sur ce que signifie exactement « selon la manière dont les conteneurs sont déployés »
L’un des avantages de Podman rootless est que, selon la charge de travail, il n’est pas nécessaire d’exécuter les conteneurs sur l’hôte avec le même utilisateur
Si vous parlez du cas où plusieurs conteneurs rootless tournent sous l’utilisateur principal d’un poste de travail, je suis d’accord, mais sur serveur on peut isoler chacun sous un utilisateur distinct, et exécuter la même image de conteneur avec différents utilisateurs non privilégiés
C’est assez différent du défaut de Docker qui exécute la plupart des choses en
root, mais j’ai aussi écrit à la fin de l’article que ce n’était pas une frontière de sécurité ultime, et qu’il faut juger au cas par cas si répartir des conteneurs rootless entre plusieurs utilisateurs non privilégiés est pertinentJ’isole certaines charges de travail dans des VM
Je me demande si le fait de dire que rootless et les user namespaces n’aident pas ici signifie qu’ils n’empêchent pas l’exploitation
Je n’ai pas traité seccomp parce que je n’ai encore jamais défini de politique explicite pour des conteneurs, mais c’est une bonne occasion d’approfondir le sujet
J’aime Podman et les conteneurs rootless, mais après avoir vu CopyFail, je suis arrivé à la même conclusion que le commentaire voisin
Même avec les avantages de contrôle d’accès supplémentaires de
podman+rootless, cela confirme au fond le conseil classique selon lequel un conteneur n’est pas une frontière de sécurité, et qu’un seul exploit kernel peut tout faire tomberJe fais de l’administration système en amateur, mais comme nouveauté intéressante dans cet espace, j’ai regardé le backend libkrun pour crun avec podman
La promesse est de gérer la plupart des workloads conteneurisés tels quels, tout en les exécutant en interne dans une MicroVM avec son propre guest kernel séparé, mais je ne sais pas vraiment où ça en est côté maturité, validation en conditions réelles et niveau d’audit de sécurité, et certains aspects semblent très cutting-edge
Les MicroVM sont adoptées activement par les outils de codage LLM, donc cet état de fait pourrait durer
podman machinesemblait aussi prometteur, mais malheureusement il a manifestement été pensé uniquement pour un usage de poste de travail développeur, avec un modèle d’une seule VM d’exécution de conteneurs par système hôteCela dit, je trouve que dire « un conteneur n’est pas une frontière de sécurité » est trop simpliste. Les conteneurs sont bien une frontière de sécurité, simplement pas aussi solide qu’on voudrait le croire
En déploiement local, cette ligne devient plus floue
Du point de vue matériel, une VM n’est pas intrinsèquement plus sûre qu’un processus, mais sa frontière est plus défendable pour trois raisons
Les évasions de VM sont moins fréquentes que les appels système, ce qui laisse plus de marge pour appliquer des mitigations de canaux auxiliaires sans dégrader les performances
L’interface hôte d’une VM est bien plus simple. Un périphérique bloc expose une interface de lecture/écriture par blocs, et un périphérique réseau envoie et reçoit des trames
L’appel
setsockoptque Linux ou *BSD fournit sur les sockets représente une surface d’attaque bien plus large que la plupart des pilotes émulés ou paravirtualisés, et ce n’est pourtant qu’une toute petite partie de la surface d’attaque totale du kernelL’interface d’une VM est aussi bien moins riche en état. Il peut y avoir des transactions en cours dans un anneau de type requête-réponse, mais à part cela il y a très peu d’état
Les identifiants, UID, GID, tables de descripteurs de fichiers et autres ajoutent de la complexité basée sur l’état dans le kernel, et s’il y a des bugs, un processus peut en tirer parti
La difficulté des variantes pour poste de travail est qu’elles réintroduisent cette complexité
Par exemple, la couche de base d’un conteneur pourrait être exposée comme un périphérique bloc contenant un système de fichiers immuable, mais les volumes et dossiers partagés seraient probablement montés via 9pfs ou VirtIO-FS, donc 9p ou FUSE au-dessus de VirtIO
La surface d’attaque s’agrandit alors de nouveau
Avec un peu de chance, il faut une chaîne d’exploitation complète
Je suis plus familier de FreeBSD, où l’on sandboxe généralement avec Capsicum les composants qui fournissent les périphériques paravirtualisés ou émulés, de sorte qu’il faut d’abord compromettre un processus hôte, puis, pour accéder à quelque chose auquel la VM n’avait pas accès, il faut ensuite encore percer jusqu’au kernel
Mais sans ce sandboxing supplémentaire, on retombe dans un monde où une évasion de conteneur permet de faire tout ce que l’utilisateur peut faire, ce qui n’est guère mieux qu’une compromission de
rootsur un poste de travailPersonnellement, je préfère gVisor. Ce n’est pas un runtime VMM, mais il existe depuis des années, et il est utilisé par des entreprises comme Tencent. Dans mon environnement, où tous les conteneurs tournent déjà dans des VM Proxmox, c’est un bon compromis
J’expérimente aussi syd-oci, qui me semble avoir reçu un peu moins d’attention que les recommandations de base que sont les MicroVM ou gVisor
Merci aussi pour la référence à libkrun, cela semble être une piste prometteuse
Il semble aussi assez probable que cela débouche sur davantage d’audits de sécurité