Re: [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
From: Hou Tao
Date: Sun Sep 06 2026 - 23:04:47 EST
On 9/5/2026 10:11 AM, Pu Lehui wrote:
> From: Pu Lehui <pulehui@xxxxxxxxxx>
>
> 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 reason is that the UAF occurs after the RCU Tasks Trace GP expires:
> when the __free_rcu() callback runs, there is no synchronization
> protecting llist_del_all() against concurrent alloc_bulk() operating on
> waiting_for_gp_ttrace, leading to the race condition below:
>
> CPU0 CPU1
> __free_rcu (RCU Tasks Trace callback)
> alloc_bulk
> llist_del_first(&c->waiting_for_gp_ttrace)
> entry = smp_load_acquire(&head->first);
> do {
> if (entry == NULL)
> return NULL;
> free_all(llist_del_all(&c->waiting_for_gp_ttrace))
> llist_for_each_safe(pos, t, llnode)
> free_one(pos);
> next = READ_ONCE(entry->next); <-- trigger UAF
> } while (!try_cmpxchg(&head->first, &entry, next));
>
> 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
> while (!try_cmpxchg(&head->first, &entry, next));
>
> Fix this by introducing a raw spinlock to synchronize the concurrent
> consumption on waiting_for_gp_ttrace and free_by_rcu_ttrace.
>
> Fixes: 04fabf00b4d3 ("bpf: Allow reuse from waiting_for_gp_ttrace list.")
> Suggested-by: Alexei Starovoitov <ast@xxxxxxxxxx>
> Suggested-by: Hou Tao <houtao1@xxxxxxxxxx>
> Signed-off-by: Pu Lehui <pulehui@xxxxxxxxxx>
Acked-by: Hou Tao <houtao1@xxxxxxxxxx>