[BUG] bpf: warn_free_bad_obj in bpf_prog_test_run_skb - slab cross-cache confusion in skb_free_head

From: antonius <hidden>
Date: 2026-03-30 05:52:27
Also in: bpf, lkml

Hello,

I found a slab cache confusion bug in bpf_prog_test_run_skb() on Linux
7.0.0-rc5, triggered via BPF_PROG_TEST_RUN with BPF_PROG_TYPE_SCHED_CLS.

The issue was originally discovered by syzkaller during a fuzzing campaign
targeting io_uring BPF filter and BPF test_run subsystems.

*REPORTER*
Antonius / Blue Dragon Security
https://bluedragonsec.com
https://github.com/bluedragonsecurity



*Bug Description *
bpf_test_init() allocates skb->head using kzalloc() with size:
  data_size_in + NET_SKB_PAD + NET_IP_ALIGN = 284 + 32 + 2 = 318 bytes

SLUB rounds this up to the kmalloc-1k cache (704 bytes as reported by
KFENCE). However, skb_free_head() subsequently calls:
  kmem_cache_free(skbuff_small_head_cache, head)

This is the wrong cache — the object belongs to kmalloc-1k, not
skbuff_small_head. SLUB detects this mismatch and fires warn_free_bad_obj(),
followed by a KFENCE out-of-bounds read in print_track().


*Affected Code*
net/bpf/test_run.c: bpf_test_init()
net/core/skbuff.c:  skb_free_head()

The root cause is a mismatch between the allocation cache used by
bpf_test_init() and the cache assumed by skb_free_head() when determining
how to free skb->head for test skbs.

*Kernel Version*

7.0.0-rc5 (commit: confirmed on rc5 tag)
Also tested: Lubuntu 25.10 (kernel 7.0.0-rc5, CONFIG_KFENCE=y)
             Debian Trixie syzkaller VM (kernel 7.0.0-rc5, CONFIG_KFENCE=y)

*Privilege Required*

CAP_BPF or root. BPF_PROG_TYPE_SCHED_CLS requires bpf_capable().

*Reproducer*

Minimal C reproducer (2 syscalls):
n.b : I have attacher the reproducer file (repro_bpf.c)

Build: gcc -O0 -o repro_bpf repro_bpf.c
Run:
for i in $(seq 1 1000); do
    sudo ./repro_bpf
    dmesg | grep -q "warn_free\|KFENCE\|cut here" && { echo "CRASH at iter
$i!"; dmesg; break; }
    echo -n "."
done
sudo dmesg | grep warn_free_bad_obj

*Kernel Output*

[  761.069607] ------------[ cut here ]------------
[  761.069623] kmem_cache_free(skbuff_small_head, ffff888186dfac00): object
belongs to different cache kmalloc-1k
[  761.069638] WARNING: mm/slub.c:6258 at warn_free_bad_obj+0x91/0xc0,
CPU#0: repro/1513
[  761.069670] Modules linked in:
[  761.069690] CPU: 0 UID: 0 PID: 1513 Comm: repro Not tainted 7.0.0-rc5 #1
[  761.069716] RIP: 0010:warn_free_bad_obj+0x98/0xc0
[  761.069882] Call Trace:
[  761.069888]  <TASK>
[  761.069899]  skb_free_head+0x1ec/0x290
[  761.069918]  skb_release_data+0x7a6/0x9d0
[  761.069970]  bpf_prog_test_run_skb+0x14f8/0x3410
[  761.070190]  __sys_bpf+0x769/0x4b60
[  761.070422]  do_syscall_64+0x111/0x690
[  761.070456]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  761.070610]  </TASK>
[  761.073682]
==================================================================
[  761.073736] BUG: KFENCE: out-of-bounds read in print_track+0x0/0x50
[  761.073790] Out-of-bounds read at 0xffff888186dfb010 (1040B right of
kfence-#252):
[  761.074117] kfence-#252: 0xffff888186dfac00-0xffff888186dfaebf,
size=704, cache=kmalloc-1k
[  761.074168] allocated by task 1513 on cpu 0 at 761.069452s:
[  761.074198]  bpf_test_init.isra.0+0xf9/0x1e0
[  761.074218]  bpf_prog_test_run_skb+0x489/0x3410

*Security Impact*

This bug causes heap corruption via slab cross-cache confusion. An object
from kmalloc-1k is placed into the freelist of skbuff_small_head cache.
Subsequent alloc_skb() calls can reclaim this chunk, potentially leading to:
  - Information leak (stale kernel data readable via new skb->head)
  - Heap corruption if controlled data written before reclaim
  - Denial of service (kernel WARNING, system instability)

Full exploitation to LPE would require chaining with additional primitives
(KASLR bypass, heap spray). Bug is not directly exploitable for LPE without
further primitives.

CVSS v3.1 estimate: AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H = 6.7 (Medium)

*Fix Suggestion*

In bpf_test_init() (net/bpf/test_run.c), the skb->head allocation should
either:
1. Use skb_head_from_pool() or kmalloc_reserve() to ensure the allocation
   lands in the cache that skb_free_head() expects, or
2. Set skb->head_frag = 0 and clear the relevant flags so skb_free_head()
   takes the kfree() path instead of kmem_cache_free() path.

Alternatively, skb_free_head() should verify the slab cache before calling
kmem_cache_free().

---
Reported-by: Antonius <redacted>
Please use this tag in the fix commit:
  Reported-by: Antonius [off-list ref]
---
If this is a known issue or already fixed, please point me to the
relevant commit. I was unable to find a matching LKML/syzbot entry
for this specific issue

Thanks,
Antonius
Blue Dragon Security
https://bluedragonsec.com

Attachments

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help