Re: [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk

From: bot+bpf-ci

Date: Fri Sep 04 2026 - 23:03:02 EST


> Syzkaller repeatedly triggered UAF splats related to nodes in
> waiting_for_gp_ttrace within the bpf memalloc:
> BUG: KASAN: slab-use-after-free in llist_del_first+0x85/0x110 lib/llist.c:61
> Read of size 8 at addr ffff8881572cd080 by task syz.4.470/5112
> ...
> llist_del_first+0x85/0x110 lib/llist.c:61
> alloc_bulk+0x193/0x460 kernel/bpf/memalloc.c:229
> bpf_mem_refill+0x386/0x560 kernel/bpf/memalloc.c:436
> Freed by task 14:
> ...
> __free_rcu kernel/bpf/memalloc.c:281 [inline]
> __free_rcu_tasks_trace+0x48/0xd0 kernel/bpf/memalloc.c:291
> rcu_tasks_invoke_cbs+0x1ec/0x3e0 kernel/rcu/tasks.h:571
> rcu_tasks_one_gp+0x13d/0x220 kernel/rcu/tasks.h:621
> rcu_tasks_kthread+0xf3/0x120 kernel/rcu/tasks.h:651

The file and line numbers in this stack trace refer to v6.6 (the v1
posting included 'Not tainted 6.6.0+', which was dropped in later
versions). Against the current tree, these line numbers point to different
locations: alloc_bulk() line 229 is in a blank/'if (i >= cnt)' section
(the waiting_for_gp_ttrace access is now at line 231), bpf_mem_refill()
line 436 is the closing brace, and __free_rcu_tasks_trace() no longer
exists at all (removed by commit 57b23c0f612d).

Should the stack trace include the kernel version to prevent future
readers from trying to map these line numbers onto the current tree?

> In addition, there is also a theoretical race condition on the
> free_by_rcu_ttrace list. This race requires two preconditions: an
> in-flight Tasks Trace GP keeping c->call_rcu_ttrace_in_progress == 1,
> and concurrent cross-CPU frees repopulating c->free_by_rcu_ttrace with
> new nodes. Under these conditions, the following scenario triggers UAF:
> // CPU0
> // irq work is still busy (on PREEMPT_RT)
> alloc_bulk()
> llist_del_first(&c->free_by_rcu_ttrace)
> entry = smp_load_acquire(&head->first);
> do {
> if (entry == NULL)
> return NULL;
> // CPU1
> bpf_mem_alloc_destroy()
> WRITE_ONCE(c->draining, true)
> // wait for CPU0
> irq_work_sync()
> // CPU2
> do_call_rcu_ttrace(tgt(CPU0))
> if (c->draining) {
> llist_del_all(&c->free_by_rcu_ttrace)
> free_all()
> }
> // CPU0 continue
> next = READ_ONCE(entry->next); <-- trigger UAF

The annotation suggests this race window exists only on PREEMPT_RT
kernels. However, on non-PREEMPT_RT configurations, alloc_bulk() runs in
hard IRQ context and irq_work_sync() from another CPU still has to wait
for it, so the same race window exists there too. The earlier comment that
commit d114dde245f9 removed also mentioned non-RT cases: 'arch_irq_work_
has_interrupt() being false and irq work is invoked in timer interrupt.'

Would '(e.g., on PREEMPT_RT)' be more accurate than '(on PREEMPT_RT)' to
indicate this is an example rather than a precondition?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33938697434