needhelp
← Retour au blog

Dirty Frag : Nouvelle Vulnérabilité Zero-Copy d'Élévation de Privilèges du Noyau Linux

par xingwangzhe
Linux
Kernel
Sécurité
LPE
Dirty Frag
CVE

Dirty Frag : Une chaîne de vulnérabilités sur le chemin zero-copy du noyau Linux qui empoisonne le cache de pages, enchaînant xfrm-ESP et RxRPC. Affecte presque toutes les distributions majeures depuis 2017 et permet l’élévation de privilèges à root sans mot de passe.


Encore ? On N’a Jamais la Paix ?

Honnêtement, je ne pensais pas devoir réécrire un article sur l’élévation de privilèges du noyau si tôt.

Seulement huit jours depuis l’article Copy Fail. Huit jours ! La liste noire algif_aead de Copy Fail venait à peine d’être tapée dans le terminal, le patch n’était même pas chaud, et voilà — Dirty Frag qui tombe.

Et le plus absurde : les mitigations de Copy Fail sont totalement inefficaces contre Dirty Frag. Cette fois, l’attaque cible des sous-systèmes complètement différents — les chemins de chiffrement xfrm-ESP et RxRPC — donc que vous désactiviez algif_aead ou non ne change absolument rien.

Dirty Pipe (2022), Copy Fail (2026.04), Dirty Frag (2026.05)… À chaque fois c’est « affecte presque toutes les distributions depuis 2017 ». Vous savez ce que ça signifie ? Que pendant près de dix ans, n’importe qui connecté à un compte non-privilégié sur votre système a eu la possibilité de devenir silencieusement root.

Mais est-ce entièrement la faute des développeurs du noyau ? Pas forcément.

L’explosion des outils d’audit de code assistés par IA permet aux chercheurs de scanner en quelques heures des vulnérabilités cachées depuis près d’une décennie. Des bugs qui exigeaient une review manuelle ligne par ligne sont maintenant balayés en masse par l’IA. Copy Fail a été localisé par Xint Code en une heure. Dirty Frag vient de l’audit manuel du chercheur coréen Hyunwoo Kim, mais les chemins de code exploités appartiennent à la même famille que Copy Fail — écriture du cache de pages sur le chemin zero-copy — ce qui montre que ce type de défaut de conception est loin d’être isolé.

Plus troublant encore : le processus de divulgation. Le chercheur avait soumis les détails à l’équipe de sécurité du noyau avec un embargo de 5 jours. Mais le jour même, un tiers a publié les détails complets et l’exploit ESP(xfrm). Pas de CVE, pas de patch, mais le PoC disponible pour tous. Ce n’est pas de la divulgation responsable, c’est un coup de couteau dans le dos de tous les utilisateurs Linux. La fenêtre de correction a explosé, tout le monde forcé de courir à nu.

Bon, fini de râler. Regardons sérieusement cette vulnérabilité.


Chronologie

DateÉvénement
2017-01Vulnérabilité xfrm-ESP introduite avec le commit cac2661c53f3 (cachée 9 ans)
2023-06Vulnérabilité RxRPC introduite avec le commit 2dc334f1a63a
2026-04-29Hyunwoo Kim (@v4bel) signale la vulnérabilité RxRPC et l’exploit complet à security@kernel.org
2026-05-07Détails soumis à linux-distros avec un embargo de 5 jours
2026-05-07Le jour même, un tiers publie les détails ESP(xfrm), l’embargo est rompu
2026-05-07Après consultation des mainteneurs, la documentation complète est publiée. Pas de CVE, pas de patch officiel
2026-05-08À l’heure où j’écris, les distributions attendent encore le patch upstream

Analyse d’Impact

