BPF List
 help / color / mirror / Atom feed
* [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace
@ 2026-08-26 10:36 Pu Lehui
  2026-08-26 10:40 ` sashiko-bot
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Pu Lehui @ 2026-08-26 10:36 UTC (permalink / raw)
  To: bpf, linux-kernel
  Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis,
	Ihor Solodrai, Pu Lehui, Pu Lehui

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);
 }
 
 static void enque_to_free(struct bpf_mem_cache *c, void *obj)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace
  2026-08-26 10:36 [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace Pu Lehui
@ 2026-08-26 10:40 ` sashiko-bot
  2026-08-26 11:16 ` bot+bpf-ci
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-08-26 10:40 UTC (permalink / raw)
  To: Pu Lehui; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Incorrect struct member used in container_of() in __free_final_rcu() leads to a misaligned pointer and severe memory corruption.
--

commit bc9dc5f25db638cea0ee9ae53e66c94250192d0e
Author: Pu Lehui <pulehui@huawei.com>

bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace

This commit addresses a use-after-free in alloc_bulk() by deferring the node
reclamation of waiting_for_gp_ttrace. It detaches the nodes in the __free_rcu()
callback and queues them for final reclamation via a new normal RCU callback.

> diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c
> index e9662db7198fe..fb1e733bfb829 100644
> --- a/kernel/bpf/memalloc.c
> +++ b/kernel/bpf/memalloc.c
[ ... ]
> @@ -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);

[Severity: Critical]
Does this calculate the correct base pointer? 

Since the callback is queued using the rcu_reclaim field in __free_rcu(),
computing the container using the rcu field will result in a misaligned pointer.

Because rcu_reclaim is located after rcu in struct bpf_mem_cache, the offset
subtraction leaves c pointing past the actual object's start address. When
this corrupted pointer is passed to free_all(), could it cause memory
corruption or a crash by reading an invalid function pointer for c->dtor?

> +	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);

[Severity: Critical]
Should __free_final_rcu() use container_of(head, struct bpf_mem_cache,
rcu_reclaim) to match this call_rcu() usage?

>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260826103615.932094-1-pulehui@huaweicloud.com?part=1

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace
  2026-08-26 10:36 [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace Pu Lehui
  2026-08-26 10:40 ` sashiko-bot
@ 2026-08-26 11:16 ` bot+bpf-ci
  2026-08-27  7:43   ` Pu Lehui
  2026-08-26 12:28 ` Hou Tao
  2026-08-26 16:13 ` [syzbot ci] " syzbot ci
  3 siblings, 1 reply; 7+ messages in thread
From: bot+bpf-ci @ 2026-08-26 11:16 UTC (permalink / raw)
  To: pulehui, bpf, linux-kernel
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, yonghong.song,
	song, jolsa, emil, ihor.solodrai, pulehui, pulehui, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 7859 bytes --]

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

Is this accurate on CONFIG_PREEMPT_RT? Looking at init_refill_work() in
kernel/bpf/memalloc.c, it uses init_irq_work() without IRQ_WORK_INIT_HARD,
so on RT the irq_work goes to lazy_list and is executed by the preemptible
per-CPU 'irq_work/N' kthread, not in hardirq context. The allocator's own
comment says "In RT irq_work runs in per-cpu kthread."

The conclusion still holds because run_irq_workd() wraps
irq_work_run_list(&lazy_list) in guard(rcu)(), making it an explicit RCU
read-side critical section on RT. So alloc_bulk() is protected by RCU, but
the stated reason ('IRQ context') is only true on !RT.

Could the commit message clarify that the RCU-reader property comes from
hardirq on !RT and explicit rcu_read_lock() in run_irq_workd() on RT?

>     Thus, the callback __free_rcu cannot run concurrently, ruling out
>     missing RCU read-side locks as the cause.

Can __free_rcu really not run concurrently with alloc_bulk()? The race
diagram two paragraphs later shows exactly that happening:

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);
                                               free_all(llist_del_all(&c->waiting_for_gp_ttrace))
    next = READ_ONCE(entry->next); <-- trigger UAF

Looking at the guarantees from 57b23c0f612d, the RCU Tasks Trace GP
contains at least one synchronize_rcu(), which waits for alloc_bulk()
invocations that started before that internal synchronize_rcu(). But a
fresh alloc_bulk() entered after the GP expired is not waited for, and
__free_rcu can run while that fresh invocation is in flight.

So what's actually true is narrower: a ttrace GP cannot expire while an
alloc_bulk() read section that began before the GP's internal
synchronize_rcu() is still in flight. A fresh alloc_bulk() after the GP
expired can race with __free_rcu().

Could the explanation be more precise about which alloc_bulk() invocations
are protected and which can race?

> 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);
                          ^^^^^^^^^^^^^^^^^^^^^^^^^

Does free_mem_alloc() wait for this new callback? Looking at the teardown
path:

