
Reproductor de condiciones de carrera y toolkit de estrés para CVE-2026-52910, un use-after-free en el kernel de Linux en programas selectores cBPF de reuseport, con comprobaciones de dmesg y de fugas de memoria.
Un reproductor de condiciones de carrera y kit de estrés para CVE-2026-52910, un use-after-free (UAF) en el manejo del kernel de Linux de los programas selectores de reuseport de BPF clásico (cBPF), corregido en upstream por el commit "bpf: Free reuseport cBPF prog after RCU grace period".
| CVE | CVE-2026-52910 |
| Tipo | Use-after-free / lectura fuera de límites (CWE-125), CVSS 3.1 7.8 HIGH AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Introducido | v4.5 (con soporte de cBPF en reuseport) |
| Corregido en | 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 en upstream | BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock (net/core/sock_reuseport.c:596) |
| Reportado por | Eulgyu Kim |
[!WARNING] Esta es una herramienta de estrés del kernel. En un kernel vulnerable amplía deliberadamente una ventana de carrera de use-after-free; un impacto puede colgar o corromper la máquina. Ejecútala solo en máquinas que poseas o para las que tengas autorización explícita de probar (VMs de prueba, máquinas de CI desechables), nunca en sistemas de producción.
SO_REUSEPORT permite que muchos sockets se enlacen al mismo puerto UDP; para cada
paquete entrante el kernel elige un socket del grupo en
reuseport_select_sock() (net/core/sock_reuseport.c). Un grupo puede instalar un
programa selector — un programa BPF clásico adjuntado con
setsockopt(SO_ATTACH_REUSEPORT_CBPF) — que decide por paquete qué socket
del grupo lo recibe. El programa se ejecuta en la softirq de RX (procesamiento de recepción de red) dentro de una sección crítica de lectura RCU.
El bug: cuando el programa se reemplaza o se desadjunta mediante setsockopt()
(reuseport_attach_prog() / reuseport_detach_prog()), el programa cBPF antiguo
se libera inmediatamente por sk_reuseport_prog_free(), sin esperar a los
lectores RCU en vuelo. Una CPU que todavía recorre las instrucciones del programa liberado
lee memoria 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 period
La ruta del selector eBPF (SO_ATTACH_REUSEPORT_EBPF) no se ve afectada: ya
libera los programas a través de etapas diferidas de bpf_prog_put(). La corrección
da a la ruta cBPF el mismo tratamiento — un período de gracia RCU antes de que se libere el programa antiguo.
El informe KASAN en upstream (en un kernel de depuración 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
| Archivo | Propósito |
|---|---|
reuseport_race_hammer.c | El reproductor: martillo multihilo que agita el selector cBPF bajo plena carga UDP, y luego verifica la integridad de la entrega. |
run_hammer.sh | Envoltorio de una sola ejecución: eleva net.core.optmem_max, ejecuta el martillo, luego revisa dmesg en busca de splats, /proc/vmallocinfo en busca de asignaciones bpf_prog filtradas, y opcionalmente kmemleak. |
livepatch_cycle.sh | Aplica/revierte un livepatch que lleva la corrección mientras el martillo se ejecuta — caza peligros del ciclo de vida del livepatch de la corrección basada en call_rcu(). |
Makefile | Compila el martillo. |
.github/workflows/ci.yml | CI: build + shellcheck (sin pruebas de kernel en tiempo de ejecución; ver CI). |
En una máquina de prueba 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 y bash.dmesg, /proc/vmallocinfo, sysctl);
el martillo en sí se ejecuta sin privilegios (el reproductor original se ejecutó como UID 1000).Resultados esperados:
RC=2; ocasionalmente la
comprobación de integridad del martillo se dispara primero → RC=4.RC=0, conteo de vmalloc de bpf_prog estable.La ventana de carrera es diminuta (liberación vs. ejecución de RX en vuelo), así que considera una única ejecución limpia como no concluyente. Para pruebas reales ejecuta horas, por ejemplo:
$ 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]
| Argumento | Por defecto | Significado |
|---|---|---|
dur_sec | 300 (mín 45) | segundos totales de ejecución |
insns | 256 | instrucciones de relleno en el programa cBPF agitado; un programa más grande es una región liberada más grande que golpear. Si el attach falla con ENOMEM, eleva net.core.optmem_max (el envoltorio hace esto por ti). |
nports | 4 (máx 64) | grupos reuseport (un puerto UDP cada uno, desde 21000) |
nsocks | 8 (máx 512) | sockets por grupo |
nsenders | 4 (máx 32) | hilos emisores UDP por grupo |
ip | 127.0.0.1 | dirección objetivo; usa una IP de NIC física para repartir las softirqs de RX entre CPUs (RSS) |
ebpf | 0 | 1 = también agita el attach/detach de SO_ATTACH_REUSEPORT_EBPF (necesita CAP_BPF/CAP_NET_ADMIN); esa ruta no es vulnerable, esto es para comparación/cobertura |
Cada grupo ejecuta un hilo churner que intercambia y desadjunta el selector
cBPF mediante setsockopt() en un bucle apretado, y hilos sender que inundan
con datagramas UDP de 64 bytes al grupo mientras los receptores cuentan la entrega por socket.
Fases de ejecución (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
Comprobación de integridad: durante la fase de medición el programa final selecciona de forma determinista el último socket del grupo. Si los paquetes recibidos por el grupo durante esa ventana no aterrizaron todos en ese socket, la selección salió mal (un posible efecto de UAF incluso sin KASAN) → código de salida 2.