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

From: Pu Lehui

Date: Fri Sep 04 2026 - 23:58:07 EST



On 2026/9/5 11:00, bot+bpf-ci@xxxxxxxxxx wrote:
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?

This is reproducible on mainline too, and the log makes the race pretty obvious at a glance. I don't think there's any real need to change it.


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?

PREEMPT_RT was just a handy example here.



---
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