kernel/bpf/memalloc.c:free_mem_alloc() {
    /*
     * Trace grace period implies RCU grace period, so all __free_rcu
     * don't need extra call_rcu() (and thus extra rcu_barrier() here).
     */
    rcu_barrier();               /* wait for __free_by_rcu */
    rcu_barrier_tasks_trace();   /* wait for __free_rcu */
    free_mem_alloc_no_barrier(ma);
        -> free_percpu(ma->cache) / free_percpu(ma->caches)
}

The barriers run in that order: normal RCU first, then tasks-trace. If
__free_rcu() executes during rcu_barrier_tasks_trace() and queues
call_rcu(&c->rcu_reclaim, __free_final_rcu), there's no rcu_barrier()
left to wait for it. So free_percpu() runs while __free_final_rcu() is
still queued on the now-freed per-cpu memory.

Concrete sequence:
1. do_call_rcu_ttrace() queues call_rcu_tasks_trace(&c->rcu_ttrace,
   __free_rcu)
2. Map destroyed, destroy path computes rcu_in_progress >= 1 from
   atomic_read(&c->call_rcu_ttrace_in_progress), takes deferred path
3. free_mem_alloc(): rcu_barrier() at 716 completes, rcu_barrier_tasks_trace()
   at 717 waits for __free_rcu() which runs and queues call_rcu(&c->rcu_reclaim,
   __free_final_rcu), barrier returns with normal-RCU callback newly queued
4. free_mem_alloc_no_barrier() frees the per-cpu memory holding c
5. When normal grace period ends, __free_final_rcu() runs on freed memory

Also, drain_mem_cache() drains seven lists but not waiting_for_reclaim_gp,
and check_mem_cache() WARN_ON_ONCE()s on those seven but never inspects
waiting_for_reclaim_gp, so the leak is silent.

Does this need an rcu_barrier() after rcu_barrier_tasks_trace() (or the
two swapped plus an added one)? And should the comment at lines 706-715
be updated since it now says the opposite of what the code does?

Also note check_mem_cache() should probably check waiting_for_reclaim_gp.

The same llist_del_first-vs-llist_del_all hazard that this patch fixes
for waiting_for_gp_ttrace also exists on free_by_rcu_ttrace. Looking at
kernel/bpf/memalloc.c:

alloc_bulk() does:
    obj = llist_del_first(&c->free_by_rcu_ttrace);

while do_call_rcu_ttrace() does:
    llnode = llist_del_all(&c->free_by_rcu_ttrace);
    ...
    free_all(c, llnode, percpu);  /* draining path, immediate free */

and drain_mem_cache() does:
    free_all(llist_del_all(&c->free_by_rcu_ttrace), ...);

include/linux/llist.h's concurrency table marks the del_first x del_all
cell 'L' (lock is needed) and the kerneldoc on llist_del_first says "If
multiple consumers are needed, please use llist_del_all or use lock
between consumers."

The non-draining path in do_call_rcu_ttrace() is incidentally covered by
the new extra GP because nodes end up in waiting_for_gp_ttrace, but the
draining direct-free and drain_mem_cache()'s free_all() still free without
any RCU deferral.

Does free_by_rcu_ttrace need the same treatment?

While the nodes sit in c->waiting_for_reclaim_gp they are invisible to
alloc_bulk()'s reuse loop, so for one extra normal RCU GP the allocator
must kmalloc() instead of reusing. This partially undoes the optimization
being fixed.

Is this expected? Should the commit message mention it?

The commit message says "Syzkaller repeatedly triggered" but carries no
Reported-by:, Closes: or Link: tag, and the splat is from a private 6.6.0+
tree rather than upstream syzbot.

Also, Hou Tao reported this exact hazard during review of 04fabf00b4d3 in
July 2023 (lore.kernel.org/rcu/957dd5cd-0855-1197-7045-4cb1590bd753@huaweicloud.com).
Alexei's reply was that the probability was "lower than a bit flip due to
cosmic rays."

