
Race-Reproduzierer und Stress-Toolkit für CVE-2026-52910, einen Use-after-Free im Linux-Kernel in reuseport-cBPF-Selektorprogrammen, mit dmesg- und Leak-Prüfungen.
Ein Race-Reproducer und Stress-Toolkit für CVE-2026-52910, einen Use-After-Free (UAF) in der Behandlung von klassischen BPF (cBPF) reuseport-Selektor- Programmen durch den Linux-Kernel, upstream behoben durch den Commit "bpf: Free reuseport cBPF prog after RCU grace period".
| CVE | CVE-2026-52910 |
| Typ | Use-after-free / Out-of-bounds-Read (CWE-125), CVSS 3.1 7.8 HIGH AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Eingeführt | v4.5 (mit reuseport-cBPF-Unterstützung) |
| Behoben 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 |
| Upstream-Splat | BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock (net/core/sock_reuseport.c:596) |
| Gemeldet von | Eulgyu Kim |
[!WARNING] Dies ist ein Kernel-Stress-Tool. Auf einem verwundbaren Kernel erweitert es absichtlich ein Use-After-Free-Race-Fenster; ein Treffer kann die Maschine zum Absturz bringen oder beschädigen. Führen Sie es nur auf Maschinen aus, die Ihnen gehören oder die Sie ausdrücklich zum Testen autorisiert haben (Test-VMs, Wegwerf-CI-Maschinen), niemals auf Produktionssystemen.
SO_REUSEPORT erlaubt es vielen Sockets, denselben UDP-Port zu binden; für
jedes eingehende Paket wählt der Kernel einen Socket der Gruppe in
reuseport_select_sock() (net/core/sock_reuseport.c) aus. Eine Gruppe kann ein
Selektorprogramm installieren — ein klassisches BPF-Programm, das mit
setsockopt(SO_ATTACH_REUSEPORT_CBPF) angehängt wird —, das pro Paket
entscheidet, welcher Socket der Gruppe es erhält. Das Programm wird im
RX-Softirq (Network-Receive-Verarbeitung) innerhalb eines RCU-Read-Side-
Critical-Section ausgeführt.
Der Bug: Wenn das Programm über setsockopt()
(reuseport_attach_prog() / reuseport_detach_prog()) ersetzt oder abgehängt
wird, wird das alte cBPF-Programm sofort von sk_reuseport_prog_free()
freigegeben, ohne auf in-flight RCU-Reader zu warten. Eine CPU, die noch die
Instruktionen des freigegebenen Programms durchläuft, liest freigegebenen
vmalloc-Speicher:
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 periodDer eBPF-Selektor-Pfad (SO_ATTACH_REUSEPORT_EBPF) ist nicht betroffen: Er
gibt Programme bereits über verzögerte bpf_prog_put()-Stufen frei. Der Fix
behandelt den cBPF-Pfad genauso — eine RCU-Grace-Period, bevor das alte
Programm freigegeben wird.
Der Upstream-KASAN-Bericht (auf einem 7.0-Debug-Kernel):
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
Auf einer Linux-Testmaschine:
$ make
$ sudo ./run_hammer.sh 600 # 10-minute run
...
== result: RC=0 (0 clean / 1 setup / 2 splat / 3 leak / 4 integrity) ==
Voraussetzungen:
gcc und bash.dmesg, /proc/vmallocinfo, sysctl);
der Hammer selbst läuft unprivilegiert (der Upstream-Repro lief als UID 1000).Erwartete Ergebnisse:
RC=2; gelegentlich
schlägt zuerst die Integritätsprüfung des Hammers an → RC=4.RC=0, stabile bpf_prog-vmalloc-
Anzahl.Das Race-Fenster ist winzig (Freigabe vs. in-flight RX-Ausführung), daher ist ein einzelner sauberer Lauf nicht aussagekräftig. Für echte Tests führen Sie Stunden lang aus, z. B.:
$ 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]
Jede Gruppe führt einen Churner-Thread aus, der den cBPF-Selektor in einer
engen Schleife über setsockopt() austauscht und abhängt, sowie Sender-
Threads, die die Gruppe mit 64-Byte-UDP-Datagrammen fluten, während Empfänger
die Zustellung pro Socket zählen.
Laufphasen (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
Integritätsprüfung: Während der Messphase wählt das finale Programm deterministisch den letzten Socket der Gruppe aus. Wenn die von der Gruppe während dieses Fensters empfangenen Pakete nicht alle auf diesem Socket gelandet sind, ist die Auswahl fehlgeschlagen (ein möglicher UAF-Effekt auch ohne KASAN) → Exit-Code 2.
Hammer-Exit-Codes: 0 PASS · 1 Setup-/Laufzeitfehler · 2 Integritäts-WARN.
run_hammer.sh — Ein-Run-WrapperFührt den Hammer aus und ergänzt die Prüfungen, die einen einzelnen Lauf aussagekräftig machen:
bpf_prog-Allokationsanzahl in /proc/vmallocinfo;net.core.optmem_max, damit Multi-KB-cBPF-Programme sauber angehängt werden;DRAIN (Standard 30s) auf verzögerte RCU-/Workqueue-Freigaben;BUG:, Oops:, WARNING:, RIP:,
leaked, stuck);bpf_prog-vmalloc-Anzahl vorher/nachher (Leak-Prüfung) und
durchsucht optional kmemleak, falls /sys/kernel/debug/kmemleak existiert.Exit-Codes: 0 sauber · 1 Setup-Fehler (einschließlich Hammer-Setup-Fehler) ·
2 Kernel-Splat gesehen · 3 mögliches bpf_prog-Leak · 4 Hammer-Integritäts-
WARN.
livepatch_cycle.sh — Livepatch-Lifecycle-TestingDer Fix gibt das alte cBPF-Programm aus einem call_rcu()-Callback frei. Wenn
Sie den Fix als Livepatch ausliefern (Kernel-Live-Patching — Code, der in
einen laufenden Kernel gepatcht wird), lebt die Callback-Funktion selbst im
Patch-Modul: Ein Revert/Unload, während Callbacks noch ausstehen, gibt den
Modultext unter den Füßen des Callbacks frei. Dieses Skript übt
Apply/Revert-Zyklen aus, während der Hammer das Race-Fenster heiß hält, und
beobachtet /sys/kernel/livepatch/*/transition und dmesg.
$ MODE=rcu ./livepatch_cycle.sh 20 120 # 20 cycles × 120s hammer each
| Modus | Verhalten |
|---|---|
cycle (Standard) | apply → revert, beides unter anhaltender Hammer-Last |
rcu |
Exit-Codes: 0 sauber · 1 Befehlsfehler · 2 Splat oder hängender Übergang ·
4 Integritäts-WARN vom Hammer.
Die CI baut den Hammer mit zwei Flag-Sets und führt shellcheck auf den Skripten aus. Kernel-Laufzeittests werden absichtlich nicht auf geteilten CI-Runnern ausgeführt: Der Reproducer benötigt Kontrolle über die Kernel-Version des Runners (und könnte auf einem verwundbaren Kernel den Runner zum Oops bringen). Führen Sie diese auf echten Testmaschinen aus.
GPL-2.0-only — siehe LICENSE.
| Datei | Zweck |
|---|
reuseport_race_hammer.c | Der Reproducer: Multithreaded-Hammer, der den cBPF-Selektor unter voller UDP-Last durchwechselt und anschließend die Zustellungsintegrität überprüft. |
run_hammer.sh | Ein-Run-Wrapper: erhöht net.core.optmem_max, führt den Hammer aus und prüft dann dmesg auf Splats, /proc/vmallocinfo auf geleakte bpf_prog-Allokationen und optional kmemleak. |
livepatch_cycle.sh | Wendet einen Livepatch mit dem Fix an bzw. macht ihn rückgängig, während der Hammer läuft — sucht nach Livepatch-Lifecycle-Gefahren des auf call_rcu() basierenden Fixes. |
Makefile | Baut den Hammer. |
.github/workflows/ci.yml | CI: Build + shellcheck (keine Kernel-Laufzeittests; siehe CI). |
| Argument | Standard | Bedeutung |
|---|
dur_sec | 300 (min 45) | Gesamtlaufzeit in Sekunden |
insns | 256 | Füll-Instruktionen im durchgewechselten cBPF-Programm; ein größeres Programm ist eine größere freigegebene Region zum Treffen. Wenn das Anhängen mit ENOMEM fehlschlägt, erhöhen Sie net.core.optmem_max (der Wrapper erledigt das für Sie). |
nports | 4 (max 64) | reuseport-Gruppen (je ein UDP-Port, ab 21000) |
nsocks | 8 (max 512) | Sockets pro Gruppe |
nsenders | 4 (max 32) | UDP-Sender-Threads pro Gruppe |
ip | 127.0.0.1 | Zieladresse; verwenden Sie eine physische NIC-IP, um RX-Softirqs über CPUs zu verteilen (RSS) |
ebpf | 0 | 1 = zusätzlich SO_ATTACH_REUSEPORT_EBPF-Attach/Detach durchwechseln (benötigt CAP_BPF/CAP_NET_ADMIN); dieser Pfad ist nicht verwundbar, dies dient dem Vergleich/der Abdeckung |
| Env | Standard | Bedeutung |
|---|
HAMMER | ./reuseport_race_hammer | Hammer-Binary |
DRAIN | 30 | Sekunden Wartezeit nach dem Lauf vor der Prüfung |
OPTMEM_MAX | 131072 | Wert für net.core.optmem_max; 0 = nicht anfassen |
| revert sofort bei maximalem Churn — der Gefahrenfall oben |
safe | Hammer stoppen → GRACE schlafen (Standard 30s, eine Grace-Period) → revert |
| Env | Standard | Bedeutung |
|---|
APPLY_CMD / REVERT_CMD | kpatch load $PATCH / kpatch unload $PATCH | Livepatch-Befehle |
PATCH | ./livepatch-reuseport.ko | Patch-Modul |
HAMMER | ./reuseport_race_hammer | Hammer-Binary |
HAMMER_ARGS | 256 4 8 4 127.0.0.1 0 | Hammer-Argumente |
TRANSITION_TIMEOUT | 60 | maximale Sekunden Wartezeit auf einen Livepatch-Übergang |
GRACE | 30 | Grace-Period-Schlaf für MODE=safe |
FORCE | 0 | 1 = auch ausführen, wenn kein Livepatch-Übergang erkannt wird |