MétriqueDétails
CVEAucun pour l’instant (le NVD n’a pas eu le temps d’en attribuer)
TypeDéfaut logique déterministe, pas une condition de course
Taux de réussite100%, succès garanti à la première exécution
PortéePresque toutes les distributions majeures depuis 2017 (noyau 7.0.3 inclus)
MéthodePayload de 192 octets, assemblé via 48 écritures de 4 octets
Trace disqueAucune — empoisonne uniquement le cache de pages en mémoire, restauration au reboot
Contourne Copy FailLa désactivation algif_aead est totalement inefficace

Comparaison avec les vulnérabilités LPE historiques :

CaractéristiqueDirty Cow (2016)Dirty Pipe (2022)Copy Fail (2026)Dirty Frag (2026)
Condition de courseRequiseNon requiseNon requiseNon requise
PortéeVersions spécifiques5.8+Toutes distros 2017+Toutes distros 2017+
Complexité de l’exploitComplexeComplexe10 lignes PythonUn seul fichier C
Trace disqueOuiOuiNonNon
Statut du patchCorrigéCorrigéCorrigéPas de patch officiel

Distributions affectées confirmées :

DistributionNoyau testé
Ubuntu 24.04.46.17.0-23-generic
RHEL 10.16.12.0-124.49.1
CentOS Stream 106.12.0-224
AlmaLinux 106.12.0-124.52.3
Fedora 446.19.14-300
openSUSE Tumbleweed7.0.2-1
Arch Linux7.0.3

De 6.12 à 7.0, d’Ubuntu à Arch — couverture totale. Oui, toute distribution majeure que vous utilisez est probablement affectée.


Principes Techniques : Pourquoi « Dirty Frag » ?

Le Cœur en Une Phrase

splice() implante une référence de cache de page de fichier en lecture seule dans le slot frag du skb → le noyau récepteur effectue des opérations cryptographiques in-situ sur le frag (src == dst, même mémoire) → la prétendue opération de déchiffrement devient une primitive STORE écrivant directement dans le cache de page en lecture seule → le code machine de su est remplacé par un ELF root-shell.

« Dirty » = empoisonnement du cache de pages. « Frag » = exploitation du mécanisme de fragments skb. Ensemble — Dirty Frag.

Pollution skb Frag sur le Chemin Zero-Copy

Normalement, quand vous écrivez dans un socket, le noyau copie les données utilisateur dans son skb. Mais splice() prend le chemin zero-copy — il insère directement le pointeur de la page de cache (structure page + offset) dans le tableau frag du skb, sans copier les données :

struct skb_shared_info {
    struct sk_buff  *frag_list;      // liste chaînée de frags
    skb_frag_t      frags[MAX_SKB_FRAGS];  // chaque entrée = {page, offset, size}
    // ...
};

Le point clé : la page passée à splice() est la page de cache en mémoire de votre fichier (ex. /usr/bin/su) — vous n’avez que les droits de lecture, mais une référence à cette page est déjà dans le skb de la pile réseau.

Ensuite, tout dépend si la pile réseau réceptrice « met les mains » sur cette page.

Vulnérabilité 1 : Écriture Cache de Page xfrm-ESP

Première vulnérabilité : le chemin de déchiffrement IPsec ESP.

esp_input() déchiffre les données. Normalement, si le skb partage des données avec un frag, le noyau doit d’abord appeler skb_cow_data() (Copy-on-Write). Mais il existe un chemin qui contourne COW :

static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
{
    if (!skb_cloned(skb)) {
        if (!skb_is_nonlinear(skb)) {
            // [1] skb linéaire : pas de frag, sûr
            nfrags = 1;
            goto skip_cow;
        } else if (!skb_has_frag_list(skb)) {
            // [2] A des frags, mais pas de frag_list → saute COW directement !
            nfrags = skb_shinfo(skb)->nr_frags;
            nfrags++;
            goto skip_cow;  // LA BOMBE
        }
    }
    err = skb_cow_data(skb, 0, &trailer);
}

