
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"
# Ou teste diretamente com Python:
python3 -c "import socket; socket.socket(38, 5, 0)"
# Modo LSM: PermissionError: [Errno 1] Operation not permitted
# Modo kprobe: Killed
AF_ALG de userspace incondicionalmente. Não há mecanismo para isentar processos específicos por PID, cgroup ou nome de comando. Isso está planejado para uma versão futura (via mapas HashMap do eBPF). Para a grande maioria dos sistemas, isso é aceitável, pois quase nada usa AF_ALG.bpf-linker (eles têm como alvo bpfel-unknown-none e não podem ser membros normais do workspace).A maioria das distribuições não habilita BPF LSM por padrão. Se você quiser o modo LSM mais limpo (EPERM em vez de SIGKILL):
# Verifique os LSMs atuais:
cat /sys/kernel/security/lsm
# Se "bpf" estiver ausente, adicione-o:
sudo sed -i 's/^GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 lsm=lockdown,capability,yama,apparmor,bpf"/' /etc/default/grub
sudo update-grub && sudo reboot
O modo kprobe funciona sem esta etapa.
Após corrigir o kernel para ≥ 7.0 / ≥ 6.19.12 / ≥ 6.18.22:
# Confirme a versão do kernel
uname -r
# Execute novamente o exploit — ele não deve mais produzir um shell root
./target/release/copy_fail
Alternativamente, confirme que o módulo vulnerável foi neutralizado:
modinfo algif_aead | grep filename
# Se embutido, coloque o initcall na blacklist (requer reinicialização)
sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
sudo reboot