
Reproducteur de race et boîte à outils de stress pour CVE-2026-52910, un use-after-free du noyau Linux dans les programmes sélecteurs cBPF de reuseport, avec vérifications dmesg et de fuites.
Un reproducteur de race et une boîte à outils de stress pour CVE-2026-52910, un use-after-free (UAF) dans la gestion par le noyau Linux des programmes sélecteurs reuseport en BPF classique (cBPF), corrigé en amont par le commit "bpf: Free reuseport cBPF prog after RCU grace period".
| CVE | CVE-2026-52910 |
| Type | Use-after-free / lecture hors 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 |
| Introduit | v4.5 (avec le support cBPF reuseport) |
| Corrigé dans | 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 amont | BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock (net/core/sock_reuseport.c:596) |
| Rapporté par | Eulgyu Kim |
[!WARNING] Ceci est un outil de stress du noyau. Sur un noyau vulnérable, il élargit délibérément une fenêtre de race use-after-free ; un déclenchement peut faire planter ou corrompre la machine. Ne l'exécutez que sur des machines que vous possédez ou pour lesquelles vous êtes explicitement autorisé à tester (VM de test, machines CI jetables), jamais sur des systèmes de production.
SO_REUSEPORT permet à de nombreux sockets de se lier au même port UDP ; pour chaque
paquet entrant, le noyau choisit un socket du groupe dans
reuseport_select_sock() (net/core/sock_reuseport.c). Un groupe peut installer un
programme sélecteur — un programme BPF classique attaché avec
setsockopt(SO_ATTACH_REUSEPORT_CBPF) — qui décide par paquet quel socket
du groupe le reçoit. Le programme s'exécute dans la softirq RX (traitement de réception réseau)
à l'intérieur d'une section critique de lecture RCU.
Le bug : lorsque le programme est remplacé ou détaché via setsockopt()
(reuseport_attach_prog() / reuseport_detach_prog()), l'ancien programme cBPF
est libéré immédiatement par sk_reuseport_prog_free(), sans attendre
les lecteurs RCU en cours. Un CPU qui parcourt encore les instructions du programme libéré
lit de la mémoire vmalloc libérée :
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 periodLe chemin sélecteur eBPF (SO_ATTACH_REUSEPORT_EBPF) n'est pas affecté : il
libère déjà les programmes via les étapes différées de bpf_prog_put(). Le correctif
applique le même traitement au chemin cBPF — une période de grâce RCU avant que l'ancien
programme ne soit libéré.
Le rapport KASAN amont (sur un noyau de 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
| Fichier | Objectif |
|---|---|
reuseport_race_hammer.c | Le reproducteur : un marteau multithreadé qui fait tourner le sélecteur cBPF sous pleine charge UDP, puis vérifie l'intégrité de la livraison. |
run_hammer.sh | Wrapper d'exécution unique : augmente net.core.optmem_max, exécute le marteau, puis vérifie dmesg pour les splats, /proc/vmallocinfo pour les allocations bpf_prog fuitées, et optionnellement kmemleak. |
livepatch_cycle.sh | Applique/révoque un livepatch contenant le correctif pendant que le marteau tourne — traque les dangers du cycle de vie des livepatches du correctif basé sur call_rcu(). |
Makefile | Compile le marteau. |
.github/workflows/ci.yml | CI : build + shellcheck (pas de tests noyau à l'exécution ; voir CI). |
Sur une machine de test Linux :
$ make
$ sudo ./run_hammer.sh 600 # 10-minute run
...
== result: RC=0 (0 clean / 1 setup / 2 splat / 3 leak / 4 integrity) ==
Prérequis :
gcc et bash.dmesg, /proc/vmallocinfo, sysctl) ;
le marteau lui-même s'exécute sans privilèges (le repro amont tournait en UID 1000).Résultats attendus :
RC=2 ; parfois la
vérification d'intégrité du marteau se déclenche en premier → RC=4.RC=0, compteur vmalloc bpf_prog stable.La fenêtre de race est minuscule (libération vs. exécution RX en cours), donc considérez une seule exécution propre comme non concluante. Pour un vrai test, exécutez pendant des heures, par exemple :
$ 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]
| Argument | Défaut | Signification |
|---|---|---|
dur_sec | 300 (min 45) | durée totale d'exécution en secondes |
insns | 256 | instructions de remplissage dans le programme cBPF churné ; un programme plus gros est une région libérée plus grande à toucher. Si l'attache échoue avec ENOMEM, augmentez net.core.optmem_max (le wrapper le fait pour vous). |
nports | 4 (max 64) | groupes reuseport (un port UDP chacun, à partir de 21000) |
nsocks | 8 (max 512) | sockets par groupe |
nsenders | 4 (max 32) | threads émetteurs UDP par groupe |
ip | 127.0.0.1 | adresse cible ; utilisez l'IP d'une NIC physique pour répartir les softirqs RX sur les CPUs (RSS) |
ebpf | 0 | 1 = churner aussi l'attache/détache SO_ATTACH_REUSEPORT_EBPF (nécessite CAP_BPF/CAP_NET_ADMIN) ; ce chemin n'est pas vulnérable, c'est pour la comparaison/couverture |
Chaque groupe exécute un thread churner qui échange et détache le sélecteur cBPF
via setsockopt() en boucle serrée, et des threads sender qui inondent
le groupe de datagrammes UDP de 64 octets pendant que les récepteurs comptent la livraison par socket.
Phases d'exécution (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
Vérification d'intégrité : pendant la phase de mesure, le programme final sélectionne de manière déterministe le dernier socket du groupe. Si les paquets reçus par le groupe pendant cette fenêtre n'ont pas tous atterri sur ce socket, la sélection a mal tourné (un effet possible de l'UAF même sans KASAN) → code de sortie 2.
Codes de sortie du marteau : 0 PASS · 1 erreur de configuration/exécution · 2 WARN d'intégrité.
run_hammer.sh — wrapper d'exécution uniqueExécute le marteau et ajoute les vérifications qui rendent une exécution unique significative :
bpf_prog dans /proc/vmallocinfo ;net.core.optmem_max pour que les programmes cBPF de plusieurs Ko s'attachent proprement ;DRAIN (30s par défaut) que les libérations différées RCU/workqueue se terminent ;BUG:, Oops:, WARNING:, RIP:,
leaked, stuck) ;bpf_prog avant/après (vérification des fuites), et
scanne optionnellement kmemleak si /sys/kernel/debug/kmemleak existe.| Env | Défaut | Signification |
|---|---|---|
HAMMER | ./reuseport_race_hammer | binaire du marteau |
DRAIN | 30 | secondes d'attente après l'exécution avant la vérification |
OPTMEM_MAX | 131072 | valeur pour net.core.optmem_max ; 0 = ne pas toucher |
Codes de sortie : 0 propre · 1 erreur de configuration (y compris échec de configuration du marteau) ·
2 splat noyau détecté · 3 fuite bpf_prog possible · 4 WARN d'intégrité du marteau.
livepatch_cycle.sh — test du cycle de vie des livepatchesLe correctif libère l'ancien programme cBPF depuis un callback call_rcu(). Si vous livrez
le correctif sous forme de livepatch (kernel live patching — code patché dans un
noyau en cours d'exécution), la fonction de callback elle-même réside dans le module de patch : une
révocation/déchargement alors que des callbacks sont encore en attente libère le texte du module sous
les pieds du callback. Ce script exerce des cycles d'application/révocation pendant que le
marteau maintient la fenêtre de race chaude, et surveille
/sys/kernel/livepatch/*/transition et dmesg.
$ MODE=rcu ./livepatch_cycle.sh 20 120 # 20 cycles × 120s hammer each
| Mode | Comportement |
|---|---|
cycle (défaut) | appliquer → révoquer, les deux sous charge de marteau soutenue |
rcu | révoquer immédiatement au churn maximal — le cas à risque ci-dessus |
safe | arrêter le marteau → dormir GRACE (30s par défaut, une période de grâce) → révoquer |
| Env | Défaut | Signification |
|---|---|---|
APPLY_CMD / REVERT_CMD | kpatch load $PATCH / kpatch unload $PATCH | commandes livepatch |
PATCH | ./livepatch-reuseport.ko | module de patch |
HAMMER | ./reuseport_race_hammer | binaire du marteau |
HAMMER_ARGS | 256 4 8 4 127.0.0.1 0 | arguments du marteau |
TRANSITION_TIMEOUT | 60 | secondes max d'attente d'une transition livepatch |
GRACE | 30 | sommeil de période de grâce pour MODE=safe |
FORCE | 0 | 1 = exécuter même si aucune transition livepatch n'est détectée |
Codes de sortie : 0 propre · 1 échec de commande · 2 splat ou transition bloquée ·
4 WARN d'intégrité du marteau.
La CI compile le marteau avec deux jeux de flags et exécute shellcheck sur les scripts. Les tests noyau à l'exécution ne sont intentionnellement pas exécutés sur les runners CI partagés : le reproducteur a besoin du contrôle sur la version du noyau du runner (et sur un noyau vulnérable, il pourrait faire planter le runner). Exécutez-les sur de vraies machines de test.
GPL-2.0-only — voir LICENSE.