Quand le skb est non-linéaire (a des frags contenant le cache de page) mais frag_list vide, le code saute à skip_cow. Ensuite, crypto_authenc_esn_decrypt() fait un déchiffrement AEAD in-situ — src et dst pointent vers la même scatterlist, la page de cache implantée par l’attaquant.

La ligne critique :

scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);

Ceci écrit 4 octets dans dst (la page de cache de su) à l’offset assoclen + cryptlen. La valeur vient des 32 bits hauts du numéro de séquence ESP — que l’attaquant contrôle via XFRMA_REPLAY_ESN_VAL.seq_hi.

L’attaquant contrôle donc :

  • Où écrire (offset, ajusté via la longueur du payload)
  • Quoi écrire (4 octets, via seq_hi)

48 SA XFRM, chacune avec son seq_hi stockant un fragment de 4 octets de l’ELF, 48 itérations → un ELF root-shell complet dans le cache de su.

Limite : il faut CAP_NET_ADMIN. Obtenable via unshare(CLONE_NEWUSER | CLONE_NEWNET), mais l’AppArmor d’Ubuntu bloque les namespaces réseau pour les non-privilégiés. D’où le besoin d’une deuxième vulnérabilité.

Vulnérabilité 2 : Écriture Cache de Page RxRPC

Deuxième vulnérabilité : le déchiffrement Kerberos du protocole RxRPC.

rxkad_verify_packet_1() fait un déchiffrement pcbc(fcrypt) in-situ sur les 8 premiers octets :

skcipher_request_set_crypt(req, sg, sg, 8, iv.x);
//                               ^^  ^^
//                             src==dst → opération in-situ !
ret = crypto_skcipher_decrypt(req);  // écriture de 8 octets

skb_to_sgvec() convertit le frag du skb (contenant la page de cache) en scatterlist, src == dst. Le « résultat de déchiffrement » est écrit directement dans la page de cache en lecture seule.

Comparaison :

Caractéristiquexfrm-ESPRxRPC
Taille d’écriture4 octets8 octets
Contrôle de la valeurDirect (seq_hi)Indirect (force brute fcrypt)
Privilège requisNamespace utilisateurAucun privilège
Introduction2017-012023-06

Les valeurs RxRPC ne sont pas directement contrôlables — c’est le résultat de fcrypt_decrypt(C, K). L’attaquant enregistre une clé K via add_key("rxrpc", ...), puis force brute pour trouver K produisant les 8 octets cibles. fcrypt = 56-bit, 8-byte block, force brute facile.

Point critique : RxRPC ne nécessite aucun privilège. Pas de namespace utilisateur, pas de namespace réseau, pas de CAP_NET_ADMIN. Et Ubuntu charge rxrpc.ko par défaut.

Logique d’Enchaînement

Couverture complète par complémentarité :

ScénarioVulnérabilitéRaison
Ubuntu (AppArmor bloque namespaces)RxRPCrxrpc.ko chargé par défaut, sans privilège
RHEL / Fedora / openSUSExfrm-ESPNamespaces dispos, écritures ESP précisément contrôlables
Autres distributionsxfrm-ESP ou RxRPCAu moins un chemin disponible

C’est ça qui rend Dirty Frag terrifiant — quelle que soit votre configuration, il y a toujours un chemin vers root.


Analyse du PoC

Exploit open-source sur github.com/V4bel/dirtyfrag :

git clone https://github.com/V4bel/dirtyfrag.git
cd dirtyfrag && gcc -O0 -Wall -o exp exp.c -lutil && ./exp

Principe :

  1. Préparer un ELF minimal de 192 octets — root-shell à exécution immédiate
  2. Diviser en 48 fragments de 4 octets (chemin xfrm-ESP)
  3. Enregistrer un XFRM SA par fragment, seq_hi = valeur du fragment
  4. splice() envoie le cache de su dans le skb → écriture in-situ
  5. 48 itérations → les 192 premiers octets du cache de su sont remplacés
  6. execve("/usr/bin/su") → root shell

