
Échec de copie : 732 octets vers root sur chaque grande distribution Linux.
732 octets. Toute distribution. Root.
Une faille logique linéaire dans le modèle cryptographique
authencesndu noyau Linux permet à un utilisateur local non privilégié d'effectuer une écriture précise et contrôlée de 4 octets dans le cache de pages de n'importe quel fichier lisible — y compris les binaires setuid. Aucune course. Aucune nouvelle tentative. Aucune recompilation. Root sur toutes les principales distributions Linux publiées depuis 2017.
📄 Analyse technique · 🔗 Correctif du noyau · 🛡️ CVSS : Critique
| Distribution | Version du noyau |
|---|---|
| Ubuntu 24.04 LTS | 6.17.0-1007-aws |
| Amazon Linux 2023 | 6.18.8-9.213.amzn2023 |
| RHEL 10.1 | 6.12.0-124.45.1.el10_1 |
| SUSE 16 | 6.12.0-160000.9-default |
Les quatre ont été compromises avec le même script Python de 732 octets, sans modification.
AF_ALG expose le sous-système cryptographique du noyau à l'espace utilisateur non privilégié. splice() transfère les données d'un fichier dans un tube par référence — en transmettant directement les pages du cache de pages, sans copie. Lorsqu'un utilisateur épisse un fichier dans une socket AEAD AF_ALG, la liste scatterlist d'entrée de la socket contient des références vivantes aux pages mises en cache par le noyau de ce fichier.
Dans algif_aead.c, l'optimisation en place de 2017 copiait l'AAD et le texte chiffré de la liste scatterlist TX vers le tampon RX, mais chaînait les pages du tag d'authentification par référence à l'aide de sg_chain(), puis définissait req->src = req->dst :
SGL d'entrée : [ AAD | CT | Tag ]
^
└─ sg_chain() → pointe toujours vers les pages du cache de pages
SGL de sortie : [ AAD | CT ] ──→ [ Tag (pages du cache de pages) ]
(tampon RX) (chaîné depuis le SGL TX)
req->src ──┐
├──→ même liste scatterlist combinée
req->dst ──┘
Les pages du cache de pages issues de splice() se retrouvaient désormais dans une liste scatterlist de destination inscriptible, séparées de la zone d'écriture légitime par une simple limite d'offset. Rien dans l'API n'imposait que les algorithmes restent dans les limites.
authencesnauthencesn est un wrapper AEAD utilisé par IPsec pour la prise en charge des Extended Sequence Numbers (ESN) 64 bits. Pour réorganiser les octets ESN pour le calcul HMAC, il utilise le tampon de destination de l'appelant comme espace de travail — y compris une écriture à l'offset assoclen + cryptlen, qui se situe au-delà de la limite du tag d'authentification :
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // lit AAD[0..7]
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // écrase dst[4..7]
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1); // ← écrit au-delà du tag
Le troisième appel écrit 4 octets (seqno_lo) à dst[assoclen + cryptlen]. Dans le chemin AF_ALG en place, le scatterwalk traverse le tampon RX pour atteindre les pages du tag du cache de pages chaînées. Le noyau mappe la page du cache de pages via kmap_local_page et écrit directement dans la copie mise en cache du fichier cible.
Le HMAC échoue ensuite (le texte chiffré est fabriqué), recvmsg() renvoie une erreur — mais l'écriture de 4 octets persiste définitivement.
Aucune modification individuelle n'était erronée. La vulnérabilité réside à l'intersection des trois.
La cible par défaut est /usr/bin/su, un binaire setuid-root présent sur toutes les distributions testées.
Étape 1 — Configuration de la socket
Ouvrir une socket AF_ALG, lier à authencesn(hmac(sha256),cbc(aes))
Définir la clé. Accepter la socket de requête. (Aucun privilège requis.)
Étape 2 — Boucle d'écriture (une fois par bloc de shellcode de 4 octets)
sendmsg() → les octets AAD [4:8] transportent les 4 octets à écrire (seqno_lo)
splice() → les pages du cache de pages du fichier cible dans la socket AF_ALG
recv() → déclenche le déchiffrement → authencesn écrit seqno_lo dans le cache de pages
(recvmsg renvoie une erreur ; l'écriture persiste)
Étape 3 — Exécution
execve("/usr/bin/su")
Le noyau charge le binaire depuis le cache de pages (désormais corrompu)
Le binaire setuid-root exécute le shellcode injecté → UID 0
a = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
# ... définir la clé, accepter la socket de requête u ...
u.sendmsg([b"A"*4 + payload_chunk], [cmsg_headers], MSG_MORE)
os.splice(target_fd, pipe_wr, offset)
os.splice(pipe_rd, alg_fd, offset)
u.recv(...) # déclenche l'écriture dans le cache de pages
Mettez à jour vers un noyau contenant le correctif a664bf3d603d. Le correctif rétablit le fonctionnement hors place de algif_aead.c : req->src pointe vers le SGL TX ; req->dst pointe vers le tampon RX. Les pages du cache de pages issues de splice() restent en lecture seule. Le mécanisme sg_chain() qui les reliait à la destination inscriptible est supprimé.
// Avant (vulnérable) : src et dst partagent la même liste scatterlist
aead_request_set_crypt(&areq->cra_u.aead_req, rsgl_src, rsgl_src, used, ctx->iv);
// Après (corrigé) : src est le SGL TX, dst est le tampon RX — entièrement séparés
aead_request_set_crypt(&areq->cra_u.aead_req, tsgl_src, rsgl_dst, used, ctx->iv);
Désactivez le module noyau algif_aead :
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
Ou bloquez la création de sockets AF_ALG via une politique seccomp dans vos profils de charge de travail.
Remarque pour les environnements conteneurisés : Étant donné que le cache de pages est partagé sur l'hôte, cette vulnérabilité traverse les frontières des conteneurs. Appliquez les atténuations au niveau du nœud, pas seulement par pod. Voir la Partie 2 pour les détails complets de l'évasion Kubernetes.
Le chercheur de Theori, Taeyang Lee, a identifié, grâce à des travaux antérieurs sur kernelCTF, que AF_ALG + splice() crée un chemin où l'espace utilisateur non privilégié peut alimenter directement les pages du cache de pages dans le sous-système cryptographique — et que la provenance des pages de la liste scatterlist était une classe de vulnérabilité sous-explorée.
L'équipe de recherche a utilisé Xint Code pour étendre cette observation à l'ensemble du sous-système crypto/ avec l'invite d'opérateur suivante :
"Ceci est le sous-système crypto/ de Linux. Veuillez examiner tous les chemins de code accessibles depuis les appels système de l'espace utilisateur. Notez une observation clé : splice() peut fournir des références du cache de pages de fichiers en lecture seule (y compris les binaires setuid) aux listes scatterlist TX cryptographiques."
Après environ une heure d'analyse automatisée, Copy Fail était le résultat de plus haute sévérité. Des vulnérabilités supplémentaires découvertes lors du même scan restent sous divulgation coordonnée.
Partie 2 : Du pod à l'hôte — comment Copy Fail s'échappe de toutes les principales plateformes Kubernetes cloud. À venir.
| Propriété | Détail |
|---|
| Déterministe | Faille logique linéaire — aucune condition de course, aucune fenêtre temporelle, aucune nouvelle tentative |
| Portable | Même script, mêmes octets, fonctionne sur toutes les distributions et architectures testées |
| Minuscule | Script Python de 732 octets utilisant uniquement la bibliothèque standard (os, socket, zlib). Nécessite Python 3.10+ pour os.splice |
| Furtive | La page corrompue n'est jamais marquée comme sale. Les sommes de contrôle sur disque sont inchangées ; seule la mémoire cache de pages est modifiée |
| Trans-conteneur | Le cache de pages est partagé à l'échelle du système au-delà des frontières des conteneurs — c'est également une primitive d'évasion de nœud Kubernetes (voir Partie 2) |
| Variable | Contrôlée via |
|---|
| Fichier cible | Tout fichier lisible par l'utilisateur actuel |
| Offset d'écriture | assoclen, offset de splice et longueur de splice |
| Valeur d'écriture | Octets 4–7 de l'AAD fourni dans sendmsg() (seqno_lo) |
| Année | Événement |
|---|
| 2011 | authencesn ajouté au noyau (a5079d084f8b) pour la prise en charge ESN d'IPsec. L'écriture de travail existait mais était inoffensive — seule la couche interne xfrm l'appelait, et l'AAD se trouvait dans une liste scatterlist séparée. |
| 2015 | AF_ALG gagne la prise en charge AEAD. authencesn converti à la nouvelle interface AEAD (104880a6b470), introduisant l'offset d'écriture assoclen + cryptlen. Toujours hors place : les pages du cache de pages étaient dans src (lecture seule). Pas encore exploitable. |
| 2017 | Optimisation en place ajoutée à algif_aead.c (72548b093ee3). req->src = req->dst. Les pages du tag du cache de pages chaînées dans la destination inscriptible. Vulnérabilité formée. |
| 2026-03-23 | Signalée à l'équipe de sécurité du noyau Linux. |
| 2026-04-01 | Correctif fusionné dans la branche principale. |
| 2026-04-22 | CVE-2026-31431 attribuée. |
| 2026-04-29 | Divulgation publique. |
| Date | Événement |
|---|
| 2026-03-23 | Vulnérabilité signalée à l'équipe de sécurité du noyau Linux |
| 2026-03-24 | Accusé de réception initial reçu |
| 2026-03-25 | Correctifs proposés et examinés |
| 2026-04-01 | Correctifs validés dans le noyau principal |
| 2026-04-22 | CVE-2026-31431 attribuée |
| 2026-04-29 | Divulgation publique |