Could citing that thread make the analysis more credible and give proper
credit to Hou Tao?


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

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace
  2026-08-26 10:36 [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace Pu Lehui
  2026-08-26 10:40 ` sashiko-bot
  2026-08-26 11:16 ` bot+bpf-ci
@ 2026-08-26 12:28 ` Hou Tao
  2026-08-27  7:40   ` Pu Lehui
  2026-08-26 16:13 ` [syzbot ci] " syzbot ci
  3 siblings, 1 reply; 7+ messages in thread
From: Hou Tao @ 2026-08-26 12:28 UTC (permalink / raw)
  To: Pu Lehui, bpf, linux-kernel
  Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis,
	Ihor Solodrai, Pu Lehui

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().
>  
>  static void enque_to_free(struct bpf_mem_cache *c, void *obj)


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [syzbot ci] Re: bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace
  2026-08-26 10:36 [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace Pu Lehui
                   ` (2 preceding siblings ...)
  2026-08-26 12:28 ` Hou Tao
@ 2026-08-26 16:13 ` syzbot ci
  3 siblings, 0 replies; 7+ messages in thread
From: syzbot ci @ 2026-08-26 16:13 UTC (permalink / raw)
  To: andrii, ast, bpf, daniel, eddyz87, emil, ihor.solodrai, jolsa,
	linux-kernel, martin.lau, memxor, pulehui, pulehui, song,
	yonghong.song
  Cc: syzbot, syzkaller-bugs

syzbot ci has tested the following series

[v1] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace
https://lore.kernel.org/all/20260826103615.932094-1-pulehui@huaweicloud.com
* [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace

and found the following issues:
* BUG: unable to handle kernel NULL pointer dereference in rcu_core
* BUG: unable to handle kernel paging request in corrupted
* WARNING in mntput_no_expire_slowpath
* kernel BUG in free_percpu

Full report is available here:
https://ci.syzbot.org/series/cfb01d47-3cc7-43a6-99e6-c4a0c6e6ce89

***

BUG: unable to handle kernel NULL pointer dereference in rcu_core

tree:      bpf
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf.git
base:      75b0a6db4300e4c2c9e97a0848deaa7acfb42fb7
arch:      amd64
compiler:  Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8
config:    https://ci.syzbot.org/builds/187f4142-010e-469f-b2d4-8ce60c1dd2c5/config
syz repro: https://ci.syzbot.org/findings/5ac25989-0260-4506-a08a-25fa6e4957e7/syz_repro

BUG: kernel NULL pointer dereference, address: 0000000000000000
#PF: supervisor instruction fetch in kernel mode
#PF: error_code(0x0010) - not-present page
PGD 800000016a651067 P4D 800000016a651067 PUD 0 
Oops: Oops: 0010 [#1] SMP KASAN PTI
CPU: 1 UID: 0 PID: 5776 Comm: syz.0.18 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:0x0
Code: Unable to access opcode bytes at 0xffffffffffffffd6.
RSP: 0018:ffffc90000a08db8 EFLAGS: 00010246
RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000000028
RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffffe8ffffc2c5d0
RBP: ffffc90000a08f20 R08: ffffffff81ad5526 R09: 0000000000000000
R10: ffffc90000a08c70 R11: 0000000000000000 R12: ffffe8ffffc2c5d0
R13: dffffc0000000000 R14: ffffffff81ad5526 R15: ffffe8ffffc2c5d8
FS:  00005555813d0500(0000) GS:ffff8882a8f2d000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffffffffffffffd6 CR3: 0000000105338000 CR4: 00000000000006f0
Call Trace:
 <IRQ>
 rcu_do_batch kernel/rcu/tree.c:2645 [inline]
 rcu_core+0x794/0x10b0 kernel/rcu/tree.c:2897
 handle_softirqs+0x226/0x860 kernel/softirq.c:645
 __do_softirq kernel/softirq.c:679 [inline]
 invoke_softirq kernel/softirq.c:519 [inline]
 __irq_exit_rcu+0xcb/0x220 kernel/softirq.c:767
 irq_exit_rcu+0x9/0x30 kernel/softirq.c:784
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
 sysvec_apic_timer_interrupt+0xa6/0xc0 arch/x86/kernel/apic/apic.c:1062
 </IRQ>
 <TASK>
 asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:lock_release+0x2d7/0x3c0 kernel/locking/lockdep.c:5911
Code: a2 cc 11 00 00 00 00 eb b5 e8 55 12 34 0a f7 c3 00 02 00 00 74 b9 65 48 8b 05 ad 5d cc 11 48 3b 44 24 28 75 44 fb 48 83 c4 30 <5b> 41 5c 41 5d 41 5e 41 5f 5d e9 8a 17 37 0a cc 48 8d 3d b2 b3 b8
RSP: 0018:ffffc900039dfa68 EFLAGS: 00000282
RAX: 4b32b4bcbbad6400 RBX: 0000000000000287 RCX: 0000000000000046
RDX: 0000000000000000 RSI: ffffffff8e4d8901 RDI: ffffffff8c4cd780
RBP: ffff88810b5d29a8 R08: ffffffff821d972f R09: 0000000000000000
R10: 0000000000000000 R11: ffff8881162fe9b8 R12: 0000000000000000
R13: 0000000000000000 R14: ffff8881162fe9b8 R15: ffff88810b5d1e00
 _inline_copy_from_user include/linux/uaccess.h:169 [inline]
 _copy_from_user+0x28/0xb0 lib/usercopy.c:18
 copy_from_user include/linux/uaccess.h:222 [inline]
 generic_map_update_batch+0x668/0xae0 kernel/bpf/syscall.c:2101
 bpf_map_do_batch+0x391/0x630 kernel/bpf/syscall.c:5781
 __sys_bpf+0xbe5/0xd90 kernel/bpf/syscall.c:-1
 __do_sys_bpf kernel/bpf/syscall.c:6486 [inline]
 __se_sys_bpf kernel/bpf/syscall.c:6483 [inline]
 __x64_sys_bpf+0xba/0xd0 kernel/bpf/syscall.c:6483
 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
 do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7febdab9e0d9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffc5722f5a8 EFLAGS: 00000246 ORIG_RAX: 0000000000000141
RAX: ffffffffffffffda RBX: 00007febdae25fa0 RCX: 00007febdab9e0d9
RDX: 0000000000000038 RSI: 00002000000003c0 RDI: 000000000000001a
RBP: 00007febdac35024 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007febdae25fac R14: 00007febdae25fa0 R15: 00007febdae25fa0
 </TASK>
Modules linked in:
CR2: 0000000000000000
---[ end trace 0000000000000000 ]---
RIP: 0010:0x0
Code: Unable to access opcode bytes at 0xffffffffffffffd6.
RSP: 0018:ffffc90000a08db8 EFLAGS: 00010246

RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000000028
RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffffe8ffffc2c5d0
RBP: ffffc90000a08f20 R08: ffffffff81ad5526 R09: 0000000000000000
R10: ffffc90000a08c70 R11: 0000000000000000 R12: ffffe8ffffc2c5d0
R13: dffffc0000000000 R14: ffffffff81ad5526 R15: ffffe8ffffc2c5d8
FS:  00005555813d0500(0000) GS:ffff8882a8f2d000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffffffffffffffd6 CR3: 0000000105338000 CR4: 00000000000006f0
----------------
Code disassembly (best guess):
   0:	a2 cc 11 00 00 00 00 	movabs %al,0xb5eb0000000011cc
   7:	eb b5
   9:	e8 55 12 34 0a       	call   0xa341263
   e:	f7 c3 00 02 00 00    	test   $0x200,%ebx
  14:	74 b9                	je     0xffffffcf
  16:	65 48 8b 05 ad 5d cc 	mov    %gs:0x11cc5dad(%rip),%rax        # 0x11cc5dcb
  1d:	11
  1e:	48 3b 44 24 28       	cmp    0x28(%rsp),%rax
  23:	75 44                	jne    0x69
  25:	fb                   	sti
  26:	48 83 c4 30          	add    $0x30,%rsp
* 2a:	5b                   	pop    %rbx <-- trapping instruction
  2b:	41 5c                	pop    %r12
  2d:	41 5d                	pop    %r13
  2f:	41 5e                	pop    %r14
  31:	41 5f                	pop    %r15
  33:	5d                   	pop    %rbp
  34:	e9 8a 17 37 0a       	jmp    0xa3717c3
  39:	cc                   	int3
  3a:	48                   	rex.W
  3b:	8d                   	.byte 0x8d
  3c:	3d                   	.byte 0x3d
  3d:	b2 b3                	mov    $0xb3,%dl
  3f:	b8                   	.byte 0xb8


***

BUG: unable to handle kernel paging request in corrupted

tree:      bpf
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf.git
base:      75b0a6db4300e4c2c9e97a0848deaa7acfb42fb7
arch:      amd64
compiler:  Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8
config:    https://ci.syzbot.org/builds/187f4142-010e-469f-b2d4-8ce60c1dd2c5/config
syz repro: https://ci.syzbot.org/findings/d4bc6f3a-dacb-412b-ac9d-51a5c1dbc4a4/syz_repro

kernel tried to execute NX-protected page - exploit attempt? (uid: 0)
BUG: unable to handle page fault for address: ffff888173315c00
#PF: supervisor instruction fetch in kernel mode
#PF: error_code(0x0011) - permissions violation
PGD 1acd0067 P4D 1acd0067 PUD 1acd5067 PMD 80000001732000e3 
Oops: Oops: 0011 [#1] SMP KASAN PTI
CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:0xffff888173315c00
Code: ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 <80> 5b 31 73 81 88 ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00
RSP: 0018:ffffc90000007d60 EFLAGS: 00010246
RAX: 1ffffd1ffff8f783 RBX: ffffe8ffffc7bc18 RCX: ffffffff8e88ef80
RDX: 0000000000000100 RSI: ffff888174548380 RDI: ffffffff81f86ab8
RBP: ffffe8ffffc7bc10 R08: 0000000000000100 R09: 0000000000000000
R10: 0000000000000000 R11: ffff888173315c00 R12: e5894855fa1e0ff3
R13: ffff888173315c00 R14: ffffffff81f86ab0 R15: ffffe8ffffc7bc18
FS:  0000000000000000(0000) GS:ffff88818d92d000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffff888173315c00 CR3: 000000010b9a2000 CR4: 00000000000006f0
Call Trace:
 <IRQ>
 </IRQ>
 <TASK>
 </TASK>
Modules linked in:
CR2: ffff888173315c00
---[ end trace 0000000000000000 ]---
RIP: 0010:0xffff888173315c00
Code: ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 <80> 5b 31 73 81 88 ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00
RSP: 0018:ffffc90000007d60 EFLAGS: 00010246
RAX: 1ffffd1ffff8f783 RBX: ffffe8ffffc7bc18 RCX: ffffffff8e88ef80
RDX: 0000000000000100 RSI: ffff888174548380 RDI: ffffffff81f86ab8
RBP: ffffe8ffffc7bc10 R08: 0000000000000100 R09: 0000000000000000
R10: 0000000000000000 R11: ffff888173315c00 R12: e5894855fa1e0ff3
R13: ffff888173315c00 R14: ffffffff81f86ab0 R15: ffffe8ffffc7bc18
FS:  0000000000000000(0000) GS:ffff88818d92d000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffff888173315c00 CR3: 000000010b9a2000 CR4: 00000000000006f0
----------------
Code disassembly (best guess), 2 bytes skipped:
* 28:	80 5b 31 73          	sbbb   $0x73,0x31(%rbx) <-- trapping instruction
  2c:	81 88 ff ff 00 00 00 	orl    $0x0,0xffff(%rax)
  33:	00 00 00


***

WARNING in mntput_no_expire_slowpath

tree:      bpf
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf.git
base:      75b0a6db4300e4c2c9e97a0848deaa7acfb42fb7
arch:      amd64
compiler:  Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8
config:    https://ci.syzbot.org/builds/187f4142-010e-469f-b2d4-8ce60c1dd2c5/config
syz repro: https://ci.syzbot.org/findings/f322b4d1-7f66-454a-80ff-6ed13eb274aa/syz_repro

------------[ cut here ]------------
count < 0
WARNING: fs/namespace.c:1353 at mntput_no_expire_slowpath+0x845/0xbf0 fs/namespace.c:1353, CPU#1: syz.0.20/5861
Modules linked in:
CPU: 1 UID: 0 PID: 5861 Comm: syz.0.20 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:mntput_no_expire_slowpath+0x845/0xbf0 fs/namespace.c:1353
Code: 01 00 00 00 bf 08 00 00 00 48 c7 c2 c0 48 ce 8e e8 a0 18 39 ff e9 47 fc ff ff e8 36 4e 72 ff e9 4a fa ff ff e8 2c 4e 72 ff 90 <0f> 0b 90 e9 68 fa ff ff e8 1e 4e 72 ff e8 69 83 fe ff e8 64 f7 ff
RSP: 0018:ffffc90003cdfba0 EFLAGS: 00010293
RAX: ffffffff8254bf34 RBX: dffffc0000000000 RCX: ffff888113571e00
RDX: 0000000000000000 RSI: 00000000ffffffff RDI: 0000000000000000
RBP: ffffc90003cdfc88 R08: ffffffff8254cacc R09: 0000000000000000
R10: 0000000000000000 R11: ffffffff8e814988 R12: ffff8881016c3600
R13: 0000000000000003 R14: 00000000ffffffff R15: 0000000000000002
FS:  00007fa12e5946c0(0000) GS:ffff8882a8f2d000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000555579145a28 CR3: 0000000176282000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 mntput fs/namespace.c:1421 [inline]
 cleanup_mnt+0x30c/0x460 fs/namespace.c:1313
 task_work_run+0x1d9/0x270 kernel/task_work.c:233
 exit_task_work include/linux/task_work.h:40 [inline]
 do_exit+0x73a/0x2360 kernel/exit.c:1009
 __do_sys_exit kernel/exit.c:1119 [inline]
 __se_sys_exit kernel/exit.c:1117 [inline]
 __x64_sys_exit+0x40/0x40 kernel/exit.c:1117
 x64_sys_call+0x18b4/0x18d0 arch/x86/include/generated/asm/syscalls_64.h:61
 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
 do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fa12d79e0d9
Code: Unable to access opcode bytes at 0x7fa12d79e0af.
RSP: 002b:00007fa12e593fd8 EFLAGS: 00000246 ORIG_RAX: 000000000000003c
RAX: ffffffffffffffda RBX: 00007fa12da25fa0 RCX: 00007fa12d79e0d9
RDX: 00007fa12e5949c8 RSI: 0000000000000000 RDI: 0000000000000000
RBP: 00007fa12d835024 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fa12da26038 R14: 00007fa12da25fa0 R15: 00007ffe0f572ca8
 </TASK>


***

kernel BUG in free_percpu

tree:      bpf
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf.git
base:      75b0a6db4300e4c2c9e97a0848deaa7acfb42fb7
arch:      amd64
compiler:  Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8
config:    https://ci.syzbot.org/builds/187f4142-010e-469f-b2d4-8ce60c1dd2c5/config
syz repro: https://ci.syzbot.org/findings/40ff44c7-989e-4e59-a57c-7d669c0abd76/syz_repro

------------[ cut here ]------------
kernel BUG at mm/vmalloc.c:835!
Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
CPU: 0 UID: 0 PID: 3326 Comm: kworker/u9:4 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Workqueue: events_unbound bpf_map_free_deferred
RIP: 0010:vmalloc_to_page+0x67c/0x6b0 mm/vmalloc.c:835
Code: 51 fb ff ff e8 b5 a8 a2 ff 42 80 7c 2d 00 00 0f 85 5c fb ff ff e9 5f fb ff ff e8 9f a8 a2 ff e9 ae fb ff ff e8 95 a8 a2 ff 90 <0f> 0b e8 8d a8 a2 ff e9 64 fa ff ff e8 83 a8 a2 ff 48 b8 00 00 00
RSP: 0018:ffffc90000007cc0 EFLAGS: 00010246
RAX: ffffffff822464cb RBX: 5440ddc2e3d42741 RCX: ffff888110ad5a00
RDX: 0000000000000100 RSI: 0000000000000100 RDI: ffffc90000000000
RBP: ffffe8fee467b538 R08: 0000000000000100 R09: 0000000000000000
R10: 0000000000000000 R11: ffffffff81f86890 R12: 0000000000a881bb
R13: dffffc0000000000 R14: ffffc90000000000 R15: ffffffff8e946000
FS:  0000000000000000(0000) GS:ffff88818d92d000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000100000000 CR3: 000000001c4e4000 CR4: 00000000000006f0
Call Trace:
 <IRQ>
 pcpu_addr_to_page mm/percpu-vm.c:373 [inline]
 pcpu_chunk_addr_search mm/percpu.c:1612 [inline]
 free_percpu+0x1fa/0xfe0 mm/percpu.c:2246
 free_one kernel/bpf/memalloc.c:262 [inline]
 free_all kernel/bpf/memalloc.c:275 [inline]
 __free_final_rcu+0x151/0x210 kernel/bpf/memalloc.c:287
 rcu_do_batch kernel/rcu/tree.c:2645 [inline]
 rcu_core+0x794/0x10b0 kernel/rcu/tree.c:2897
 handle_softirqs+0x226/0x860 kernel/softirq.c:645
 __do_softirq kernel/softirq.c:679 [inline]
 invoke_softirq kernel/softirq.c:519 [inline]
 __irq_exit_rcu+0xcb/0x220 kernel/softirq.c:767
 irq_exit_rcu+0x9/0x30 kernel/softirq.c:784
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
 sysvec_apic_timer_interrupt+0xa6/0xc0 arch/x86/kernel/apic/apic.c:1062
 </IRQ>
 <TASK>
 asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:lock_acquire+0x222/0x350 kernel/locking/lockdep.c:5890
Code: ff ff ff e8 f0 43 34 0a f7 44 24 08 00 02 00 00 0f 84 39 ff ff ff 65 48 8b 05 42 8f cc 11 48 3b 44 24 58 75 33 fb 48 83 c4 60 <5b> 41 5c 41 5d 41 5e 41 5f 5d c3 cc cc cc cc cc 48 8d 3d d7 e1 b8
RSP: 0018:ffffc90006dc77e8 EFLAGS: 00000286
RAX: b973d41dfa964800 RBX: 0000000000000000 RCX: 8000000000000001
RDX: 00000000fa315d19 RSI: ffffffff8e4d8901 RDI: ffffffff8c4cd780
RBP: ffffffff823e19a8 R08: ffffffff823e19a8 R09: 0000000000000000
R10: 0000000000000000 R11: ffffffff8eb5a1e0 R12: 0000000000000002
R13: ffffffff8eb5a1e0 R14: 0000000000000000 R15: 0000000000000246
 rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 rcu_read_lock include/linux/rcupdate.h:840 [inline]
 get_non_dying_memcg_start mm/memcontrol.c:817 [inline]
 mod_memcg_state+0x64/0x190 mm/memcontrol.c:879
 account_kmem_nmi_safe mm/memcontrol.c:3057 [inline]
 obj_cgroup_uncharge_pages mm/memcontrol.c:3073 [inline]
 __refill_obj_stock+0x559/0x6c0 mm/memcontrol.c:3459
 __memcg_slab_free_hook+0x2bc/0x470 mm/memcontrol.c:3649
 memcg_slab_free_hook mm/slub.c:2475 [inline]
 slab_free mm/slub.c:6374 [inline]
 kfree+0x3d6/0x640 mm/slub.c:6692
 free_one kernel/bpf/memalloc.c:264 [inline]
 free_all kernel/bpf/memalloc.c:275 [inline]
 drain_mem_cache+0x643/0x8e0 kernel/bpf/memalloc.c:653
 bpf_mem_alloc_destroy+0x142/0x550 kernel/bpf/memalloc.c:766
 trie_free+0x134/0x160 kernel/bpf/lpm_trie.c:652
 bpf_map_free+0x19d/0x3f0 kernel/bpf/syscall.c:896
 process_one_work kernel/workqueue.c:3322 [inline]
 process_scheduled_works+0xa88/0x14c0 kernel/workqueue.c:3405
 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
 kthread+0x38b/0x480 kernel/kthread.c:436
 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:vmalloc_to_page+0x67c/0x6b0 mm/vmalloc.c:835
Code: 51 fb ff ff e8 b5 a8 a2 ff 42 80 7c 2d 00 00 0f 85 5c fb ff ff e9 5f fb ff ff e8 9f a8 a2 ff e9 ae fb ff ff e8 95 a8 a2 ff 90 <0f> 0b e8 8d a8 a2 ff e9 64 fa ff ff e8 83 a8 a2 ff 48 b8 00 00 00
RSP: 0018:ffffc90000007cc0 EFLAGS: 00010246
RAX: ffffffff822464cb RBX: 5440ddc2e3d42741 RCX: ffff888110ad5a00
RDX: 0000000000000100 RSI: 0000000000000100 RDI: ffffc90000000000
RBP: ffffe8fee467b538 R08: 0000000000000100 R09: 0000000000000000
R10: 0000000000000000 R11: ffffffff81f86890 R12: 0000000000a881bb
R13: dffffc0000000000 R14: ffffc90000000000 R15: ffffffff8e946000
FS:  0000000000000000(0000) GS:ffff88818d92d000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000100000000 CR3: 000000000e946000 CR4: 00000000000006f0
----------------
Code disassembly (best guess), 3 bytes skipped:
   0:	e8 f0 43 34 0a       	call   0xa3443f5
   5:	f7 44 24 08 00 02 00 	testl  $0x200,0x8(%rsp)
   c:	00
   d:	0f 84 39 ff ff ff    	je     0xffffff4c
  13:	65 48 8b 05 42 8f cc 	mov    %gs:0x11cc8f42(%rip),%rax        # 0x11cc8f5d
  1a:	11
  1b:	48 3b 44 24 58       	cmp    0x58(%rsp),%rax
  20:	75 33                	jne    0x55
  22:	fb                   	sti
  23:	48 83 c4 60          	add    $0x60,%rsp
* 27:	5b                   	pop    %rbx <-- trapping instruction
  28:	41 5c                	pop    %r12
  2a:	41 5d                	pop    %r13
  2c:	41 5e                	pop    %r14
  2e:	41 5f                	pop    %r15
  30:	5d                   	pop    %rbp
  31:	c3                   	ret
  32:	cc                   	int3
  33:	cc                   	int3
  34:	cc                   	int3
  35:	cc                   	int3
  36:	cc                   	int3
  37:	48                   	rex.W
  38:	8d                   	.byte 0x8d
  39:	3d                   	.byte 0x3d
  3a:	d7                   	xlat   %ds:(%rbx)
  3b:	e1 b8                	loope  0xfffffff5


***

If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
  Tested-by: syzbot@syzkaller.appspotmail.com

---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.

To test a fix for this bug, please reply with `#syz test`
(on a separate line) and attach the patch to the email.

Notes:
- The patch will be applied on top of the tested series (as an
  incremental fix).
- To test a new version of the whole series, please send it directly
  to syzbot@lists.linux.dev.
- Arguments like custom git repos and branches are not supported.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace
  2026-08-26 12:28 ` Hou Tao
@ 2026-08-27  7:40   ` Pu Lehui
  0 siblings, 0 replies; 7+ messages in thread
From: Pu Lehui @ 2026-08-27  7:40 UTC (permalink / raw)
  To: Hou Tao, Pu Lehui, bpf, linux-kernel
  Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis,
	Ihor Solodrai



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

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace
  2026-08-26 11:16 ` bot+bpf-ci
@ 2026-08-27  7:43   ` Pu Lehui
  0 siblings, 0 replies; 7+ messages in thread
From: Pu Lehui @ 2026-08-27  7:43 UTC (permalink / raw)
  To: bot+bpf-ci, pulehui, bpf, linux-kernel
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, yonghong.song,
	song, jolsa, emil, ihor.solodrai, martin.lau, clm


Thanks very much!

The review comments on bpf ci look excellent, and I will extract the 
valuable information from them.

On 2026/8/26 19:16, bot+bpf-ci@kernel.org wrote:
>>      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.
> 
> Is this accurate on CONFIG_PREEMPT_RT? Looking at init_refill_work() in
> kernel/bpf/memalloc.c, it uses init_irq_work() without IRQ_WORK_INIT_HARD,
> so on RT the irq_work goes to lazy_list and is executed by the preemptible
> per-CPU 'irq_work/N' kthread, not in hardirq context. The allocator's own
> comment says "In RT irq_work runs in per-cpu kthread."
> 
> The conclusion still holds because run_irq_workd() wraps
> irq_work_run_list(&lazy_list) in guard(rcu)(), making it an explicit RCU
> read-side critical section on RT. So alloc_bulk() is protected by RCU, but
> the stated reason ('IRQ context') is only true on !RT.
> 
> Could the commit message clarify that the RCU-reader property comes from
> hardirq on !RT and explicit rcu_read_lock() in run_irq_workd() on RT?
> 
>>      Thus, the callback __free_rcu cannot run concurrently, ruling out
>>      missing RCU read-side locks as the cause.
> 
> Can __free_rcu really not run concurrently with alloc_bulk()? The race
> diagram two paragraphs later shows exactly that happening:
> 
> 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);
>                                                 free_all(llist_del_all(&c->waiting_for_gp_ttrace))
>      next = READ_ONCE(entry->next); <-- trigger UAF
> 
> Looking at the guarantees from 57b23c0f612d, the RCU Tasks Trace GP
> contains at least one synchronize_rcu(), which waits for alloc_bulk()
> invocations that started before that internal synchronize_rcu(). But a
> fresh alloc_bulk() entered after the GP expired is not waited for, and
> __free_rcu can run while that fresh invocation is in flight.
> 
> So what's actually true is narrower: a ttrace GP cannot expire while an
> alloc_bulk() read section that began before the GP's internal
> synchronize_rcu() is still in flight. A fresh alloc_bulk() after the GP
> expired can race with __free_rcu().
> 
> Could the explanation be more precise about which alloc_bulk() invocations
> are protected and which can race?
> 
>> 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);
>                            ^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> Does free_mem_alloc() wait for this new callback? Looking at the teardown
> path:
> 
> kernel/bpf/memalloc.c:free_mem_alloc() {
>      /*
>       * Trace grace period implies RCU grace period, so all __free_rcu
>       * don't need extra call_rcu() (and thus extra rcu_barrier() here).
>       */
>      rcu_barrier();               /* wait for __free_by_rcu */
>      rcu_barrier_tasks_trace();   /* wait for __free_rcu */
>      free_mem_alloc_no_barrier(ma);
>          -> free_percpu(ma->cache) / free_percpu(ma->caches)
> }
> 
> The barriers run in that order: normal RCU first, then tasks-trace. If
> __free_rcu() executes during rcu_barrier_tasks_trace() and queues
> call_rcu(&c->rcu_reclaim, __free_final_rcu), there's no rcu_barrier()
> left to wait for it. So free_percpu() runs while __free_final_rcu() is
> still queued on the now-freed per-cpu memory.
> 
> Concrete sequence:
> 1. do_call_rcu_ttrace() queues call_rcu_tasks_trace(&c->rcu_ttrace,
>     __free_rcu)
> 2. Map destroyed, destroy path computes rcu_in_progress >= 1 from
>     atomic_read(&c->call_rcu_ttrace_in_progress), takes deferred path
> 3. free_mem_alloc(): rcu_barrier() at 716 completes, rcu_barrier_tasks_trace()
>     at 717 waits for __free_rcu() which runs and queues call_rcu(&c->rcu_reclaim,
>     __free_final_rcu), barrier returns with normal-RCU callback newly queued
> 4. free_mem_alloc_no_barrier() frees the per-cpu memory holding c
> 5. When normal grace period ends, __free_final_rcu() runs on freed memory
> 
> Also, drain_mem_cache() drains seven lists but not waiting_for_reclaim_gp,
> and check_mem_cache() WARN_ON_ONCE()s on those seven but never inspects
> waiting_for_reclaim_gp, so the leak is silent.
> 
> Does this need an rcu_barrier() after rcu_barrier_tasks_trace() (or the
> two swapped plus an added one)? And should the comment at lines 706-715
> be updated since it now says the opposite of what the code does?
> 
> Also note check_mem_cache() should probably check waiting_for_reclaim_gp.
> 
> The same llist_del_first-vs-llist_del_all hazard that this patch fixes
> for waiting_for_gp_ttrace also exists on free_by_rcu_ttrace. Looking at
> kernel/bpf/memalloc.c:
> 
> alloc_bulk() does:
>      obj = llist_del_first(&c->free_by_rcu_ttrace);
> 
> while do_call_rcu_ttrace() does:
>      llnode = llist_del_all(&c->free_by_rcu_ttrace);
>      ...
>      free_all(c, llnode, percpu);  /* draining path, immediate free */
> 
> and drain_mem_cache() does:
>      free_all(llist_del_all(&c->free_by_rcu_ttrace), ...);
> 
> include/linux/llist.h's concurrency table marks the del_first x del_all
> cell 'L' (lock is needed) and the kerneldoc on llist_del_first says "If
> multiple consumers are needed, please use llist_del_all or use lock
> between consumers."
> 
> The non-draining path in do_call_rcu_ttrace() is incidentally covered by
> the new extra GP because nodes end up in waiting_for_gp_ttrace, but the
> draining direct-free and drain_mem_cache()'s free_all() still free without
> any RCU deferral.
> 
> Does free_by_rcu_ttrace need the same treatment?
> 
> While the nodes sit in c->waiting_for_reclaim_gp they are invisible to
> alloc_bulk()'s reuse loop, so for one extra normal RCU GP the allocator
> must kmalloc() instead of reusing. This partially undoes the optimization
> being fixed.
> 
> Is this expected? Should the commit message mention it?
> 
> The commit message says "Syzkaller repeatedly triggered" but carries no
> Reported-by:, Closes: or Link: tag, and the splat is from a private 6.6.0+
> tree rather than upstream syzbot.
> 
> Also, Hou Tao reported this exact hazard during review of 04fabf00b4d3 in
> July 2023 (lore.kernel.org/rcu/957dd5cd-0855-1197-7045-4cb1590bd753@huaweicloud.com).
> Alexei's reply was that the probability was "lower than a bit flip due to
> cosmic rays."
> 
> Could citing that thread make the analysis more credible and give proper
> credit to Hou Tao?
> 
> 
> ---
> 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/32959494022

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-08-27  7:43 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-26 10:36 [PATCH bpf] bpf: Fix UAF due to concurrent consumption of waiting_for_gp_ttrace Pu Lehui
2026-08-26 10:40 ` sashiko-bot
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
2026-08-26 16:13 ` [syzbot ci] " syzbot ci

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox