Dirty COW CVE-2016-5195 : course dans le copy-on-write Linux
Section intitulée « Dirty COW CVE-2016-5195 : course dans le copy-on-write Linux »Dirty COW (CVE-2016-5195) est une élévation de privilège locale du noyau Linux, corrigée en urgence en octobre 2016 après avoir été trouvée exploitée dans la nature, alors qu’elle traînait là depuis 2007. Le défaut vit dans le traitement du copy-on-write : une course interne au noyau laisse un utilisateur sans privilège modifier un fichier qu’il n’a le droit que de lire.
On a le droit de consulter le registre de la mairie et d’en recopier une page sur son propre cahier pour la gribouiller sans déranger personne, puisque c’est une copie. Dirty COW, c’est l’histoire d’un guichet tellement pressé qu’il finit par laisser écrire directement sur le registre officiel, celui qui décide qui a les clés du bâtiment.
→ Lire l’explication complète de la marmotte
Copy-on-write, cache de pages et condition de course
Section intitulée « Copy-on-write, cache de pages et condition de course »Le copy-on-write (COW) est une optimisation du gestionnaire de mémoire. Quand un processus projette un fichier en mémoire de façon privée, le noyau ne recopie rien tout de suite : il laisse plusieurs utilisateurs partager la même page physique, qui provient du cache de pages (page cache), la copie en mémoire du contenu du fichier. La copie réelle n’intervient qu’au moment où l’un d’eux tente d’écrire. Tant qu’on ne fait que lire, une seule page sert tout le monde ; l’écriture déclenche la duplication, et chacun repart avec sa version.
Le défaut de CVE-2016-5195 se situe dans l’enchaînement de deux opérations distinctes côté noyau : constater qu’une écriture doit porter sur une copie privée, puis réaliser effectivement cette écriture. Entre ces deux instants, l’état sur lequel la première opération s’est appuyée peut changer. C’est une condition de course classée CWE-362, nichée dans mm/gup.c, le code qui va chercher les pages d’un processus quand le noyau doit y lire ou y écrire à sa place (get_user_pages). Le motif est celui d’une vérification séparée de l’action qu’elle autorise : entre le moment où le noyau décide et le moment où il agit, la décision n’est plus valable.
Le déroulé tient en peu d’acteurs : deux fils d’exécution du même processus, le noyau, et le cache de pages qui porte le contenu du fichier. Le schéma montre où la décision du noyau cesse d’être vraie.
Le résultat, quand la fenêtre se referme du mauvais côté, est que l’écriture atterrit sur la page du cache correspondant au fichier d’origine au lieu de la copie privée. Les permissions du fichier n’ont pas bougé, et pourtant son contenu a changé : c’est la version en mémoire que le système sert aux autres processus, et la page, marquée modifiée (le « dirty » du surnom), peut ensuite être réécrite sur le disque. Appliqué à /etc/passwd ou à un binaire SUID (Set User ID, un exécutable qui s’exécute avec les droits de son propriétaire), le passage à root est immédiat. Voir à ce sujet l’élévation de privilège via SUID.
Précision qui change tout pour la modélisation de menace : c’est une faille locale. Il faut déjà un compte sur la machine ou du code qui y tourne. Elle ne transforme pas un inconnu d’Internet en root ; elle transforme un utilisateur restreint, un compte de service, ou un processus déjà compromis par une autre brèche, en administrateur complet. Autrement dit, le maillon d’une chaîne, pas le point d’entrée.
Onze ans de latence : chronologie de CVE-2016-5195
Section intitulée « Onze ans de latence : chronologie de CVE-2016-5195 »La faille est datée du noyau 2.6.22, en 2007. Le plus savoureux figure dans le message du correctif : Linus Torvalds avait déjà tenté de corriger ce même bogue onze ans plus tôt, mal, dans un commit annulé ensuite parce qu’il cassait l’architecture s390. La course était alors jugée « purement théorique ». Elle l’est restée jusqu’à ce que les gestionnaires de mémoire deviennent franchement plus parallèles, rendant la fenêtre nettement plus facile à viser. Le théorique a une fâcheuse habitude de patienter.
Phil Oester ne l’a pas trouvée en relisant du code par oisiveté. Il l’a repérée en analysant une capture réseau d’une attaque réelle contre un serveur qu’il administrait : la faille était exploitée avant d’être connue. Le correctif le crédite, avec un humour sec qui tranche dans la prose kernel, sous la mention « Reported-and-tested-by: Phil “not Paul” Oester ».
La séquence de divulgation s’est jouée en quelques jours.
Ce qu’il faut lire dans cette chronologie : le correctif technique (un drapeau interne FOLL_COW validé par le bit « dirty » de l’entrée de table des pages) est arrivé vite, mais la propagation réelle s’étale sur des années. CISA (Cybersecurity and Infrastructure Security Agency) ne l’a inscrite au catalogue KEV (Known Exploited Vulnerabilities, vulnérabilités exploitées) que le 3 mars 2022, soit plus de cinq ans après le correctif. Un correctif publié n’est pas un parc corrigé, et le décalage se compte ici en demi-décennie.
Serveurs, conteneurs et Android : la surface réelle
Section intitulée « Serveurs, conteneurs et Android : la surface réelle »Dirty COW ne connaît pas de frontière de distribution : tout ce qui embarque un noyau Linux vulnérable est concerné, serveurs, postes, et téléphones Android, où la faille a directement alimenté des outils d’enracinement. Deux points méritent d’être martelés pour l’architecture.
D’abord les conteneurs. Un conteneur n’est pas une machine virtuelle : il partage le noyau de l’hôte. L’isolation par espaces de noms et cgroups découpe la vue d’un processus, elle ne lui donne pas son propre noyau. Une faille du noyau de l’hôte est donc, par construction, une faille de tous les conteneurs qui tournent dessus. Compter sur « c’est dans un conteneur » pour contenir une élévation de privilège noyau est une erreur de modèle, pas une approximation tolérable. Le sujet est développé dans la page sur l’isolation des conteneurs par espaces de noms et cgroups.
Ensuite les parcs dormants. Les serveurs sous contrat de support reçoivent leurs correctifs ; les équipements embarqués, les appliances, les téléphones sortis du support et les images de base jamais reconstruites gardent le noyau de leur jour de fabrication. Android illustre le problème jusqu’à la caricature : correctif partiel en novembre 2016, complet seulement au niveau de correctif 2016-12-05, et une fragmentation telle qu’une fraction du parc n’a jamais vu passer la mise à jour.
La famille ne s’est d’ailleurs pas arrêtée là. « Huge Dirty COW » (CVE-2017-1000405, fin 2017) a montré que le correctif de 2016 restait incomplet pour les pages énormes transparentes (Transparent Huge Pages). Puis « Dirty Pipe » (CVE-2022-0847, 2022), mécanisme tout différent (des drapeaux de tampon de tube laissés non initialisés) pour presque le même effet : écrire dans le cache de pages d’un fichier en lecture seule et passer root, cette fois sans que la page soit marquée modifiée, donc sans trace sur le disque après un redémarrage. Inscrite au KEV le 25 avril 2022. La leçon architecturale n’est pas liée à un bogue précis mais au motif : la frontière entre « lecture seule » et « modifiable » repose sur une chaîne de code noyau dont un seul maillon fautif suffit à annuler la permission.
Exemple : contrôle d’intégrité et inventaire de noyaux
Section intitulée « Exemple : contrôle d’intégrité et inventaire de noyaux »Il n’y a pas de configuration à activer contre Dirty COW : la seule vraie réponse est le noyau corrigé. Côté défense, deux gestes concrets tiennent : vérifier que le noyau réellement en service correspond au correctif, et surveiller les fichiers sensibles que ce type de faille vise. Le schéma se lit de haut en bas, machine par machine.
Le piège à comprendre avant tout : uname -r affiche le noyau chargé, pas celui posé sur le disque. Installer le paquet corrigé ne corrige rien tant que la machine n’a pas redémarré sur ce noyau (ou reçu un correctif à chaud via kpatch, Livepatch ou kGraft). Une machine qui tourne depuis des mois peut porter un paquet à jour et exécuter encore le noyau vulnérable. Comparer la sortie de uname -r au paquet installé est le seul contrôle qui ne ment pas.
Pour la détection, l’angle n’est pas de « voir » la course, qui ne laisse aucune trace réseau ni aucune entrée de journal, mais de constater son effet : un binaire SUID ou un fichier d’authentification modifié sans paquet associé ni changement enregistré. Un contrôle d’intégrité de fichiers, comparant une empreinte de référence aux fichiers sensibles, lève cette alerte. Les mitigations partielles de l’époque, comme le script SystemTap diffusé par Red Hat, étaient fragiles et ne remplaçaient pas le correctif ; elles achetaient du temps, pas de la sûreté.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans noyau corrigé puis redémarré, toute présence sur la machine, compte restreint, compte de service, processus déjà compromis par une autre brèche, dispose d’un chemin fiable vers root. L’écriture illégitime ne passe par aucun droit d’accès au fichier et ne génère aucun flux réseau : sans contrôle d’intégrité sur les binaires SUID et les fichiers d’authentification, la compromission ne laisse quasiment rien à voir. Un parc qui installe le paquet sans redémarrer croit être protégé tout en exécutant le noyau fautif, écart que seul un contrôle du noyau en service révèle. Les conteneurs n’offrent aucun rempart, puisqu’ils partagent le noyau de l’hôte : une seule machine non corrigée expose tout ce qui tourne dessus.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Le système Linux garde en mémoire une copie du contenu des fichiers pour aller plus vite. Quand un programme ouvre un fichier qu’il a le droit de lire mais pas de modifier, Linux lui laisse partager cette copie commune tant qu’il se contente de lire. S’il veut écrire, le système lui fabrique une copie à part, bien à lui, pour qu’il ne touche jamais l’original. C’est une bonne idée, et ça marche presque toujours.
Le problème est que le système fait ça en deux temps : d’abord il prépare la copie privée, ensuite il y écrit. Entre ces deux instants, un autre fil du même programme peut faire disparaître la copie privée au bon moment. L’écriture, qui croyait viser la copie, retombe alors sur la version commune du fichier, celle que tout le système utilise. La version en mémoire du fichier est modifiée, c’est elle que tout le système lit, et elle peut ensuite être recopiée sur le disque. En visant le fichier qui liste les comptes ou un programme à privilèges, un utilisateur ordinaire se fabrique un accès administrateur complet.
Pour que ça marche, il faut déjà être sur la machine. La seule vraie défense est de remplacer le morceau de système fautif par sa version corrigée, puis de redémarrer : tant que l’ancien tourne encore en mémoire, rien n’est réglé, même si la mise à jour est installée.
À lire aussi
- Détournement de bibliothèque partagée Linux et ld.soOrdre de recherche du chargeur dynamique Linux, LD_PRELOAD, RPATH et RUNPATH, mode secure-execution et contre-mesures contre le détournement de bibliothèque partagée.
- WannaCry et MS17-010 : fenêtre d'exposition et correctifsWannaCry, EternalBlue et MS17-010 montrent comment les deux mois entre un correctif publié et un correctif déployé deviennent la fenêtre d'exposition d'un parc Windows.
- Linux : définition, origine et rôle du noyauDéfinition de Linux : noyau système créé par Linus Torvalds en 1991, sous licence GPLv2, ce qu'il fait concrètement et pourquoi il équipe serveurs, Android et supercalculateurs.
- Linux shell, permissions et processus, le modèle de contrôle d'accèsComment un système Linux décide qui a le droit de faire quoi, du shell qui lance les processus aux identités, permissions et capabilities que ces processus portent.
Pages voisines
Dirty COW CVE-2016-5195 : course dans le copy-on-write Linux
Elle cite
