
Race reproducer e stress toolkit per CVE-2026-52910, un use-after-free nel kernel Linux nei programmi selettori cBPF di reuseport, con controlli dmesg e leak.
Un riproduttore di race e toolkit di stress per CVE-2026-52910, un use-after-free (UAF) nella gestione da parte del kernel Linux dei programmi selettore classic BPF (cBPF) reuseport, corretto upstream dal commit "bpf: Free reuseport cBPF prog after RCU grace period".
| CVE | CVE-2026-52910 |
| Tipo | Use-after-free / lettura out-of-bounds (CWE-125), CVSS 3.1 7.8 HIGH AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Introdotto | v4.5 (con il supporto cBPF reuseport) |
| Corretto in | 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) |
| Segnalato da | Eulgyu Kim |
[!WARNING] Questo è uno strumento di stress per il kernel. Su un kernel vulnerabile amplia deliberatamente una finestra di race use-after-free; un colpo può mandare in crash o corrompere la macchina. Eseguilo solo su macchine di tua proprietà o per cui sei esplicitamente autorizzato a testare (VM di test, macchine CI usa e getta), mai su sistemi di produzione.
SO_REUSEPORT consente a molti socket di fare bind sulla stessa porta UDP; per ogni
pacchetto in ingresso il kernel sceglie un socket del gruppo in
reuseport_select_sock() (net/core/sock_reuseport.c). Un gruppo può installare un
programma selettore — un programma BPF classico collegato con
setsockopt(SO_ATTACH_REUSEPORT_CBPF) — che decide per ogni pacchetto quale socket
del gruppo lo riceve. Il programma viene eseguito nella softirq RX (elaborazione
di ricezione di rete) all'interno di una sezione critica RCU read-side.
Il bug: quando il programma viene sostituito o rimosso tramite setsockopt()
(reuseport_attach_prog() / reuseport_detach_prog()), il vecchio programma cBPF
viene liberato immediatamente da sk_reuseport_prog_free(), senza attendere
i lettori RCU in corso. Una CPU che sta ancora percorrendo le istruzioni del programma
liberato legge memoria vmalloc liberata:
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 period
Il percorso del selettore eBPF (SO_ATTACH_REUSEPORT_EBPF) non è interessato:
rilascia già i programmi attraverso fasi differite di bpf_prog_put(). La correzione
riserva al percorso cBPF lo stesso trattamento — un periodo di grazia RCU prima che il vecchio
programma venga liberato.
Il report KASAN upstream (su un kernel di debug 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
| File | Scopo |
|---|---|
reuseport_race_hammer.c | Il riproduttore: hammer multithread che agita il selettore cBPF sotto pieno carico UDP, poi verifica l'integrità della consegna. |
run_hammer.sh | Wrapper per una singola esecuzione: alza net.core.optmem_max, esegue l'hammer, poi controlla dmesg per splat, /proc/vmallocinfo per allocazioni bpf_prog perse, e opzionalmente kmemleak. |
livepatch_cycle.sh | Applica/ripristina una livepatch che contiene la correzione mentre l'hammer è in esecuzione — caccia ai rischi del ciclo di vita della livepatch della correzione basata su call_rcu(). |
Makefile | Compila l'hammer. |
.github/workflows/ci.yml | CI: build + shellcheck (nessun test runtime del kernel; vedi CI). |
Su una macchina Linux di test:
$ make
$ sudo ./run_hammer.sh 600 # 10-minute run
...
== result: RC=0 (0 clean / 1 setup / 2 splat / 3 leak / 4 integrity) ==
Requisiti:
gcc e bash.dmesg, /proc/vmallocinfo, sysctl);
l'hammer stesso viene eseguito senza privilegi (il repro upstream girava come UID 1000).Risultati attesi:
RC=2; occasionalmente il
controllo di integrità dell'hammer scatta per primo → RC=4.RC=0, conteggio vmalloc bpf_prog stabile.La finestra di race è minuscola (free vs. esecuzione RX in corso), quindi considera una singola esecuzione pulita come non conclusiva. Per un test reale esegui per ore, ad esempio:
$ 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]
| Argomento | Default | Significato |
|---|---|---|
dur_sec | 300 (min 45) | secondi totali di esecuzione |
insns | 256 | istruzioni di riempimento nel programma cBPF agitato; un programma più grande è una regione liberata più grande da colpire. Se l'attach fallisce con ENOMEM, alza net.core.optmem_max (il wrapper lo fa per te). |
nports | 4 (max 64) | gruppi reuseport (una porta UDP ciascuno, da 21000) |
nsocks | 8 (max 512) | socket per gruppo |
nsenders | 4 (max 32) | thread mittenti UDP per gruppo |
ip | 127.0.0.1 | indirizzo di destinazione; usa l'IP di una NIC fisica per distribuire le softirq RX tra le CPU (RSS) |
ebpf | 0 | 1 = agita anche l'attach/detach di SO_ATTACH_REUSEPORT_EBPF (richiede CAP_BPF/CAP_NET_ADMIN); quel percorso non è vulnerabile, questo è per confronto/copertura |
Ogni gruppo esegue un thread churner che scambia e rimuove il selettore
cBPF tramite setsockopt() in un ciclo stretto, e thread sender che inondano
di datagrammi UDP da 64 byte il gruppo mentre i ricevitori contano la consegna per socket.
Fasi di esecuzione (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
Controllo di integrità: durante la fase di misura il programma finale seleziona in modo deterministico l'ultimo socket del gruppo. Se i pacchetti ricevuti dal gruppo durante quella finestra non sono finiti tutti su quel socket, la selezione è andata storta (un possibile effetto UAF anche senza KASAN) → codice di uscita 2.
Codici di uscita dell'hammer: 0 PASS · 1 errore di setup/runtime · 2 WARN di integrità.