CVE-2026-98642: bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
In the Linux kernel, the following vulnerability has been resolved:
bpf: Fix UAF due to concurrent consumption of ttrace lists in allocbulk
Syzkaller repeatedly triggered UAF splats related to nodes in waitingforgpttrace within the bpf memalloc:
BUG: KASAN: slab-use-after-free in llistdelfirst+0x85/0x110 lib/llist.c:61 Read of size 8 at addr ffff8881572cd080 by task syz.4.470/5112 ... llistdelfirst+0x85/0x110 lib/llist.c:61 allocbulk+0x193/0x460 kernel/bpf/memalloc.c:229 bpfmemrefill+0x386/0x560 kernel/bpf/memalloc.c:436
Freed by task 14: ... freercu kernel/bpf/memalloc.c:281 [inline] freercutaskstrace+0x48/0xd0 kernel/bpf/memalloc.c:291 rcutasksinvokecbs+0x1ec/0x3e0 kernel/rcu/tasks.h:571 rcutasksonegp+0x13d/0x220 kernel/rcu/tasks.h:621 rcutaskskthread+0xf3/0x120 kernel/rcu/tasks.h:651
The reason is that the UAF occurs after the RCU Tasks Trace GP expires: when the freercu() callback runs, there is no synchronization protecting llistdelall() against concurrent allocbulk() operating on waitingforgpttrace, leading to the race condition below:
CPU0 CPU1 freercu (RCU Tasks Trace callback) allocbulk llistdelfirst(&c->waitingforgpttrace) entry = smploadacquire(&head->first); do { if (entry == NULL) return NULL; freeall(llistdelall(&c->waitingforgpttrace)) llistforeachsafe(pos, t, llnode) freeone(pos); next = READONCE(entry->next); <-- trigger UAF } while (!trycmpxchg(&head->first, &entry, next));
In addition, there is also a theoretical race condition on the freebyrcuttrace list. This race requires two preconditions: an in-flight Tasks Trace GP keeping c->callrcuttraceinprogress == 1, and concurrent cross-CPU frees repopulating c->freebyrcuttrace with new nodes. Under these conditions, the following scenario triggers UAF:
// CPU0 // irq work is still busy (on PREEMPTRT) allocbulk() llistdelfirst(&c->freebyrcuttrace) entry = smploadacquire(&head->first); do { if (entry == NULL) return NULL;
// CPU1 bpfmemallocdestroy() WRITEONCE(c->draining, true) // wait for CPU0 irqworksync()
// CPU2 docallrcuttrace(tgt(CPU0)) if (c->draining) { llistdelall(&c->freebyrcuttrace) freeall() }
// CPU0 continue next = READONCE(entry->next); <-- trigger UAF while (!trycmpxchg(&head->first, &entry, next));
Fix this by introducing a raw spinlock to synchronize the concurrent consumption on waitingforgpttrace and freebyrcuttrace.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Compensating control
Introduce a raw spinlock to synchronize concurrent llist_del_all() and alloc_bulk() operations on the BPF memalloc ttrace lists, preventing the use-after-free race.
Event History
Frequently Asked Questions
What conditions trigger the use-after-free?
The race involves concurrent access to the BPF memalloc waiting_for_gp_ttrace list. It occurs when alloc_bulk() consumes list entries while an RCU Tasks Trace grace period completes and the __free_rcu() callback frees entries from the same list.
How was this issue observed?
Syzkaller repeatedly triggered KASAN slab-use-after-free reports. The reported read occurred in llist_del_first() during alloc_bulk(), while the affected object had been freed by the RCU Tasks Trace callback path.
What code paths are involved when investigating a crash?
The allocation-side stack includes llist_del_first(), alloc_bulk(), and bpf_mem_refill(). The freeing-side stack includes __free_rcu(), __free_rcu_tasks_trace(), rcu_tasks_invoke_cbs(), and the RCU Tasks Trace kernel thread.