
PoC de exploração em Rust para LPE no kernel Linux CVE-2026-31431 (gravação no page-cache do AF_ALG) além de defesa em tempo de execução via eBPF bloqueando a criação de sockets AF_ALG por meio de LSM ou kprobe sem reinicialização.
CVE-2026-31431 (Copy Fail) é uma vulnerabilidade de escalonamento local de privilégios no módulo algif_aead do kernel Linux (subsistema AF_ALG). Uma falha lógica introduzida em agosto de 2017 permite que qualquer usuário local sem privilégios grave 4 bytes controlados no cache de páginas de qualquer arquivo legível via AF_ALG + splice(), e então execute um binário setuid corrompido para obter acesso root. O exploit é determinístico — sem condições de corrida, sem offsets de kernel, sem travamento do sistema. Afeta todas as principais distribuições Linux que utilizam kernels de 4.14 até 7.0-rc.
CVSS: 7.8 | Corrigido em: kernel 7.0, 6.19.12, 6.18.22 | Correção no mainline: commit a664bf3d603d
Fontes: copy.fail, The Hacker News, Aviso da CloudLinux
crates/
├── exp/ # Exploit PoC (reimplementação em Rust)
├── copy_fail_guard/ # Carregador eBPF em userspace — modo LSM
├── copy_fail_guard-ebpf/ # Programa eBPF LSM (fora do workspace)
├── copy_fail_guard_kprobe/ # Carregador eBPF em userspace — modo kprobe
└── copy_fail_guard_kprobe-ebpf/ # Programa eBPF kprobe (fora do workspace)
scripts/
└── build_guard.sh # Build com um clique → gera saída em dist/
A causa raiz é uma cadeia de três recursos independentes do kernel interagindo de forma insegura:
AF_ALG — expõe a API de criptografia do kernel para userspace sem privilégiossplice() — transfere dados de arquivos com cópia zero como referências ao cache de páginas (não cópias) para o scatterlist de criptografiaauthencesn — usa o buffer de saída do chamador como espaço temporário, gravando 4 bytes em dst[assoclen + cryptlen]Em 2017, uma otimização in-place em algif_aead.c (72548b093ee3) fez req->src == req->dst, encadeando páginas do cache de páginas no scatterlist de destino gravável. Quando authencesn grava seus bytes temporários, ele ultrapassa o buffer de saída e alcança as páginas encadeadas do cache de páginas. O atacante controla:
sendmsg()A página corrompida nunca é marcada como suja — o arquivo em disco não é alterado, mas execve() lê do cache de páginas. Corrompa um binário setuid → root.
Reimplementação em Rust do PoC público em Python de 732 bytes. Tem como alvo /usr/bin/su, faz splice de suas páginas do cache de páginas em um socket AEAD AF_ALG e as sobrescreve com um payload de shell compactado.
Requer Rust 1.85+ (edition 2024).
cargo build --release -p copy_fail
O binário é exclusivo para Linux. Em outras plataformas, ele sai com um erro Unsupported.
Aviso: Isso explora uma falha real do kernel. Execute apenas em sistemas que você possui e controla, idealmente uma VM descartável.
Inicie uma VM executando um kernel vulnerável (qualquer distribuição mainstream com kernel < 7.0 / < 6.19.12 / < 6.18.22).
Copie o binário compilado para a VM e execute-o como um usuário sem privilégios:
./target/release/copy_fail
Vulnerável: um shell root (#) aparece. Execute whoami para confirmar root.
Não vulnerável (kernel corrigido): a operação AF_ALG falha ou o binário su se comporta normalmente. Você verá um erro ou um prompt normal de senha do su.
Uma defesa de kernel em tempo de execução que bloqueia CVE-2026-31431 sem atualizar o kernel ou reiniciar. Ela usa eBPF para interceptar a criação de sockets AF_ALG, cortando o primeiro passo do exploit.
Dois modos são fornecidos. O carregador com um clique seleciona automaticamente o melhor modo disponível:
| Modo LSM | Modo kprobe | |
|---|---|---|
| Como bloqueia | Retorna -EPERM (criação de socket negada) | SIGKILL (processo morto) |
| Requisito do kernel | ≥ 5.7 com parâmetro de boot lsm=bpf | ≥ 5.3, sem parâmetros especiais |
| Precisa reiniciar para ativar | Talvez (se lsm=bpf ainda não estiver definido) | Não |
| Ponto de hook | Hook LSM socket_create | kprobe __sys_socket |
┌──────────────────────────────────────────────────┐
│ run_guard.sh │
│ • Detecta suporte a BPF LSM │
│ • LSM disponível → copy_fail_guard (EPERM) │
│ • LSM indisponível → copy_fail_guard_kprobe (KILL)│
└──────────────┬───────────────────────────────────┘
│
┌──────────────▼───────────────────────────────────┐
│ Programa eBPF │
│ se família do socket == 38 (AF_ALG) │
│ → bloquear (EPERM ou SIGKILL) │
│ senão │
│ → permitir │
└──────────────────────────────────────────────────┘
Bloquear AF_ALG tem impacto quase zero em sistemas típicos:
AF_ALGAF_ALG (por exemplo, OpenSSL com o engine afalg habilitado, alguns caminhos de offload de criptografia embarcada)socket(AF_ALG, ...)Build com um clique (recomendado):
./scripts/build_guard.sh
Isso instala automaticamente toolchains ausentes (nightly, bpf-linker), compila todos os programas eBPF e carregadores de userspace, e gera tudo em dist/:
dist/
├── copy_fail_guard.bpf.o # Programa eBPF LSM
├── copy_fail_guard_kprobe.bpf.o # Programa eBPF kprobe
├── copy_fail_guard # Carregador de userspace (LSM)
├── copy_fail_guard_kprobe # Carregador de userspace (kprobe)
└── run_guard.sh # Seleção automática: sudo ./run_guard.sh
Copie o diretório dist/ para qualquer máquina de destino e execute sudo ./run_guard.sh para ativar a proteção.
Build manual (passo a passo):
Passo 1: Compile os programas eBPF (deve ser feito no Linux):
# Variante LSM
cd crates/copy_fail_guard-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
# Variante kprobe
cd crates/copy_fail_guard_kprobe-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
Passo 2: Compile os carregadores de userspace:
cargo build --release -p copy_fail_guard -p copy_fail_guard_kprobe
Recomendado — seleciona automaticamente o melhor modo:
sudo ./dist/run_guard.sh
Manual — execute um modo específico:
# Modo LSM (requer lsm=bpf)
sudo GUARD_BPF_OBJ=caminho/para/copy_fail_guard.bpf.o RUST_LOG=info ./copy_fail_guard
# Modo kprobe (funciona em qualquer lugar)
sudo GUARD_BPF_OBJ=caminho/para/copy_fail_guard_kprobe.bpf.o RUST_LOG=info ./copy_fail_guard_kprobe
Pressione Ctrl-C para desanexar o programa eBPF e restaurar o comportamento normal.
Com o guard em execução em um terminal:
# Em outro terminal, tente o exploit:
./target/release/copy_fail
# Modo LSM: "error: Operation not permitted"
# Modo kprobe: "已杀死" / "Killed"