
POC para CVE-2026-31429 (Linux Kernel >= 6.3 < 6.12.82 Confusión Cross-Cache de Slab) - vulnerabilidad descubierta por Antonius - w1sdom - bluedragonsec.com
bpf_prog_test_run_skbGravedad: Media (CWE-763: Liberación de Puntero o Referencia Inválida)
Publicado: 2026-04-20
Subsistema afectado: net/core/skbuff.c — skb_kfree_head()
Investigador: Antonius / w1sdom — Blue Dragon Security
Contacto: [email protected]
Hilo de lore: https://lore.kernel.org/netdev/CAK8a0jxC5L5N7hq-DT2_NhUyjBxrPocoiDazzsBk4TGgT1r4-A@mail.gmail.com/
Este repositorio contiene la prueba de concepto para CVE-2026-31429 (no es un exploit funcional, solo un POC), un error de confusión cross-cache de slabs en la pila de red del kernel Linux. El error se desencadena cuando KFENCE está habilitado y un llamador (específicamente bpf_test_init en net/bpf/test_run.c) asigna un búfer de cabeza SKB mediante kzalloc() con un tamaño que coincide con SKB_SMALL_HEAD_CACHE_SIZE. Debido a la semántica de reporte de tamaño exacto de KFENCE, la función skb_kfree_head() del kernel libera incorrectamente el objeto de vuelta a skb_small_head_cache en lugar de la caché original kmalloc-1k, corrompiendo los metadatos del slab.
| Estado | Rango |
|---|---|
| Afectado | Linux >= 6.3 (introducido por bf9f1baa279f) |
| No afectado | < 6.3 |
| Corregido | >= 6.12.82 |
| Corregido | >= 6.18.23 |
| Corregido | >= 6.19.13 |
| Corregido | >= 7.0 (mainline, commit 0f42e3f4fe2a) |
La vulnerabilidad fue introducida por el commit bf9f1baa279f ("net: add dedicated kmem_cache for typical/small skb->head"), que añadió skb_small_head_cache y la lógica de liberación condicional en skb_kfree_head().
skb_small_head_cacheSKB_SMALL_HEAD_CACHE_SIZE se establece intencionalmente a un valor que no es potencia de 2 (p. ej. 704 bytes en x86_64) para evitar colisiones con los tamaños genéricos de buckets de kmalloc (siempre potencias de 2: 512, 1024, ...). La heurística en skb_kfree_head() explota esta unicidad para enrutar las liberaciones usando solo skb_end_offset:
// net/core/skbuff.c (VULNERABLE — pre-fix)
static void skb_kfree_head(void *head, unsigned int end_offset)
{
if (end_offset == SKB_SMALL_HEAD_HEADROOM)
kmem_cache_free(net_hotdata.skb_small_head_cache, head);
else
kfree(head);
}
end_offset == SKB_SMALL_HEAD_HEADROOM → se asume de skb_small_head_cache → kmem_cache_free()kfree() genéricoEsta heurística es válida solo bajo semántica normal de slabs, donde ksize() devuelve el tamaño del bucket (1024 para una solicitud de 704 bytes), que nunca es igual a SKB_SMALL_HEAD_CACHE_SIZE.
KFENCE (Kernel Electric-Fence) intercepta un subconjunto de asignaciones del kernel y las sirve desde memoria con páginas de guarda. Su diferencia de comportamiento crítica: kfence_ksize() devuelve el tamaño solicitado exacto, no el tamaño del bucket del slab.
BPF_PROG_TEST_RUN (syscall 321, cmd BPF_PROG_TEST_RUN=10)
└─> __sys_bpf()
└─> bpf_prog_test_run_skb()
└─> bpf_test_init()
└─> kzalloc(size, GFP_USER)
│ size == SKB_SMALL_HEAD_CACHE_SIZE (704 en x86_64)
│ KFENCE intercepta → objeto servido desde la región kmalloc-1k
│
└─> slab_build_skb(data, NULL, size)
└─> ksize(data)
└─> kfence_ksize() ← devuelve 704 (¡exacto!)
└─> skb_end_offset
= ksize(data) - sizeof(skb_shared_info)
= 704 - 320
= 384
= SKB_SMALL_HEAD_HEADROOM ← ¡coincidencia falsa!
[En la ruta de liberación de SKB:]
└─> sk_skb_reason_drop()
└─> skb_release_data()
└─> skb_free_head()
└─> skb_kfree_head(head, skb->end)
└─> (end_offset == SKB_SMALL_HEAD_HEADROOM) == TRUE
└─> kmem_cache_free(skb_small_head_cache, head)
↑ ERROR: head proviene de kmalloc-1k, ¡no de skb_small_head_cache!
→ warn_free_bad_obj() → corrupción de SLUB
skb_end_offset = 384?En x86_64:
SKB_SMALL_HEAD_CACHE_SIZE = 704 bytes
sizeof(skb_shared_info) = 320 bytes
SKB_SMALL_HEAD_HEADROOM = 704 - 320 = 384
Cuando KFENCE intercepta el kzalloc() de 704 bytes, kfence_ksize() devuelve exactamente 704. La aritmética produce skb_end_offset = 384 = SKB_SMALL_HEAD_HEADROOM, satisfaciendo la condición en skb_kfree_head() — desencadenando la ruta de liberación incorrecta.
La corrección upstream de Jiayuan Chen (revisada por Eric Dumazet, fusionada por Jakub Kicinski) elimina la heurística por completo:
// net/core/skbuff.c (CORREGIDO)
static void skb_kfree_head(void *head, unsigned int end_offset)
{
kfree(head); // siempre genérico; funciona para ambos casos
}
kfree() es seguro tanto para memoria asignada por kmalloc como para memoria asignada por skb_small_head_cache porque kmem_cache_free() en skb_small_head_cache ya no es necesario — el kfree() genérico resuelve la caché correcta internamente mediante el puntero kmem_cache de la página del slab.
El reproductor (repro_bpf.c) se ejecutó en Linux 7.0.0-rc5 en un entorno QEMU (i440FX, BIOS 1.17.0-debian). Se observó la siguiente cascada de WARNING del kernel:
[ 3065.322973] ------------[ cut here ]------------
[ 3065.322990] kmem_cache_free(skbuff_small_head, ffff888186d6e000): object belongs to different cache kmalloc-1k
[ 3065.323005] WARNING: mm/slub.c:6258 at warn_free_bad_obj+0x91/0xc0, CPU#0: repro_bpf/2167
[ 3065.323061] CPU: 0 UID: 0 PID: 2167 Comm: repro_bpf Not tainted 7.0.0-rc5 #1 PREEMPT(lazy)
[ 3065.323098] RIP: 0010:warn_free_bad_obj+0x98/0xc0
...
[ 3065.323231] Call Trace:
[ 3065.323247] skb_free_head+0x1ec/0x290
[ 3065.323267] skb_release_data+0x7a6/0x9d0
[ 3065.323308] bpf_prog_test_run_skb+0x14f8/0x3410
[ 3065.323510] __sys_bpf+0x769/0x4b60
[ 3065.323763] __x64_sys_bpf+0x78/0xc0
[ 3065.323794] do_syscall_64+0x111/0x690
[ 3065.323813] entry_SYSCALL_64_after_hwframe+0x77/0x7f
La cascada de WARNING produce 4 splats separados por activación:
warn_free_bad_obj — detección primaria de liberación cross-cache (mm/slub.c:6258)depot_fetch_stack — índice de pool del stack depot fuera de límites (lib/stackdepot.c:506) en el seguimiento Allocatedstack_depot_print — handle corrupto detectado (lib/stackdepot.c:780)depot_fetch_stack + stack_depot_print — mismo par repetido para el seguimiento FreedEsta cascada indica que los metadatos de seguimiento SLUB del objeto (alloc_track / free_track) referencian un handle del stack depot que se corrompe después de la liberación en la caché incorrecta.