
Reprodutor de corrida e kit de ferramentas de estresse para CVE-2026-52910, um use-after-free no kernel Linux em programas seletores cBPF de reuseport, com verificações de dmesg e vazamentos.
Um reprodutor de corrida e kit de estresse para CVE-2026-52910, um use-after-free (UAF) no tratamento do kernel Linux de programas seletores classic BPF (cBPF) de reuseport, corrigido upstream pelo commit "bpf: Free reuseport cBPF prog after RCU grace period".
| CVE | CVE-2026-52910 |
| Tipo | Use-after-free / leitura fora dos limites (CWE-125), CVSS 3.1 7.8 HIGH AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Introduzido | v4.5 (com suporte a cBPF reuseport) |
| Corrigido em | 5.10.259, 5.15.210, 6.1.176, 6.6.143, 6.12.94, 6.18.36, 7.0.13 (stable); mainline v7.1 |
| Splat upstream | BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock (net/core/sock_reuseport.c:596) |
| Reportado por | Eulgyu Kim |
[!WARNING] Esta é uma ferramenta de estresse do kernel. Em um kernel vulnerável ela deliberadamente amplia uma janela de corrida de use-after-free; um acerto pode travar ou corromper a máquina. Execute-a apenas em máquinas que você possui ou está explicitamente autorizado a testar (VMs de teste, máquinas de CI descartáveis), nunca em sistemas de produção.
SO_REUSEPORT permite que muitos sockets se vinculem à mesma porta UDP; para cada
pacote recebido o kernel escolhe um socket do grupo em
reuseport_select_sock() (net/core/sock_reuseport.c). Um grupo pode instalar um
programa seletor — um programa classic BPF anexado com
setsockopt(SO_ATTACH_REUSEPORT_CBPF) — que decide por pacote qual socket
do grupo o recebe. O programa executa na softirq de RX (processamento de recepção
de rede) dentro de uma seção crítica de leitura RCU.
O bug: quando o programa é substituído ou desanexado via setsockopt()
(reuseport_attach_prog() / reuseport_detach_prog()), o programa cBPF antigo
é liberado imediatamente por sk_reuseport_prog_free(), sem esperar pelos
leitores RCU em andamento. Uma CPU ainda percorrendo as instruções do programa
liberado lê memória vmalloc liberada:
sequenceDiagram
autonumber
participant C as CPU0 — churner thread
participant K as setsockopt() path
participant R as CPU1 — RX softirq
R->>R: rcu_read_lock()
R->>R: prog = rcu_dereference(reuse->prog)
C->>K: setsockopt(SO_ATTACH_REUSEPORT_CBPF, progB)
K->>K: swap progA → progB
K->>K: sk_reuseport_prog_free(progA)
Note right of K: unfixed kernels: bpf_prog_free()<br/>runs NOW — no RCU grace period
R->>R: execute progA->insns (run_bpf_filter)
Note right of R: progA was already freed<br/>KASAN: vmalloc-out-of-bounds
Note over K: fix: call_rcu(sk_reuseport_prog_free_rcu) —<br/>free deferred by one RCU grace periodO caminho do seletor eBPF (SO_ATTACH_REUSEPORT_EBPF) não é afetado: ele
já libera programas através de estágios diferidos de bpf_prog_put(). A correção
dá ao caminho cBPF o mesmo tratamento — um período de graça RCU antes que o
programa antigo seja liberado.
O relatório KASAN upstream (em um kernel de depuração 7.0):
BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock+0xedc/0x1220
Read of size 4 at addr ffffc9000051e004 by task slowme/10208
net/core/sock_reuseport.c:596
Em uma máquina de teste Linux:
$ make
$ sudo ./run_hammer.sh 600 # 10-minute run
...
== result: RC=0 (0 clean / 1 setup / 2 splat / 3 leak / 4 integrity) ==
Requisitos:
gcc e bash.dmesg, /proc/vmallocinfo, sysctl);
o martelo em si roda sem privilégios (o reprodutor upstream rodou como UID 1000).Resultados esperados:
RC=2; ocasionalmente a
verificação de integridade do martelo dispara primeiro → RC=4.RC=0, contagem de vmalloc de bpf_prog estável.A janela de corrida é minúscula (liberação vs. execução de RX em andamento), então trate uma única execução limpa como inconclusiva. Para testes reais, execute por horas, por exemplo:
$ sudo ./run_hammer.sh 86400 512 8 16 4 127.0.0.1 0
reuseport_race_hammer$ ./reuseport_race_hammer [dur_sec] [insns] [nports] [nsocks] [nsenders] [ip] [ebpf]
Cada grupo executa uma thread churner que troca e desanexa o seletor cBPF
via setsockopt() em um loop apertado, e threads sender inundando o grupo com
datagramas UDP de 64 bytes enquanto os receptores contam a entrega por socket.
Fases de execução (T = dur_sec):
time ──────────────────────────────────────────────────────────────►
[0 ──────────── T-15s) [T-15s ── T-10s) [T-10s ─────────── T]
CHURN + FLOOD SETTLE MEASURE
churner swaps/detaches churn frozen, deterministic program
the selector prog at final program (selects the LAST
max rate under full attached socket): EVERY packet
UDP flood — THE (selects LAST must land on the LAST
race window open socket) socket; snapshot A →
run → snapshot B
Verificação de integridade: durante a fase de medição o programa final seleciona deterministicamente o último socket do grupo. Se os pacotes recebidos pelo grupo durante essa janela não chegaram todos a esse socket, a seleção deu errado (um possível efeito de UAF mesmo sem KASAN) → código de saída 2.
Códigos de saída do martelo: 0 PASS · 1 erro de setup/execução · 2 WARN de integridade.
run_hammer.sh — wrapper de execução únicaExecuta o martelo e adiciona as verificações que tornam uma única execução significativa:
bpf_prog em /proc/vmallocinfo;net.core.optmem_max para que programas cBPF de vários KB sejam anexados sem problemas;DRAIN (padrão 30s) para que as liberações diferidas de RCU/workqueue terminem;BUG:, Oops:, WARNING:, RIP:,
leaked, stuck);bpf_prog antes/depois (verificação de vazamento) e,
opcionalmente, varre o kmemleak se /sys/kernel/debug/kmemleak existir.Códigos de saída: 0 limpo · 1 erro de setup (incluindo falha de setup do martelo) ·
2 splat do kernel visto · 3 possível vazamento de bpf_prog · 4 WARN de integridade
do martelo.
livepatch_cycle.sh — teste do ciclo de vida do livepatchA correção libera o programa cBPF antigo a partir de um callback call_rcu(). Se você distribuir
a correção como um livepatch (kernel live patching — código aplicado em um
kernel em execução), a própria função de callback vive no módulo do patch: um
revert/unload enquanto callbacks ainda estão pendentes libera o texto do módulo sob
os pés do callback. Este script exercita ciclos de apply/revert enquanto o
martelo mantém a janela de corrida quente, e observa
/sys/kernel/livepatch/*/transition e o dmesg.
$ MODE=rcu ./livepatch_cycle.sh 20 120 # 20 cycles × 120s hammer each
| Modo | Comportamento |
|---|---|
cycle (padrão) | apply → revert, ambos sob carga contínua do martelo |
rcu |
Códigos de saída: 0 limpo · 1 falha de comando · 2 splat ou transição travada ·
4 WARN de integridade do martelo.
A CI compila o martelo com dois conjuntos de flags e executa shellcheck nos scripts. Testes de kernel em tempo de execução não são executados intencionalmente em runners de CI compartilhados: o reprodutor precisa de controle sobre a versão do kernel do runner (e em um kernel vulnerável poderia causar oops no runner). Execute esses em máquinas de teste reais.
GPL-2.0-only — veja LICENSE.
| Arquivo | Propósito |
|---|
reuseport_race_hammer.c | O reprodutor: martelo multithread que alterna o seletor cBPF sob carga UDP total e depois verifica a integridade da entrega. |
run_hammer.sh | Wrapper de execução única: aumenta net.core.optmem_max, executa o martelo, depois verifica o dmesg em busca de splats, /proc/vmallocinfo em busca de alocações bpf_prog vazadas e, opcionalmente, kmemleak. |
livepatch_cycle.sh | Aplica/reverte um livepatch que carrega a correção enquanto o martelo é executado — caça riscos do ciclo de vida do livepatch da correção baseada em call_rcu(). |
Makefile | Compila o martelo. |
.github/workflows/ci.yml | CI: build + shellcheck (sem testes de kernel em tempo de execução; veja CI). |
| Argumento | Padrão | Significado |
|---|
dur_sec | 300 (min 45) | segundos totais de execução |
insns | 256 | instruções de preenchimento no programa cBPF alternado; um programa maior é uma região liberada maior para acertar. Se o attach falhar com ENOMEM, aumente net.core.optmem_max (o wrapper faz isso por você). |
nports | 4 (max 64) | grupos reuseport (uma porta UDP cada, a partir de 21000) |
nsocks | 8 (max 512) | sockets por grupo |
nsenders | 4 (max 32) | threads remetentes UDP por grupo |
ip | 127.0.0.1 | endereço de destino; use um IP de NIC física para espalhar as softirqs de RX entre as CPUs (RSS) |
ebpf | 0 | 1 = também alterna attach/detach de SO_ATTACH_REUSEPORT_EBPF (requer CAP_BPF/CAP_NET_ADMIN); esse caminho não é vulnerável, isto é para comparação/cobertura |
| Env | Padrão | Significado |
|---|
HAMMER | ./reuseport_race_hammer | binário do martelo |
DRAIN | 30 | segundos a aguardar após a execução antes de verificar |
OPTMEM_MAX | 131072 | valor para net.core.optmem_max; 0 = não alterar |
| revert imediatamente na churn máxima — o caso de risco acima |
safe | para o martelo → dorme GRACE (padrão 30s, um período de graça) → revert |
| Env | Padrão | Significado |
|---|
APPLY_CMD / REVERT_CMD | kpatch load $PATCH / kpatch unload $PATCH | comandos de livepatch |
PATCH | ./livepatch-reuseport.ko | módulo do patch |
HAMMER | ./reuseport_race_hammer | binário do martelo |
HAMMER_ARGS | 256 4 8 4 127.0.0.1 0 | argumentos do martelo |
TRANSITION_TIMEOUT | 60 | segundos máximos a aguardar por uma transição de livepatch |
GRACE | 30 | sono de período de graça para MODE=safe |
FORCE | 0 | 1 = executar mesmo se nenhuma transição de livepatch for detectada |