syzbot
BUG: unable to handle kernel NULL pointer dereference in __refill_objects_node
Status:
moderation: reported on 2026/11/01 02:26
Subsystems:
bluetooth
Labels:
prio:high
[Documentation on labels]
Reported-by: syzbot+35c18d9d31d4326e6b81@syzkaller.appspotmail.com
First crash: 4d09h, last: 4d09h
✨ AI-generated root cause summary:
In mm/slub.c, __refill_objects_node() replenishes percpu sheaves by detaching a freelist from a partial slab via get_freelist_nofreeze() and iterating through the freelist pointers in a while (count && refilled < max) loop. Because the loop condition only checks count and refilled rather than validating that object is non-NULL, a prematurely terminated or corrupted freelist (where fewer objects are linked in slab->freelist than the expected count = slab->objects - slab->inuse) causes object to become NULL before count reaches zero. On the subsequent iteration, get_freepointer() dereferences NULL + s->offset (offset 0x400 for kmalloc-2k), resulting in an immediate kernel NULL pointer dereference crash at address 0x0000000000000400.
▶
▼
✨ Full AI root cause analysis
Technical Root Cause Analysis
1. Functions, Structs, and Files Involved
- Source File:
mm/slub.c
- Functions:
- Caller Context:
l2cap_chan_create() in net/bluetooth/l2cap_core.c:447 allocating a struct l2cap_chan via kzalloc_obj() from the kmalloc-2k slab cache (s->offset = 0x400 / 1024, s->size = 2048).
- Structs and Fields:
struct kmem_cache: s->offset (freepointer byte offset, 0x400 / 1024 for kmalloc-2k).
struct slab: slab->freelist and slab->counters (inuse, objects, frozen).
struct freelist_counters: counters union holding inuse:16, objects:15, and frozen:1.
struct slub_percpu_sheaves: per-CPU sheaf cache structure managing batch allocations.
2. Failure Mechanism and Invariant Violation
-
Trigger Path:
A userspace process issues a socket(AF_BLUETOOTH, ...) syscall. In l2cap_chan_create(), the kernel calls kzalloc_obj(*chan, GFP_ATOMIC) to allocate struct l2cap_chan. This invokes alloc_from_pcs() in the SLUB allocator to fetch an object from the local per-CPU sheaf (PCS). Because the sheaf is empty, alloc_from_pcs() calls __pcs_replace_empty_main() $\to$ refill_sheaf() $\to$ refill_objects() $\to$ __refill_objects_node().
-
Detaching Freelist via get_freelist_nofreeze():
__refill_objects_node() calls get_partial_node_bulk() to grab partial slabs from n->partial into a local list pc.slabs. For the slab at 0xfffffdffc322ee00, get_freelist_nofreeze() is called:
static inline void *get_freelist_nofreeze(struct kmem_cache *s, struct slab *slab,
unsigned int *count)
{
...
do {
old.freelist = slab->freelist;
old.counters = slab->counters;
new.freelist = NULL;
new.counters = old.counters;
new.inuse = old.objects;
} while (!slab_update_freelist(s, slab, &old, &new, "get_freelist_nofreeze"));
*count = old.objects - old.inuse;
return old.freelist;
}
At the crash site, old.counters was 0x00080002 (objects = 8, inuse = 2), and new.counters was 0x00080008 (objects = 8, inuse = 8). Based on the counters, get_freelist_nofreeze() computed *count = old.objects - old.inuse = 6.
-
Loop Invariant Violation in __refill_objects_node():
__refill_objects_node() iterates over the detached freelist:
object = get_freelist_nofreeze(s, slab, &count);
while (count && refilled < max) {
p[refilled] = object;
object = get_freepointer(s, object);
maybe_wipe_obj_freeptr(s, p[refilled]);
refilled++;
count--;
}
The loop invariant assumes that the singly-linked object freelist chain contains at least count valid objects. However:
- On this slab, after popping 3 objects (
refilled = 0, 1, 2), the third object contained 0x0 (NULL) at offset 0x400, causing object to become NULL prematurely, while 3 free objects were still accounted for by count.
- The loop condition
while (count && refilled < max) only verifies count != 0 and refilled < max, without verifying object != NULL.
- On the 4th iteration (
refilled = 3, max = 4, count > 0), p[3] = object writes NULL into the sheaf array, and the code immediately invokes:
object = get_freepointer(s, object);
- In
get_freepointer():
ptr_addr = (unsigned long)object + s->offset; // 0 + 0x400 = 0x400
p = *(freeptr_t *)(ptr_addr); // ldr x28, [x8, x27] where x27=0, x8=0x400
- The CPU dereferences address
0x0000000000000400, triggering a kernel translation fault / Data Abort (ESR = 0x96000005, FSC = 0x05: level 1 translation fault).
3. Direct Consequences in Production
- Memory Corruption (
MemoryCorruption: true):
The defect stems from slab freelist / counter corruption: the freelist pointer chain terminated prematurely while the accounting metadata (slab->objects - slab->inuse) indicated objects were still free. Furthermore, if execution did not fault at get_freepointer(), storing NULL into p[refilled] would propagate a NULL pointer into the per-CPU sheaf as a valid allocated object. A subsequent allocator caller retrieving this slot receives a NULL pointer that is treated as an active heap object, causing severe data structure inconsistency and memory corruption.
- Denial of Service (
RawDenialOfService: true):
The invalid memory access occurs in kernel mode during syscall processing (sys_socket), resulting in an immediate kernel oops/panic. In production kernels, this fatal data abort halts execution or crashes the active task holding critical locks.
- Information Leak (
InfoLeak: "none"):
Execution halts abruptly on the invalid load at 0x400. No kernel memory contents are exposed or returned to userspace.
- Network Candidate (
IsNetCandidate: true):
The crash occurs during network socket creation for Bluetooth L2CAP (bt_sock_create() / l2cap_sock_create() / l2cap_chan_create()).
- KVM Candidate (
IsKVMCandidate: false):
The issue is purely within core kernel slab memory management and the Bluetooth socket subsystem, with no relation to virtualization or KVM.
Sample crash report:
Unable to handle kernel NULL pointer dereference at virtual address 0000000000000400
Mem abort info:
ESR = 0x0000000096000005
EC = 0x25: DABT (current EL), IL = 32 bits
SET = 0, FnV = 0
EA = 0, S1PTW = 0
FSC = 0x05: level 1 translation fault
Data abort info:
ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000
CM = 0, WnR = 0, TnD = 0, TagAccess = 0
GCS = 0, Overlay = 0, DirtyBit = 0
user pgtable: 4k pages, 48-bit VAs, pgdp=0000000130a12000
[0000000000000400] pgd=0800000141542403, p4d=0800000141542403, pud=0000000000000000
Internal error: Oops: 0000000096000005 [#1] SMP
Modules linked in:
CPU: 1 UID: 0 PID: 8392 Comm: syz.0.758 Tainted: G M syzkaller #0 PREEMPT
Tainted: [M]=MACHINE_CHECK
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 08/14/2026
pstate: 83400005 (Nzcv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--)
pc : get_freepointer mm/slub.c:564 [inline]
pc : __refill_objects_node+0x308/0x510 mm/slub.c:7292
lr : get_freelist_nofreeze mm/slub.c:4457 [inline]
lr : __refill_objects_node+0x2b8/0x510 mm/slub.c:7288
sp : ffff8000963378d0
x29: ffff800096337960 x28: 0000000000000000 x27: 0000000000000000
x26: 0000000000000004 x25: fffffdffc322ee00 x24: 0000000000000003
x23: 0000000000000004 x22: 0000000000000004 x21: 0000000000000001
x20: ffff0000c1b2f938 x19: 0000000000000004 x18: 0000000000000000
x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000001
x14: 0000000000886339 x13: 0000000000000001 x12: 0000000000000003
x11: 0000000000000000 x10: fffffdffc322ee20 x9 : ffff0000c1b2f920
x8 : 0000000000000400 x7 : 0000000000000000 x6 : 0000000000000000
x5 : 0000000000000001 x4 : 0000000000000008 x3 : 0000000000080008
x2 : 0000000000000000 x1 : 0000000000080002 x0 : 0000000000000001
Call trace:
get_freepointer mm/slub.c:563 [inline] (P)
__refill_objects_node+0x308/0x510 mm/slub.c:7292 (P)
refill_objects+0x78/0x320 mm/slub.c:7405
refill_sheaf+0x44/0x6c mm/slub.c:2885
__pcs_replace_empty_main+0x22c/0x4f4 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__kmalloc_cache_noprof+0x37c/0x5fc mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
l2cap_chan_create+0x58/0x6ec net/bluetooth/l2cap_core.c:447
l2cap_sock_create+0x128/0x2d0 net/bluetooth/l2cap_sock.c:2063
bt_sock_create+0x14c/0x240 net/bluetooth/af_bluetooth.c:132
__sock_create+0x31c/0x670 net/socket.c:1676
sock_create net/socket.c:1734 [inline]
__sys_socket_create+0xc0/0x11c net/socket.c:1771
__sys_socket+0x44/0xec net/socket.c:1818
__do_sys_socket net/socket.c:1832 [inline]
__se_sys_socket net/socket.c:1830 [inline]
__arm64_sys_socket+0x84/0x9c net/socket.c:1830
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x244 arch/arm64/kernel/syscall.c:49
el0_svc_common+0xec/0x23c arch/arm64/kernel/syscall.c:121
do_el0_svc+0x4c/0x5c arch/arm64/kernel/syscall.c:140
el0_svc+0x60/0x1d8 arch/arm64/kernel/entry-common.c:796
el0t_64_sync_handler+0x44/0x104 arch/arm64/kernel/entry-common.c:815
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:606
Code: f9401fe8 f900029b 2a1a03f7 b9402908 (f87b691c)
---[ end trace 0000000000000000 ]---
----------------
Code disassembly (best guess):
0: f9401fe8 ldr x8, [sp, #56]
4: f900029b str x27, [x20]
8: 2a1a03f7 mov w23, w26
c: b9402908 ldr w8, [x8, #40]
* 10: f87b691c ldr x28, [x8, x27] <-- trapping instruction
* Struck through repros no longer work on HEAD.