cloud's Blog

Security blog

View on GitHub
9 May 2026

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 :


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_aead pour vous protéger de Copy Fail, Dirty Frag fonctionne toujours via xfrm-ESP ou RxRPC. 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 :

  1. Dirty Pipe (2022) — Écriture dans struct pipe_buffer
  2. Copy Fail (2026) — Écriture via algif_aead + splice() dans le TX SGL
  3. Dirty Frag (2026) — Écriture via le frag de struct 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ù :

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 :

La mitigation publique de Copy Fail (blacklist algif_aead) ne protège PAS contre Dirty Frag.


8. Recommandations

  1. Appliquez les correctifs dès qu’ils seront backportés par votre distribution
  2. Mitigation d’urgence : bloquez esp4, esp6, rxrpc via modprobe.d
  3. Si vous ne pouvez pas patcher : envisagez de désactiver les user namespaces non privilégiés (kernel.unprivileged_userns_clone=0) et bloquez rxrpc.ko
  4. 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