
POC pour CVE-2026-31429 (Linux Kernel >= 6.3 < 6.12.82 Slab Cross-Cache Confusion) - vulnérabilité découverte par Antonius - w1sdom - bluedragonsec.com
bpf_prog_test_run_skbSévérité : Moyenne (CWE-763 : Libération d'un pointeur ou d'une référence invalide)
Publié : 2026-04-20
Sous-système concerné : net/core/skbuff.c — skb_kfree_head()
Chercheur : Antonius / w1sdom — Blue Dragon Security
Contact : [email protected]
Fil de discussion Lore : https://lore.kernel.org/netdev/CAK8a0jxC5L5N7hq-DT2_NhUyjBxrPocoiDazzsBk4TGgT1r4-A@mail.gmail.com/
Ce dépôt contient la preuve de concept pour CVE-2026-31429 (pas un exploit fonctionnel, juste un POC), un bug de confusion cross-cache de slab dans la pile réseau du noyau Linux. Le bug est déclenché lorsque KFENCE est activé et qu'un appelant (plus précisément bpf_test_init dans net/bpf/test_run.c) alloue un tampon d'en-tête SKB via kzalloc() avec une taille qui se trouve être égale à SKB_SMALL_HEAD_CACHE_SIZE. En raison de la sémantique de rapport de taille exacte de KFENCE, la fonction skb_kfree_head() du noyau libère incorrectement l'objet vers skb_small_head_cache au lieu du cache kmalloc-1k d'origine, corrompant les métadonnées du slab.
| Statut | Plage |
|---|---|
| Concerné | Linux >= 6.3 (introduit par bf9f1baa279f) |
| Non concerné | < 6.3 |
| Corrigé | >= 6.12.82 |
| Corrigé | >= 6.18.23 |
| Corrigé | >= 6.19.13 |
| Corrigé | >= 7.0 (mainline, commit 0f42e3f4fe2a) |
La vulnérabilité a été introduite par le commit bf9f1baa279f (« net: add dedicated kmem_cache for typical/small skb->head »), qui a ajouté skb_small_head_cache et la logique de libération conditionnelle dans skb_kfree_head().
skb_small_head_cacheSKB_SMALL_HEAD_CACHE_SIZE est intentionnellement défini sur une valeur non puissance de 2 (par ex. 704 octets sur x86_64) pour éviter les collisions avec les tailles génériques des buckets kmalloc (toujours des puissances de 2 : 512, 1024, ...). L'heuristique dans skb_kfree_head() exploite cette unicité pour acheminer les libérations en utilisant uniquement skb_end_offset :
// net/core/skbuff.c (VULNÉRABLE — avant correctif)
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 → supposé provenir de skb_small_head_cache → kmem_cache_free()kfree() génériqueCette heuristique n'est valable que sous une sémantique de slab normale, où ksize() renvoie la taille du bucket (1024 pour une requête de 704 octets), qui n'est jamais égale à SKB_SMALL_HEAD_CACHE_SIZE.
KFENCE (Kernel Electric-Fence) intercepte un sous-ensemble des allocations du noyau et les sert depuis une mémoire à pages gardées. Sa différence comportementale critique : kfence_ksize() renvoie la taille exacte demandée, et non la taille du bucket de 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 sur x86_64)
│ KFENCE intercepte → objet servi depuis la région kmalloc-1k
│
└─> slab_build_skb(data, NULL, size)
└─> ksize(data)
└─> kfence_ksize() ← renvoie 704 (exact !)
└─> skb_end_offset
= ksize(data) - sizeof(skb_shared_info)
= 704 - 320
= 384
= SKB_SMALL_HEAD_HEADROOM ← fausse correspondance !
[Sur le chemin de libération 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)
↑ BUG : head provient de kmalloc-1k, pas de skb_small_head_cache !
→ warn_free_bad_obj() → corruption SLUB
skb_end_offset = 384 ?Sur x86_64 :
SKB_SMALL_HEAD_CACHE_SIZE = 704 octets
sizeof(skb_shared_info) = 320 octets
SKB_SMALL_HEAD_HEADROOM = 704 - 320 = 384
Lorsque KFENCE intercepte le kzalloc() de 704 octets, kfence_ksize() renvoie exactement 704. L'arithmétique produit skb_end_offset = 384 = SKB_SMALL_HEAD_HEADROOM, satisfaisant la condition dans skb_kfree_head() — déclenchant le mauvais chemin de libération.
Le correctif en amont de Jiayuan Chen (revu par Eric Dumazet, fusionné par Jakub Kicinski) élimine entièrement l'heuristique :
// net/core/skbuff.c (CORRIGÉ)
static void skb_kfree_head(void *head, unsigned int end_offset)
{
kfree(head); // toujours générique ; fonctionne pour les deux cas
}
kfree() est sûr à la fois pour la mémoire allouée par kmalloc et par skb_small_head_cache car kmem_cache_free() sur skb_small_head_cache n'est plus nécessaire — le kfree() générique résout le cache correct en interne via le pointeur kmem_cache de la page de slab.
Le reproducteur (repro_bpf.c) a été exécuté sur Linux 7.0.0-rc5 dans un environnement QEMU (i440FX, BIOS 1.17.0-debian). La cascade d'avertissements du noyau suivante a été observée :
[ 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 cascade d'avertissements produit 4 splats distincts par déclenchement :
warn_free_bad_obj — détection primaire de libération cross-cache (mm/slub.c:6258)depot_fetch_stack — index du pool du dépôt de piles hors limites (lib/stackdepot.c:506) sur le suivi Allocatedstack_depot_print — handle corrompu détecté (lib/stackdepot.c:780)depot_fetch_stack + stack_depot_print — même paire répétée pour le suivi FreedCette cascade indique que les métadonnées de suivi SLUB de l'objet (alloc_track / free_track) référencent un handle du dépôt de piles qui devient corrompu après la libération dans le mauvais cache.