
Analyse technique approfondie de la CVE-2026-31431 (Copy Fail) du noyau Linux, une élévation de privilèges locale via le bug de scatterlist en place d'AF_ALG, incluant la cause racine, le chemin d'exploitation et le guide de soumission de correctif.
Gravité : CVSS 7,8 (Élevée)
Classe : Élévation locale de privilèges
Introduit : Linux 4.14 (2017)
Corrigé : commit a664bf3d603d
CVE-2026-31431 est une faille d'élévation locale de privilèges dans le module algif_aead du noyau Linux — l'interface socket AEAD de l'API crypto utilisateur du noyau (AF_ALG).
La vulnérabilité permet à un utilisateur non privilégié d'utiliser la famille d'adresses AF_ALG de l'API Crypto du noyau Linux pour effectuer des écritures contrôlées de 4 octets dans le cache de pages, qui soutient les copies en mémoire des fichiers. En remplaçant le code dans la copie en mémoire d'un exécutable lisible stocké dans le cache de pages, un attaquant peut élever ses privilèges utilisateur lorsqu'un processus privilégié exécute ensuite cette version corrompue du fichier.
Contrairement à Dirty Cow (CVE-2016-5195), qui nécessitait de gagner une course, Copy Fail est une faille logique directe. Elle se déclenche sans courses, sans tentatives répétées, ni fenêtres temporelles sujettes aux plantages.
Le commit 72548b093ee3 (« crypto: algif_aead - switch to in-place processing ») a introduit une optimisation qui définissait req->src == req->dst dans aead_recvmsg(), éliminant l'allocation d'un tampon de sortie séparé.
Cette optimisation a fait pointer req->src et req->dst vers une scatterlist combinée. De ce fait, les pages du cache de pages issues de l'appel splice() ont été incorrectement chaînées directement dans la scatterlist de destination accessible en écriture.
L'API AEAD du noyau définit un contrat de sortie clair : le tampon de destination reçoit AAD || plaintext, soit exactement assoclen + (cryptlen - authsize) octets. Rien dans l'API n'impose ce contrat, et rien ne le documente comme une exigence. La conception suppose que chaque algorithme AEAD limitera ses écritures à la destination prévue. Un algorithme ne l'a pas fait.
crypto/
├── algif_aead.c ← bug de scatterlist en place (principal)
└── authencesn.c ← écriture hors limites dans l'espace de travail (déclencheur)
crypto/algif_aead.c — L'assignation en place/* VULNÉRABLE — chemin en place post-2017 */
static int aead_recvmsg(struct socket *sock, struct msghdr *msg,
size_t ignored, int flags)
{
/* ... */
/*
* Lorsque l'espace utilisateur appelle splice(2) sur un fichier,
* areq->tsgl contient les pages du cache de pages de ce fichier.
* Définir dst = src remet ces pages du cache de pages au moteur
* crypto comme tampon de sortie accessible en écriture.
*/
aead_request_set_crypt(req,
areq->tsgl, /* src */
areq->tsgl, /* dst — pointeur identique ; cause racine */
areq->cryptlen, iv);
}
crypto/authencesn.c — L'écriture hors limites dans l'espace de travailLe modèle authencesn est un wrapper AEAD utilisé par IPsec pour la prise en charge des numéros de séquence étendus (ESN). IPsec utilise des numéros de séquence 64 bits divisés en une moitié haute (seqno_hi, octets 0–3 de l'AAD) et une moitié basse (seqno_lo, octets 4–7). Le format fil transporte uniquement seqno_lo ; seqno_hi est implicite.
/* crypto/authencesn.c — écriture dans l'espace de travail au-delà de la limite de sortie */
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
/* ... */
/*
* authencesn stocke temporairement seqno_hi à l'offset
* (assoclen + cryptlen) dans req->dst — un mot au-delà de la
* région de sortie légitime.
*
* En fonctionnement normal : atteint la mémoire appartenant au noyau.
* Avec la scatterlist en place : atteint une page du cache de pages.
*/
scatterwalk_map_and_copy(&seqno_hi,
req->dst,
req->assoclen + req->cryptlen, /* offset hors limites */
sizeof(seqno_hi), /* 4 octets */
1 /* écriture */);
/* authencesn ne restaure pas ces octets sur le chemin d'échec */
}
Correctif en amont : commit a664bf3d603d — annule l'optimisation de 2017.
crypto/algif_aead.c — Scatterlists séparées/* CORRIGÉ — src et dst séparés */
aead_request_set_crypt(req,
areq->tsgl, /* src — peut contenir des pages du cache de pages ; lecture seule */
areq->rsgl, /* dst — tampon noyau fraîchement alloué ; accessible en écriture */
areq->cryptlen, iv);
Effet : Les pages du cache de pages restent dans src, que le moteur crypto ne fait que lire. L'écriture dans l'espace de travail de authencesn à assoclen + cryptlen atterrit désormais dans un tampon alloué par le noyau que le processus possède. Aucune page du cache de pages n'est jamais accessible depuis le chemin d'écriture.
Ce qui suit décrit comment l'écriture de 4 octets permet une élévation complète de privilèges.
| Étape | Action |
|---|---|
| 1 | Ouvrir une socket AF_ALG configurée pour authencesn(hmac(sha256),cbc(aes)) |
| 2 | splice() un binaire setuid lisible (par ex., /usr/bin/sudo) dans la socket — cela place ses pages du cache de pages dans areq->tsgl |
| 3 | Concevoir assoclen + cryptlen pour que l'offset de l'écriture dans l'espace de travail s'aligne sur un octet cible dans l'image en cache du binaire |
| 4 | Appeler recvmsg() — déclenche le chemin de déchiffrement, authencesn écrit 4 octets contrôlés par l'attaquant dans le cache de pages |
| 5 | Le fichier sur disque est inchangé ; les vérificateurs d'intégrité, inotify et les horodatages inode ne sont pas affectés |
| 6 | Toute exécution ultérieure du binaire mappe les pages du cache de pages déjà modifiées — le code corrigé s'exécute avec euid=0 |
L'écriture aboutit dans les données en cache du fichier fusionné en mémoire, contournant entièrement les permissions du fichier.
./scripts/get_maintainer.pl crypto/algif_aead.c
# Herbert Xu <[email protected]>
# [email protected]
./scripts/checkpatch.pl 0001-crypto-algif_aead-revert-inplace.patch
crypto: algif_aead - restore separate src/dst scatterlists in recvmsg
Commit 72548b093ee3 ("crypto: algif_aead - switch to in-place
processing") set req->src == req->dst inside aead_recvmsg(). When
the caller uses splice(2), the source scatterlist contains page
cache pages of the spliced file. With the in-place optimization,
those same pages become the writable destination passed to the
AEAD transform.
authencesn writes 4 bytes at offset (assoclen + cryptlen) into
req->dst as scratch space for Extended Sequence Number
rearrangement and does not restore them on the error path. With
page cache pages present in dst, this write bypasses the VFS
permission layer and corrupts the in-memory image of the spliced
file without modifying the on-disk inode.
An unprivileged local user can exploit this to overwrite
arbitrary bytes in the page cache of any readable setuid binary
and escalate privileges to root.
Restore separate src and dst scatterlists so that page cache
pages are confined to the read-only source path.
Fixes: 72548b093ee3 ("crypto: algif_aead - switch to in-place processing")
Cc: [email protected] # 4.14+
Reported-by: Taeyang Lee <[email protected]>
Signed-off-by: Your Name <[email protected]>
git send-emailgit send-email \
[email protected] \
[email protected] \
[email protected] \
0001-crypto-algif_aead-revert-inplace.patch
| Élément | Objectif |
|---|---|
Sujet sous-système : fichier - phrase verbale | Format requis ; moins de 72 caractères |
Fixes: <sha> ("titre original") | Déclenche l'outillage automatique de rétroportage stable |
Cc: [email protected] | Signale le correctif pour les rétroportages des branches LTS |
Reported-by: | Attribution au découvreur |
Signed-off-by: | Certificat d'origine du développeur — obligatoire |
Les noyaux vulnérables couvrent Linux 4.14 jusqu'à 7.0-rc, toutes les versions 6.18.x antérieures à 6.18.22, et 6.19.x antérieures à 6.19.12.
Atténuation provisoire (aucun redémarrage requis sur la plupart des systèmes) :
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
Ce contournement n'affecte pas dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS ou SSH.
xint.io/blog/copy-fail-linux-distributionsnvd.nist.gov/vuln/detail/CVE-2026-31431a664bf3d603d (mainline)72548b093ee3 (Linux 4.14, 2017)