
# Reescrita educacional do PoC Copy Fail (CVE-2026-31431) — LPE no kernel Linux via crypto in-place algif_aead + escrita em page-cache com splice()
Reescrita educacional do proof-of-concept da Theori/Xint para a CVE-2026-31431,
uma vulnerabilidade de escalonamento local de privilégios na interface de sockets
criptográficos algif_aead do kernel Linux. Afeta todos os kernels mainline de 4.14 (julho de 2017)
até 6.18.21, e o branch estável 6.19 até 6.19.11.
Apenas para fins educacionais e pesquisa de segurança autorizada. NÃO execute em sistemas que você não possui ou para os quais não tenha permissão explícita por escrito para testar. Executar isto em um host vulnerável concede a qualquer usuário local sem privilégios acesso root imediato. Os autores não se responsabilizam por uso indevido.
O kernel Linux expõe uma API de criptografia interna ao espaço do usuário desde o
kernel 3.2 através da família de sockets AF_ALG (<linux/if_alg.h>). O espaço do usuário
abre um socket SOCK_SEQPACKET, o vincula a um template de algoritmo (ex.:
"aead" / "authencesn(hmac(sha256),cbc(aes))"), e envia dados para
criptografia ou descriptografia.
Em 2017, uma otimização de desempenho foi incorporada e permite que algoritmos AEAD criptografem ou descriptografem dados in-place quando o kernel detecta que a origem e o destino compartilham as mesmas páginas subjacentes. Isso evita uma cópia redundante de memória em cada operação.
O template authencesn (que adiciona suporte a Extended Sequence Number à
criptografia autenticada) contém uma escrita temporária durante a descriptografia: ele
escreve bytes ESN em um offset específico dentro do buffer de dados da operação
antes que a verificação de autenticação seja executada. Quando as páginas de origem da operação foram
obtidas do page cache de um arquivo regular via splice(), e a
otimização in-place está ativa, essa escrita temporária vai diretamente de volta para
essas páginas em cache.
Como o page cache do kernel é um mapeamento compartilhado — qualquer processo que abra o mesmo arquivo vê as mesmas páginas físicas — um usuário sem privilégios pode:
/usr/bin/su) com O_RDONLY.AF_ALG.authencesn; a escrita temporária ESN corrompe a
página em um offset controlado com bytes controlados pelo atacante.Scanners de integridade fora do host (AIDE, Tripwire, IMA/EVM) que comparam checksums de arquivos em disco são completamente cegos a este ataque. A escrita desaparece após uma evicção do page cache ou reinicialização, sem deixar vestígios forenses no disco.
Ao mirar /usr/bin/su (um binário setuid root) e sobrescrever um branch
que protege a verificação de autenticação PAM, o atacante obtém um shell root
sem senha.
O commit de otimização que introduziu o bug entrou no ciclo de desenvolvimento 4.14
(julho de 2017). Qualquer kernel compilado com CONFIG_CRYPTO_USER_API_AEAD=y
e CONFIG_CRYPTO_AUTHENC=y (ambos padrões comuns) nas faixas acima é
vulnerável.
Verifique o rastreador de segurança da sua distribuição para o status exato do advisory.
os.splice foi adicionado no Python 3.10)/usr/bin/su, legível mundialmente
pois é setuid)# Mostra o que seria escrito sem tocar no kernel (seguro):
python3 copy_fail_exploit.py --dry-run
# Altera o page cache do /usr/bin/su (kernel vulnerável necessário):
# AVISO: kernel vulnerável necessário -- use apenas em sistemas que você possui
python3 copy_fail_exploit.py
# Altera e imediatamente escala para root:
python3 copy_fail_exploit.py --spawn-shell
# Usa um binário alvo e payload personalizados:
python3 copy_fail_exploit.py --target /usr/bin/sudo --payload-file ./custom_patch.bin
# Saída detalhada (imprime cada escrita de 4 bytes):
python3 copy_fail_exploit.py --verbose --dry-run
Por padrão, --spawn-shell está desativado. Após a alteração, o script imprime:
[+] Patch aplicado ao page cache de '/usr/bin/su' (disco inalterado).
Execute `su` para escalar, ou re-execute com --spawn-shell.
Para restaurar: echo 3 | sudo tee /proc/sys/vm/drop_caches
Você deve optar pelo spawn do shell com --spawn-shell. Isso é intencional:
força você a entender o que o exploit fez antes de executar o resultado.
Configurando a primitiva.
O exploit abre um socket AF_ALG e o vincula ao template
authencesn(hmac(sha256),cbc(aes)) com uma chave dummy de todos zeros. Ele
então configura o tamanho da tag de autenticação para 4 bytes via ALG_SET_AEAD_AUTHSIZE.
accept() no socket de controle produz um socket de operação que pode enviar
solicitações individuais de descriptografia.
Acionando a escrita no page cache.
Para cada bloco de 4 bytes do payload do patch, o exploit chama sendmsg() no
socket de operação com MSG_MORE definido, passando 8 bytes de dados associados
(AAD): 4 bytes de preenchimento seguidos pelos 4 bytes do payload. MSG_MORE instrui o
kernel a segurar a operação até que mais dados cheguem. Ele então cria um pipe
e emite duas chamadas splice() — file_fd → pipe → op_sock — doando as
páginas do page cache do próprio arquivo como entrada para a operação de descriptografia. Quando
recv() é finalmente chamado, o kernel executa a descriptografia authencesn. A
otimização in-place é acionada (origem e destino são as mesmas páginas), e
a escrita temporária ESN copia nossos bytes do payload para a página em cache no
offset alvo. recv() retorna EBADMSG porque a tag de autenticação não pode
ser verificada — isso é esperado e inofensivo. A escrita já ocorreu.
Aplicando o patch no binário.
O payload embutido (retirado literalmente do PoC da Theori) sobrescreve uma instrução
de branch em /usr/bin/su de uma build padrão do util-linux, transformando um
salto condicional que impõe a autenticação PAM em um fall-through
incondicional. Após o patch, executar su sem senha é bem-sucedido e
fornece um shell root. O binário em disco não é tocado; remover as entradas do
page cache (echo 3 > /proc/sys/vm/drop_caches) reverte a alteração instantaneamente.
Para análise técnica mais aprofundada, veja as referências abaixo, em particular o post do blog da Xint e o repositório do PoC da Theori.
- rule: AF_ALG socket opened by unprivileged process
desc: >
CVE-2026-31431 — um processo sem privilégios abriu um socket AF_ALG (família 38),
que é o primeiro passo do exploit Copy Fail.
condition: >
evt.type = socket and
evt.arg.domain = 38 and
not user.uid = 0
output: >
AF_ALG socket opened (user=%user.name uid=%user.uid
pid=%proc.pid comm=%proc.name)
priority: WARNING
tags: [host, network, privilege_escalation, CVE-2026-31431]
rule CopyFail_CVE_2026_31431 {
meta:
description = "Detects Copy Fail exploit script in memory or on disk"
cve = "CVE-2026-31431"
strings:
$template = "authencesn(hmac(sha256),cbc(aes))" ascii
$splice = "os.splice" ascii
$aflag = "AF_ALG" ascii
condition:
all of them
}
Adicione a /etc/audit/rules.d/cve-2026-31431.rules:
-a always,exit -F arch=b64 -S socket -F a0=38 -k cve_2026_31431_afalg
Depois monitore os logs com:
ausearch -k cve_2026_31431_afalg --interpret
1. Aplique o patch do kernel (recomendado) Atualize para o kernel 6.18.22, 6.19.12, 7.0-rc7 ou posterior, ou aplique a atualização de segurança da sua distribuição.
2. Desative CONFIG_CRYPTO_USER_API_AEAD no momento da compilação Se você compilar seu próprio kernel, defina:
# CONFIG_CRYPTO_USER_API_AEAD is not set
Isso remove toda a interface AEAD AF_ALG e elimina a superfície de ataque.
A maioria dos deployments embarcados/endurecidos não precisa dela.
3. seccomp — negar a família de sockets AF_ALG
Adicione um filtro seccomp a processos sensíveis (ou em todo o sistema via uma política LSM)
que rejeite socket(AF_ALG, ...):
// Deny socket() when domain == AF_ALG (38)
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 38, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EACCES),
4. Reduza privilégios do page cache via endurecimento do kernel
vm.unprivileged_userfaultfd=0 e tunables semelhantes reduzem a superfície de ataque
das interfaces de memória do kernel acessíveis ao usuário, embora não bloqueiem diretamente
esta CVE.
O exploit modifica apenas o page cache em memória; o binário em disco nunca é tocado. Para restaurar o conteúdo original do arquivo em memória, remova o page cache do kernel:
echo 3 | sudo tee /proc/sys/vm/drop_caches
Isso remove todas as páginas limpas do cache. A próxima leitura de /usr/bin/su
(ou qualquer binário que foi alterado) recarregará os bytes originais do disco.
Reiniciar também limpa completamente o page cache.
MIT — veja LICENSE.
MIT foi escolhida em vez de uma licença personalizada "apenas para uso educacional" porque
nenhuma licença desse tipo existe como um identificador OSI/SPDX reconhecido. MIT é permissiva, legalmente
bem compreendida e compatível com repositórios de divulgação responsável. A
intenção educacional é expressa no docstring do módulo, na seção de Aviso Legal deste README
e na saída do --help — não em uma cláusula de licença personalizada legalmente duvidosa.
| Branch | Primeiro Vulnerável | Último Vulnerável | Corrigido Em |
|---|
| mainline | 4.14 | 6.18.21 | 6.18.22 |
| 6.19 stable | 6.19.0 | 6.19.11 | 6.19.12 |
| 7.0-rc | 7.0-rc1 | 7.0-rc6 | 7.0-rc7 |
| Distribuição | Kernel Fornecido (aprox.) | Corrigido? |
|---|
| Debian 12 (Bookworm) | 6.1.x | Sim (backport) |
| Debian 13 (Trixie) | 6.12.x / 6.14.x | Sim (backport) |
| Ubuntu 24.04 LTS | 6.8.x | Sim (USN-7xxx) |
| Ubuntu 24.10 | 6.11.x | Sim |
| RHEL 9.x | 5.14.x (rebase) | Sim (RHSA) |
| Alpine 3.20 | 6.6.x | Sim (backport) |
| Arch Linux | rolling (≥ 6.18.22 agora) | Sim |
| Kali Linux 2026.1 | 6.18.12 | Vulnerável |
| Kali Linux 2026.2+ | 6.18.22+ | Corrigido |
| Linux Mint 22 | 6.8.x (base Ubuntu) | Sim (USN Ubuntu) |