UBUNTU-CVE-2026-98642: Use After Free
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 consumption of the waiting_for_gp_ttrace and free_by_rcu_ttrace lists, protecting alloc_bulk() from concurrent llist_del_all()/free_all() operations.