Root Cause and Crash Analysis
1. Crash Site and Exact Failure Mechanism
The crash occurs in softirq / RCU callback processing:
rcu_core() -> rcu_do_batch() -> rcu_free_sheaf() -> __rcu_free_sheaf_prepare()
The trapping instruction is:
mov 0x38(%rbx), %r8
where %rbx == 0x0 (NULL), attempting to access offset 0x38 (56 bytes).
In mm/slub.c:3036-3038, __rcu_free_sheaf_prepare() processes each object queued in the RCU sheaf:
static bool __rcu_free_sheaf_prepare(struct kmem_cache *s,
struct slab_sheaf *sheaf)
{
...
while (i < sheaf->size) {
struct slab *slab = virt_to_slab(p[i]);
memcg_slab_free_hook(s, slab, p + i, 1);
...
memcg_slab_free_hook() in mm/slub.c:2572 unconditionally calls slab_obj_exts(slab):
static inline unsigned long slab_obj_exts(struct slab *slab)
{
unsigned long obj_exts = READ_ONCE(slab->obj_exts); // slab->obj_exts is at offset 0x38 (56 bytes)
return obj_exts;
}
Because slab == NULL, accessing slab->obj_exts (0x38(%rbx)) results in a supervisor read page fault at 0x0000000000000038, causing an immediate kernel NULL pointer dereference panic.
2. Disassembly & Page Invariant Verification
In virt_to_slab(addr):
static inline struct slab *page_slab(const struct page *page)
{
page = compound_head(page);
if (data_race(page->page_type >> 24) != PGTY_slab)
page = NULL;
return (struct slab *)page;
}
The disassembled instructions leading up to the crash verify that page_slab() executed:
cmpb $0xf5, 0x33(%rbx) ; PGTY_slab == 0xf5 at offset 0x33 of struct page
cmovne %rax, %rbx ; %rax is 0, so %rbx becomes NULL if page_type != PGTY_slab
mov 0x38(%rbx), %r8 ; Dereference 0x38(NULL) -> CRASH
This confirms that when the RCU grace period expired and rcu_free_sheaf() ran, the page containing object p[0] no longer had PGTY_slab set (i.e., it was not a slab page or had already been returned to the buddy allocator via free_slab() -> __free_slab()).
3. Object Lifecycle and Hypotheses Evaluated
- KFENCE Pool Objects (Ruled Out):
kfence_init_pool() in mm/kfence/core.c unconditionally executes __SetPageSlab(page) on all pool pages at boot, and their page_type is never cleared. Therefore, KFENCE objects do not produce virt_to_slab() == NULL.
- Bootstrap Sheaves (Ruled Out):
Bootstrap sheaves have capacity 0 and are never handed to RCU sheaf freeing paths (
__kfree_rcu_sheaf() checks cache_has_sheaves(s)).
- Missing NULL Validation in
__rcu_free_sheaf_prepare (Verified):
virt_to_slab() is known to be able to return NULL for addresses whose slab pages have been reclaimed or corrupted, but __rcu_free_sheaf_prepare() performs no check for !slab before passing it to memcg_slab_free_hook(s, slab, ...).
- Trigger Scenario (Partially Inconclusive):
An object can be queued into an RCU sheaf via
kfree_call_rcu_nolock() or kfree_rcu_sheaf(). At the moment of queuing, virt_to_slab() is verified to be non-NULL. However, during the grace period before rcu_free_sheaf() executes:
- If a caller double-frees an object (e.g. through a race between
kfree()/kmem_cache_free() and kfree_rcu()), the slab's inuse count reaches 0 from the regular free path, allowing discard_slab()/free_slab() to return the slab page to the buddy allocator and clear PGTY_slab.
- Alternatively, if cache shrinking or tearing down occurs, or in interaction with
CONFIG_SLUB_RCU_DEBUG (which defers object reuse for SLAB_TYPESAFE_BY_RCU), a slab page may be freed before the sheaf's RCU callback processes its cached object pointer.
Pinpointing the exact offending caller/driver that queued the specific object p[0] is inconclusive from the report alone because syzkaller's report captures only the softirq RCU callback execution on the idle CPU (swapper/2), not the earlier allocation or freeing stack trace.
Classification & Direct Consequences
- RootCauseFiles:
mm/slub.c, mm/slab.h
- MemoryCorruption:
false
The direct crash is a pure NULL-pointer dereference at 0x0000000000000038 when reading slab->obj_exts. Pure NULL-pointer dereferences do not corrupt memory.
- InfoLeak:
"none"
No kernel memory or pointer values are disclosed to userspace or an observer.
- RawDenialOfService:
true
The supervisor read access to address 0x38 triggers a kernel Oops/panic in interrupt/softirq context (rcu_core), halting the machine.
- IsNetCandidate:
false
The bug is in core SLUB memory management and RCU callback execution; no networking subsystems or network packets are involved in the crash trace.
- IsKVMCandidate:
false
No KVM or virtualization components are involved.