From: Pu Lehui <pulehui@huawei.com>
To: Hou Tao <houtao@huaweicloud.com>,
Pu Lehui <pulehui@huaweicloud.com>, <bpf@vger.kernel.org>,
<linux-kernel@vger.kernel.org>
Cc: Alexei Starovoitov <ast@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Andrii Nakryiko <andrii@kernel.org>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
Martin KaFai Lau <martin.lau@linux.dev>,
Yonghong Song <yonghong.song@linux.dev>,
Song Liu <song@kernel.org>, Jiri Olsa <jolsa@kernel.org>,
Emil Tsalapatis <emil@etsalapatis.com>,
Ihor Solodrai <ihor.solodrai@linux.dev>
Subject: Re: [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace
Date: Thu, 27 Aug 2026 15:40:57 +0800 [thread overview]
Message-ID: <05446fdb-2e94-47b0-91fb-b4f84b6baaa3@huawei.com> (raw)
In-Reply-To: <8ddc39b4-a28c-fffb-8b90-b4d60e0adaec@huaweicloud.com>
On 2026/8/26 20:28, Hou Tao wrote:
> Hi,
>
> On 8/26/2026 6:36 PM, Pu Lehui wrote:
>> From: Pu Lehui <pulehui@huawei.com>
>>
>> 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
>>
>> CPU: 2 PID: 5112 Comm: syz.4.470 Not tainted 6.6.0+ #1
>> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996)
>> Call Trace:
>> <IRQ>
>> ...
>> kasan_report+0xab/0xe0 mm/kasan/report.c:581
>> 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:
>> ...
>> __kmem_cache_free+0x15d/0x330 mm/slub.c:3885
>> free_one kernel/bpf/memalloc.c:262 [inline]
>> free_all kernel/bpf/memalloc.c:271 [inline]
>> __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
>>
>> Initially, we suspected that alloc_bulk() lacked RCU Tasks Trace
>> protection when accessing waiting_for_gp_ttrace. However, explicitly
>> adding rcu_read_lock_trace() did not help.
>>
>> This is expected because, as noted in commit 57b23c0f612d("bpf: Retire
>> rcu_trace_implies_rcu_gp()"), an RCU Tasks Trace GP currently implies
>> (and will continue to imply in the future) a normal RCU GP. Since
>> alloc_bulk() runs in an IRQ context (serving as an implicit normal RCU
>> read-side critical section), an RCU Tasks Trace GP cannot complete while
>> alloc_bulk() is accessing the list. Thus, the callback __free_rcu cannot
>> run concurrently, ruling out missing RCU read-side locks as the cause.
>>
>> Further investigation revealed that the UAF does not occur before the
>> RCU Tasks Trace grace period expires, but rather during the execution of
>> its callback. When the callback invokes llist_del_all to reclaim
>> waiting_for_gp_ttrace nodes, there is no synchronization protecting
>> against concurrent alloc_bulk() calls. If alloc_bulk() operates on
>> waiting_for_gp_ttrace simultaneously, a race condition ensues, as
>> illustrated below:
>>
>> CPU0 CPU1
>> __free_rcu (RCU Tasks Trace callback)
>> alloc_bulk (irq context)
>> 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));
>>
>> Since alloc_bulk() operates on waiting_for_gp_ttrace under irq context,
>> fix the issue by deferring the node reclamation. In __free_rcu callback,
>> detach the waiting_for_gp_ttrace nodes to a local list pointer and invoke
>> a normal RCU callback to free them.
>>
>> Fixes: 04fabf00b4d3 ("bpf: Allow reuse from waiting_for_gp_ttrace list.")
>> Signed-off-by: Pu Lehui <pulehui@huawei.com>
>> ---
>> Another potential fix would be to invoke llist_del_all() on
>> waiting_for_gp_ttrace before call_rcu_tasks_trace(), but that would
>> defeat the purpose of reusing waiting_for_gp_ttrace in alloc_bulk().
>>
>> kernel/bpf/memalloc.c | 22 ++++++++++++++++++++--
>> 1 file changed, 20 insertions(+), 2 deletions(-)
>>
>> diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c
>> index e9662db7198f..fb1e733bfb82 100644
>> --- a/kernel/bpf/memalloc.c
>> +++ b/kernel/bpf/memalloc.c
>> @@ -110,7 +110,9 @@ struct bpf_mem_cache {
>> struct llist_node *free_by_rcu_tail;
>> struct llist_head waiting_for_gp;
>> struct llist_node *waiting_for_gp_tail;
>> + struct llist_node *waiting_for_reclaim_gp;
>> struct rcu_head rcu;
>> + struct rcu_head rcu_reclaim;
>> atomic_t call_rcu_in_progress;
>> struct llist_head free_llist_extra_rcu;
>>
>> @@ -276,12 +278,28 @@ static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool per
>> return cnt;
>> }
>>
>> +static void __free_final_rcu(struct rcu_head *head)
>> +{
>> + struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu);
>> + struct llist_node *llnode = c->waiting_for_reclaim_gp;
>> +
>> + c->waiting_for_reclaim_gp = NULL;
>> + free_all(c, llnode, !!c->percpu_size);
>> + atomic_set(&c->call_rcu_ttrace_in_progress, 0);
>> +}
>> +
>> static void __free_rcu(struct rcu_head *head)
>> {
>> struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu_ttrace);
>> + struct llist_node *llnode = llist_del_all(&c->waiting_for_gp_ttrace);
>>
>> - free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size);
>> - atomic_set(&c->call_rcu_ttrace_in_progress, 0);
>> + if (!llnode) {
>> + atomic_set(&c->call_rcu_ttrace_in_progress, 0);
>> + return;
>> + }
>> +
>> + c->waiting_for_reclaim_gp = llnode;
>> + call_rcu(&c->rcu_reclaim, __free_final_rcu);
>> }
>
> It will extra delay for the freeing of these memory objects. I think
> using a raw spinlock to protect the concurrentl llist_del_all() and
> llist_del_first() will be simpler. Alexei had written a patch for it
> before [0].
>
> [0]:
> https://lore.kernel.org/bpf/CAADnVQKea47Q1WPtmVrHEZijb=Ms8QzufVj8eds5HmNXGxSRug@mail.gmail.com/#t
>
> However in my understanding, free_by_rcu_ttrace doesn't have such
> problem. The only possible way when there is concurrent llist_del_all()
> and llist_del_first() is during bpf_mem_alloc_destroy().
> bpf_mem_alloc_destroy() will set draining as true and do_call_rcu_ttrace
> will invoke free_all in advance. But right then, the caller of
> bpf_mem_alloc_destroy() will ensure there is no active allocation and
> there will be no invocation of llist_del_first().
Hi Hou Tao,
Thanks for the review! Using a raw spinlock to protect
waiting_for_gp_ttrace is indeed much simpler and cleaner, avoiding both
extra freeing latency and teardown barrier issues.
I will send v2 with this approach shortly.
>>
>> static void enque_to_free(struct bpf_mem_cache *c, void *obj)
>
next prev parent reply other threads:[~2026-08-27 7:41 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-26 10:36 Pu Lehui
2026-08-26 11:16 ` bot+bpf-ci
2026-08-27 7:43 ` Pu Lehui
2026-08-26 12:28 ` Hou Tao
2026-08-27 7:40 ` Pu Lehui [this message]
2026-08-26 16:13 ` [syzbot ci] " syzbot ci
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=05446fdb-2e94-47b0-91fb-b4f84b6baaa3@huawei.com \
--to=pulehui@huawei.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=houtao@huaweicloud.com \
--cc=ihor.solodrai@linux.dev \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=pulehui@huaweicloud.com \
--cc=song@kernel.org \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®