
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.
Noyau : Linux >= 6.3, compilé avec :
CONFIG_KFENCE=y
CONFIG_BPF_SYSCALL=y
CONFIG_NET_SCH_INGRESS=y (ou tout pilote compatible SCHED_CLS)
CONFIG_SLUB_DEBUG=y (pour la visibilité de warn_free_bad_obj)
CONFIG_STACKDEPOT=y (pour la cascade complète)
Privilèges : root (uid=0) — requis pour BPF_PROG_LOAD
gcc -O2 -o cve-2026-31429-poc-only cve-2026-31429-poc-only.c
sudo ./cve-2026-31429-poc-only
dmesg | grep -E "warn_free_bad_obj|Wrong slab cache|cross-cache"
Le PoC charge un programme BPF minimal de 3 instructions (type BPF_PROG_TYPE_SCHED_CLS) :
ld_imm64 r0, 0 ; 2 insns (large)
exit ; 1 insn
Il appelle ensuite BPF_PROG_TEST_RUN (cmd=10) avec :
data_size_in = 284 octets de données de paquet dérivées de Syzkallerflags = BPF_F_TEST_RUN_ON_CPU (0x4) — épingle l'exécution sur le CPU 0repeat = 4Les 284 octets de données d'entrée exercent le chemin d'allocation de bpf_test_init de sorte que la taille du tampon demandé soit égale à SKB_SMALL_HEAD_CACHE_SIZE, atteignant de manière probabiliste la fenêtre d'interception de KFENCE.
| Commit | Arbre | Fusionné par | Date |
|---|---|---|---|
0f42e3f4fe2a | mainline | Jakub Kicinski | 2026-04-06 |
60313768a8ed | linux-stable | Greg Kroah-Hartman | 2026-04-18 |
2d64618ea846 | linux-stable | Greg Kroah-Hartman | 2026-04-18 |
474e00b935db | linux-stable | Greg Kroah-Hartman | 2026-04-18 |
Chaîne de signature : Jiayuan Chen → Reviewed-by Eric Dumazet (Google) → Jakub Kicinski → Greg Kroah-Hartman
Crédit Reported-by : Antonius <[email protected]> dans les 4 commits
Introduit par : bf9f1baa279f (« net: add dedicated kmem_cache for typical/small skb->head »)
.
├── README.md — ce fichier
├── cve-2026-31429-poc-only.c — preuve de concept uniquement (pas un exploit)
└── dmesg.txt — splat brut du noyau issu d'une reproduction réussie
| Date | Événement |
|---|---|
| ~Début 2026 | Bug découvert via le fuzzing Syzkaller sur Linux 7.0-rc5 |
| 2026-04-03 | Correctif rédigé par Jiayuan Chen, crédit Reported-by à Antonius |
| 2026-04-06 | Commit mainline 0f42e3f4fe2a fusionné par Jakub Kicinski |
| 2026-04-18 | Rétroportages stables fusionnés par Greg Kroah-Hartman (6.12.x, 6.18.x, 6.19.x) |
| 2026-04-20 | CVE-2026-31429 publiée |
Antonius (surnom : w1sdom)
Fondateur et chercheur senior — Blue Dragon Security
Indonésie
[email protected]
Ce PoC est publié à des fins éducatives et de recherche après la disponibilité du correctif en amont. Ne l'utilisez pas sur des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation explicite de test. L'auteur décline toute responsabilité en cas d'utilisation abusive.