Dirty Frag : LPE universel Linux par chaîne de deux vulnérabilités Page-Cache Write
by cloud
Dirty Frag (CVE-2026-43284 / CVE-2026-43500) est une classe de vulnérabilités découvverte par Hyunwoo Kim (@v4bel) qui enchaîne deux failles d’écriture dans le cache de pages Linux pour obtenir les privilèges root sur la majorité des distributions. Elle appartient à la même famille que Dirty Pipe et Copy Fail (CVE-2026-31431) — des bugs de logique déterministes sans fenêtre de concurrence.
Caractéristiques principales :
- ✅ Root sur Ubuntu 24.04, RHEL 10.1, openSUSE Tumbleweed, CentOS Stream 10, Fedora 44, etc.
- ✅ 9 ans de fenêtre d’exploitation (2017 → aujourd’hui)
- ✅ Déterministe — pas de race condition, pas de kernel panic en cas d’échec
- ✅ 100% de succès
TL;DR
| Champ | Valeur |
|---|---|
| CVE | CVE-2026-43284 (xfrm-ESP) / CVE-2026-43500 (RxRPC) |
| Impact | Critique — LPE de utilisateur standard → root |
| Composant | net/ipv4/esp4.c + net/rxrpc/ |
| Type | Page-Cache Write via splice() → frag de sk_buff |
| Auteur | Hyunwoo Kim (@v4bel) |
| Exploit | github.com/V4bel/dirtyfrag |
| Correctif esp | f4c50a4034e6 (dans mainline) |
| Correctif rxrpc | Pas encore de patch upstream |
Dirty Frag vs Copy Fail : différences clés
Dirty Frag et Copy Fail partagent le même mécanisme fondamental : splice() permet de planter une page du cache de pages dans un skb, et le noyau écrit in-place dessus. Mais les différences sont notables :
| Aspect | Copy Fail (CVE-2026-31431) | Dirty Frag (CVE-2026-43284/43500) |
|---|---|---|
| Surface d’attaque | algif_aead (AF_ALG) |
xfrm-ESP (IPsec) + RxRPC (AF_RXRPC) |
| Module requis | algif_aead |
esp4/esp6 + rxrpc.ko |
| Namespace requis | Non | Oui pour la variante ESP (CLONE_NEWUSER) |
| Primitive | 4 octets arbitraires | 4 octets arbitraires (ESP) / 8 octets chiffrés (RxRPC) |
| Valeur contrôlée | Oui directement | Oui (ESP) / via brute-force fcrypt (RxRPC) |
| Mitigation connue | Blacklist algif_aead |
Aucune — Copy Fail mitigation ne protège PAS contre Dirty Frag |
Point critique : Même si vous avez blacklisté
algif_aeadpour vous protéger de Copy Fail, Dirty Frag fonctionne toujours viaxfrm-ESPouRxRPC. La mitigation de Copy Fail est insuffisante face à Dirty Frag.
1. Le concept : Dirty Frag
1.1 La famille “Dirty”
Dirty Frag appartient à la lignée des vulnérabilités qui “salissent” le cache de pages Linux :
- Dirty Pipe (2022) — Écriture dans
struct pipe_buffer - Copy Fail (2026) — Écriture via
algif_aead+splice()dans le TX SGL - Dirty Frag (2026) — Écriture via le
fragdestruct sk_buff
Ce qui distingue Dirty Frag, c’est qu’elle cible le membre frag de struct sk_buff (d’où son nom), et qu’elle chaîne deux vulnérabilités pour couvrir les angles morts de chaque variante.
1.2 Le mécanisme commun
1. splice(file_fd, ..., pipe, ...) → page du cache planée dans le pipe
2. splice(pipe, ..., socket, ...) → page transférée dans le frag du skb émetteur
3. Réception loopback → traité par le code noyau (ESP ou RxRPC)
4. Opération crypto in-place → écriture directe DANS la page du cache
5. Le fichier sur disque est intact → seule la copie mémoire est modifiée
6. execve() du binaire setuid → charge la version modifiée → root
2. Variante 1 : xfrm-ESP Page-Cache Write (CVE-2026-43284)
2.1 Root Cause
La fonction esp_input() dans net/ipv4/esp4.c contient une branche qui contourne skb_cow_data() :
if (!skb_cloned(skb)) {
if (!skb_is_nonlinear(skb)) {
nfrags = 1;
goto skip_cow;
} else if (!skb_has_frag_list(skb)) {
nfrags = skb_shinfo(skb)->nr_frags;
nfrags++;
goto skip_cow; // ← BYPASS : pas de copy-on-write !
}
}
Quand cette branche est prise, l’AEAD decrypt opère directement sur les frags du skb. Si un attaquant a planté une page du cache (via splice) dans ces frags, la page devient à la fois source ET destination de l’opération in-place. L’écriture de 4 octets dans crypto_authenc_esn_decrypt() modifie alors le cache de pages.
2.2 La primitive : STORE 4 octets arbitraires
/* crypto_authenc_esn_decrypt() */
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);
Ces 4 octets sont les high-order bits du sequence number de l’ESP header, que l’attaquant contrôle via XFRMA_REPLAY_ESN_VAL.seq_hi lors de l’enregistrement de la SA.
L’attaquant contrôle à la fois la valeur (4 octets) et la position (offset fichier).
2.3 Prérequis : CAP_NET_ADMIN via user namespace
Pour enregistrer une SA XFRM, CAP_NET_ADMIN est requis. L’exploit crée donc un nouveau user/net namespace :
unshare(CLONE_NEWUSER | CLONE_NEWNET);
// + configuration identity mapping (uid_map, gid_map)
// + bring up lo
C’est le maillon faible : si la création de user namespace est bloquée (AppArmor sur Ubuntu par exemple), cette variante est inopérante.
2.4 Exploitation : patch de /usr/bin/su en 48 coups
L’exploit écrit un shellcode ELF statique de 192 octets à l’offset 0 de /usr/bin/su dans le cache de pages. Les 192 octets sont découpés en 48 chunks de 4 octets, chacun écrit via une SA XFRM distincte (SPI = 0xDEADBE10 + i).
for (i = 0; i < 48; i++) {
// 1. Enregistrer SA avec seq_hi = shellcode[i*4..i*4+3]
// 2. vmsplice(hdr_ESP_24B) + splice(/usr/bin/su, offset i*4, 16B)
// 3. splice(pipe -> socket) → déclenche esp_input → STORE 4B
}
Résultat : execve("/usr/bin/su") charge le binaire modifié depuis le cache, le shellcode s’exécute avec euid=0 et lance /bin/sh.
3. Variante 2 : RxRPC Page-Cache Write (CVE-2026-43500)
3.1 Root Cause
Dans rxkad_verify_packet_1(), le decrypt in-place pcbc(fcrypt) opère directement sur le skb sans copy :
sg_init_table(sg, ARRAY_SIZE(sg));
skb_to_sgvec(skb, sg, sp->offset, 8); // ← conversion directe des frags
skcipher_request_set_crypt(req, sg, sg, 8, iv.x); // src = dst = page cache !
crypto_skcipher_decrypt(req); // ← STORE 8 octets dans la page
La différence majeure avec la variante ESP : pas besoin de namespace. add_key("rxrpc", ...) est accessible sans privilège.
3.2 La primitive : STORE 8 octets chiffrés
La valeur écrite est fcrypt_decrypt(C, K) où :
C= les 8 octets ciphertext présents à l’offset cibleK= la clé 56-bit placée dans le token RxRPC par l’attaquant
L’attaquant ne contrôle pas directement la valeur. Il doit brute-forcer K en espace utilisateur jusqu’à obtenir le plaintext désiré. Un port userspace de crypto/fcrypt.c tourne à ~18 M clefs/s.
3.3 Cible : /etc/passwd ligne 1
Contrairement à la variante ESP qui peut écrire un ELF complet, ici le coût du brute-force limite la cible. L’exploit transforme la ligne 1 de /etc/passwd :
Avant : root:x:0:0:root:/root:/bin/bash
Après : root::0:0:GGGGGG:/root:/bin/bash
^^^^ ^^ ^
4 6 8 ← offsets des 3 splice()
Le champ passwd devient vide (""). pam_unix.so nullok accepte cette chaîne vide sans prompt et renvoie PAM_SUCCESS.
3.4 Les trois écritures
| Splice | Offset | 8 octets écrits | Contrainte |
|---|---|---|---|
| A | 4 | "::" + 6B quelconques |
chars 4-5 = :: |
| B | 6 | "0:" + 6B quelconques |
chars 6-7 = 0: |
| C | 8 | "0:GGGGGG:" |
8B exacts |
Chaque écriture est fcrypt_decrypt(C, K). Comme splice A modifie le ciphertext que splice B verra, la correction en chaîne est intégrée dans le brute-force :
find_K(Ca, check_pa, &Ka, &Pa); /* "::" */
memcpy(Cb_actual, Pa+2, 6); memcpy(Cb_actual+6, Cb+6, 2);
find_K(Cb_actual, check_pb, &Kb, &Pb); /* "0:" */
memcpy(Cc_actual, Pb+2, 6); memcpy(Cc_actual+6, Cc+6, 2);
find_K(Cc_actual, check_pc, &Kc, &Pc); /* "0:GGGGGG:" */
Temps de brute-force : ~5 ms pour K_A, ~5 ms pour K_B, ~1 seconde pour K_C.
4. Chaînage : couvrir les angles morts
Le génie de Dirty Frag réside dans le chaînage des deux variantes :
1. Tenter la variante ESP dans un enfant unshare(USER|NET) :
- Succès → modification de /usr/bin/su → forkpty + execve → root shell
- Échec (EPERM, AppArmor, esp4.ko absent) → fallback
2. Fallback variante RxRPC :
- Brute-force K_A/K_B/K_C → 3 splice → /etc/passwd modifié
- forkpty + execve("/usr/bin/su") → PAM nullok → root shell
Couverture : | Distribution | User namespace | Variante qui fonctionne | |—|—|—| | RHEL 10.1 / CentOS Stream 10 | ✅ Autorisé | ESP (RxRPC pas build) | | Ubuntu 24.04 | ⛔ Bloqué par AppArmor | RxRPC (rxrpc.ko chargé par défaut) | | Fedora 44 | ✅ Autorisé | ESP | | openSUSE Tumbleweed | ✅ Autorisé | ESP |
5. Correctif et mitigation
5.1 Patch esp (CVE-2026-43284) — mergé dans mainline
Le patch de Kuan-Ting Chen (f4c50a4034e6) ajoute le flag SKBFL_SHARED_FRAG sur les pages provenant de splice(), et vérifie ce flag dans esp_input() :
- } else if (!skb_has_frag_list(skb)) {
+ } else if (!skb_has_frag_list(skb) &&
+ !skb_has_shared_frag(skb)) {
Les pages épinglees par splice() sont désormais routées vers skb_cow_data().
5.2 Patch rxrpc (CVE-2026-43500) — en attente
Le patch de v4bel ajoute || skb->data_len à la condition de copy existante. Pas encore mergé.
5.3 Mitigation immédiate
En attendant les correctifs :
sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtyfrag.conf; rmmod esp4 esp6 rxrpc 2>/dev/null; echo 3 > /proc/sys/vm/drop_caches; true"
⚠️ Cela désactive IPsec et RxRPC.
6. Timeline
| Date | Événement |
|---|---|
| 2026-04-29 | Signalement RxRPC à security@kernel.org |
| 2026-04-30 | Signalement ESP + patch à netdev |
| 2026-05-07 | Patch ESP mergé, embargo brisé par un tiers |
| 2026-05-07 | Disclosure complète Dirty Frag |
| 2026-05-08 | CVE-2026-43284 attribué |
| 2026-05-08 | CVE-2026-43500 réservé |
7. Différence clé avec Copy Fail
Copy Fail et Dirty Frag partagent le même sink pour la variante ESP (crypto_authenc_esn_decrypt), mais :
- Copy Fail nécessite
algif_aead— blacklister ce module suffit à s’en protéger. - Dirty Frag (variante ESP) utilise le chemin IPsec (
xfrm-ESP) qui est présent sur toutes les distributions, et ne dépend pas d’algif_aead. - Dirty Frag (variante RxRPC) ne nécessite même pas de namespace — fonctionne là où user namespace est bloqué.
La mitigation publique de Copy Fail (blacklist algif_aead) ne protège PAS contre Dirty Frag.
8. Recommandations
- Appliquez les correctifs dès qu’ils seront backportés par votre distribution
- Mitigation d’urgence : bloquez
esp4,esp6,rxrpcvia modprobe.d - Si vous ne pouvez pas patcher : envisagez de désactiver les user namespaces non privilégiés (
kernel.unprivileged_userns_clone=0) et bloquezrxrpc.ko - Surveillez les mises à jour de noyau des prochains jours
Références
| Source | URL |
|---|---|
| GitHub Dirty Frag | https://github.com/V4bel/dirtyfrag |
| Write-up technique | https://github.com/V4bel/dirtyfrag/blob/master/assets/write-up.md |
| Patch ESP (merged) | https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f4c50a4034e6 |
| Copy Fail (article précédent) | https://madpowah.github.io/2026/05/01/copy-fail-exploit.html |
| Dirty Pipe | https://dirtypipe.cm4all.com/ |
| Copy Fail | https://copy.fail |
Have fun.
tags: security - linux - exploit - lpe - kernel - dirtyfrag