Zéro touche au disque. /usr/bin/su intact sur disque, md5 inchangé. Mais dans le cache de page du noyau, il a été remplacé. Quand un processus lance execve sur su, le noyau lit le cache — et exécute votre binaire. Root.


Réponse d’Urgence : Désactiver les Modules Vulnérables

Au 8 mai 2026, pas de patch officiel. Mitigation temporaire : décharger les trois modules.

Vérifiez si vous êtes affecté :

lsmod | grep -E 'esp4|esp6|rxrpc'

Si sortie non vide → affecté.

Exécutez immédiatement (pas de reboot requis) :

sudo sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtyfrag.conf"
sudo rmmod esp4 esp6 rxrpc 2>/dev/null

Important : si le PoC a déjà été exécuté, videz le cache de pages :

echo 3 | sudo tee /proc/sys/vm/drop_caches

Sans cela, même modules désactivés, le cache empoisonné reste en mémoire.

Effets secondaires :

  • esp4/esp6 désactivés → coupure des tunnels VPN IPsec. Si vous utilisez strongSwan/Libreswan, évaluez d’abord l’impact.
  • rxrpc désactivé → impact minimal sauf si vous utilisez AFS.

Course aux Armements Accélérée par l’IA

Dirty Frag impose une question plus profonde : pourquoi les LPE du noyau sortent-elles en rafale ?

VulnérabilitéDécouverteOutil cléDélai rapport → public
Dirty Pipe2022Audit manuelStandard
Copy Fail2026-04Xint Code (IA)~1 mois
Dirty Frag2026-05Audit manuel8 jours (embargo rompu le jour même)

D’une par an → une par mois → une par semaine ?

Derrière ça, l’explosion des outils d’audit IA. Avant, un humain lisait ligne par ligne, une vulnérabilité pouvait dormir dix ans. Aujourd’hui l’IA scanne un sous-système en heures. Le noyau n’a pas empiré — les vieilles dettes cachées sont juste révélées.

Et l’écosystème de divulgation change. L’embargo de Dirty Frag a été délibérément brisé le jour même. Du rapport à security@kernel.org jusqu’aux armes dans les mains des hackers du monde entier : huit jours. La fenêtre de correction — anéantie.

La même famille continue de s’agrandir :

VulnérabilitéSous-systèmeStatut
Copy FailAF_ALG + authencesnCorrigé
Dirty Fragxfrm-ESP + RxRPCPas de patch
Copy Fail 2ESP-in-UDPDivulgué
ZCRX Freelistio_uring ZCRXDivulgué

Même principe partout : splice() injecte des références de cache de page en lecture seule, les sous-systèmes écrivent in-situ. La même classe de défaut de conception :

ComposantRaisonEffet secondaire
splice()Zero-copy, perfRéfs de cache en lecture seule envoyées aux sous-systèmes
AF_ALGExposer le chiffrement noyauSessions de chiffrement initiables sans privilège
xfrm-ESPAccélération IPsecDéchiffrement in-situ, pages en lecture seule comme buffer
RxRPCSupport protocole AFSIdem, sans même avoir besoin de privilège namespace

Chaque design, pris isolément, est une optimisation ou fonctionnalité légitime. Assemblés, ils forment une chaîne où n’importe quel utilisateur local devient root sans mot de passe.

Tant que le noyau upstream n’aura pas réexaminé le paradigme des « opérations in-situ sur chemins zero-copy », je vous le garantis — ce ne sera pas le dernier.

Pour les utilisateurs :

  1. Exécutez les commandes de blacklist maintenant. Revenez en arrière au patch officiel.
  2. Surveillez les mises à jour du noyau. Dès que corrigé, mettez à jour et rebootez.
  3. Vérifiez régulièrement avec lsmod | grep.

Références


Diagrammes d’Information

Vue d'ensemble Dirty Frag

Figure 1 : Vue d’ensemble — comment les références de cache zero-copy sont exploitées via les skb frags

Partager cette page