* [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
@ 2026-09-05 2:11 Pu Lehui
2026-09-05 3:00 ` bot+bpf-ci
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: Pu Lehui @ 2026-09-05 2:11 UTC (permalink / raw)
To: bpf, linux-kernel, Alexei Starovoitov, Hou Tao, Leon Hwang
Cc: 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
...
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@kernel.org>
Suggested-by: Hou Tao <houtao1@huawei.com>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
v4:
- Update comments for ttrace list in alloc_bulk. (BPF CI)
- Drop initial troubleshooting explaining in commit message.
v3: https://lore.kernel.org/bpf/20260901150307.3474349-1-pulehui@huaweicloud.com
- Fix concurrent issue also for free_by_rcu_ttrace. (bpfci and Hou Tao)
- Use scoped_guard. (Leon)
v2: https://lore.kernel.org/bpf/20260827084013.1062816-1-pulehui@huaweicloud.com
- Use raw spinlock to fix concurrent alloc_bulk and __free_rcu on
waiting_for_gp_ttrace after GP. (Hou Tao)
v1: https://lore.kernel.org/bpf/20260826103615.932094-1-pulehui@huaweicloud.com
kernel/bpf/memalloc.c | 50 ++++++++++++++++++++++++-------------------
1 file changed, 28 insertions(+), 22 deletions(-)
diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c
index e9662db7198f..8a8f088e83e6 100644
--- a/kernel/bpf/memalloc.c
+++ b/kernel/bpf/memalloc.c
@@ -119,6 +119,7 @@ struct bpf_mem_cache {
struct llist_head waiting_for_gp_ttrace;
struct rcu_head rcu_ttrace;
atomic_t call_rcu_ttrace_in_progress;
+ raw_spinlock_t lock;
};
struct bpf_mem_caches {
@@ -214,25 +215,24 @@ static void alloc_bulk(struct bpf_mem_cache *c, int cnt, int node, bool atomic)
gfp = __GFP_NOWARN | __GFP_ACCOUNT;
gfp |= atomic ? GFP_NOWAIT : GFP_KERNEL;
- for (i = 0; i < cnt; i++) {
- /*
- * For every 'c' llist_del_first(&c->free_by_rcu_ttrace); is
- * done only by one CPU == current CPU. Other CPUs might
- * llist_add() and llist_del_all() in parallel.
- */
- obj = llist_del_first(&c->free_by_rcu_ttrace);
- if (!obj)
- break;
- add_obj_to_free_list(c, obj);
- }
- if (i >= cnt)
- return;
+ /*
+ * c->lock serializes concurrent llist_del_first() against
+ * llist_del_all() in __free_rcu() and do_call_rcu_ttrace().
+ */
+ scoped_guard(raw_spinlock_irqsave, &c->lock) {
+ for (i = 0; i < cnt; i++) {
+ obj = llist_del_first(&c->free_by_rcu_ttrace);
+ if (!obj)
+ break;
+ add_obj_to_free_list(c, obj);
+ }
- for (; i < cnt; i++) {
- obj = llist_del_first(&c->waiting_for_gp_ttrace);
- if (!obj)
- break;
- add_obj_to_free_list(c, obj);
+ for (; i < cnt; i++) {
+ obj = llist_del_first(&c->waiting_for_gp_ttrace);
+ if (!obj)
+ break;
+ add_obj_to_free_list(c, obj);
+ }
}
if (i >= cnt)
return;
@@ -279,8 +279,12 @@ static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool per
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;
+
+ scoped_guard(raw_spinlock_irqsave, &c->lock)
+ llnode = llist_del_all(&c->waiting_for_gp_ttrace);
- free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size);
+ free_all(c, llnode, !!c->percpu_size);
atomic_set(&c->call_rcu_ttrace_in_progress, 0);
}
@@ -300,7 +304,8 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c)
if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) {
if (unlikely(READ_ONCE(c->draining))) {
- llnode = llist_del_all(&c->free_by_rcu_ttrace);
+ scoped_guard(raw_spinlock_irqsave, &c->lock)
+ llnode = llist_del_all(&c->free_by_rcu_ttrace);
free_all(c, llnode, !!c->percpu_size);
}
return;
@@ -535,6 +540,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu)
c->objcg = objcg;
c->percpu_size = percpu_size;
c->tgt = c;
+ raw_spin_lock_init(&c->lock);
init_refill_work(c);
prefill_mem_cache(c, cpu);
}
@@ -557,7 +563,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu)
c->objcg = objcg;
c->percpu_size = percpu_size;
c->tgt = c;
-
+ raw_spin_lock_init(&c->lock);
init_refill_work(c);
prefill_mem_cache(c, cpu);
}
@@ -609,7 +615,7 @@ int bpf_mem_alloc_percpu_unit_init(struct bpf_mem_alloc *ma, int size)
c->objcg = objcg;
c->percpu_size = percpu_size;
c->tgt = c;
-
+ raw_spin_lock_init(&c->lock);
init_refill_work(c);
prefill_mem_cache(c, cpu);
}
--
2.34.1
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
2026-09-05 2:11 [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk Pu Lehui
@ 2026-09-05 3:00 ` bot+bpf-ci
2026-09-05 3:57 ` Pu Lehui
2026-09-07 2:40 ` Hou Tao
` (3 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: bot+bpf-ci @ 2026-09-05 3:00 UTC (permalink / raw)
To: pulehui, bpf, linux-kernel, ast, houtao, leon.hwang
Cc: daniel, andrii, eddyz87, memxor, martin.lau, yonghong.song, song,
jolsa, emil, ihor.solodrai, pulehui, pulehui, ast, andrii,
daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3164 bytes --]
> 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
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
2026-09-05 3:00 ` bot+bpf-ci
@ 2026-09-05 3:57 ` Pu Lehui
0 siblings, 0 replies; 8+ messages in thread
From: Pu Lehui @ 2026-09-05 3:57 UTC (permalink / raw)
To: bot+bpf-ci, bpf, linux-kernel, ast, houtao, leon.hwang
Cc: daniel, andrii, eddyz87, memxor, martin.lau, yonghong.song, song,
jolsa, emil, ihor.solodrai, pulehui, martin.lau, mason
On 2026/9/5 11:00, bot+bpf-ci@kernel.org 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
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
2026-09-05 2:11 [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk Pu Lehui
2026-09-05 3:00 ` bot+bpf-ci
@ 2026-09-07 2:40 ` Hou Tao
2026-09-11 1:17 ` Pu Lehui
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Hou Tao @ 2026-09-07 2:40 UTC (permalink / raw)
To: Pu Lehui, bpf, linux-kernel, Alexei Starovoitov, Leon Hwang
Cc: Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Yonghong Song,
Song Liu, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, Pu Lehui
On 9/5/2026 10:11 AM, 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
> ...
> 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@kernel.org>
> Suggested-by: Hou Tao <houtao1@huawei.com>
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
Acked-by: Hou Tao <houtao1@huawei.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
2026-09-05 2:11 [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk Pu Lehui
2026-09-05 3:00 ` bot+bpf-ci
2026-09-07 2:40 ` Hou Tao
@ 2026-09-11 1:17 ` Pu Lehui
2026-09-11 15:29 ` Alexei Starovoitov
2026-09-14 2:48 ` Alexei Starovoitov
2026-09-14 2:50 ` patchwork-bot+netdevbpf
4 siblings, 1 reply; 8+ messages in thread
From: Pu Lehui @ 2026-09-11 1:17 UTC (permalink / raw)
To: bpf, linux-kernel, Alexei Starovoitov, Hou Tao, Leon Hwang
Cc: Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Yonghong Song,
Song Liu, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, Pu Lehui
Gentle ping~
Hi all, Is this commit looks proper?
On 2026/9/5 10:11, 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
> ...
> 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@kernel.org>
> Suggested-by: Hou Tao <houtao1@huawei.com>
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
> ---
> v4:
> - Update comments for ttrace list in alloc_bulk. (BPF CI)
> - Drop initial troubleshooting explaining in commit message.
>
> v3: https://lore.kernel.org/bpf/20260901150307.3474349-1-pulehui@huaweicloud.com
> - Fix concurrent issue also for free_by_rcu_ttrace. (bpfci and Hou Tao)
> - Use scoped_guard. (Leon)
>
> v2: https://lore.kernel.org/bpf/20260827084013.1062816-1-pulehui@huaweicloud.com
> - Use raw spinlock to fix concurrent alloc_bulk and __free_rcu on
> waiting_for_gp_ttrace after GP. (Hou Tao)
>
> v1: https://lore.kernel.org/bpf/20260826103615.932094-1-pulehui@huaweicloud.com
>
> kernel/bpf/memalloc.c | 50 ++++++++++++++++++++++++-------------------
> 1 file changed, 28 insertions(+), 22 deletions(-)
>
> diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c
> index e9662db7198f..8a8f088e83e6 100644
> --- a/kernel/bpf/memalloc.c
> +++ b/kernel/bpf/memalloc.c
> @@ -119,6 +119,7 @@ struct bpf_mem_cache {
> struct llist_head waiting_for_gp_ttrace;
> struct rcu_head rcu_ttrace;
> atomic_t call_rcu_ttrace_in_progress;
> + raw_spinlock_t lock;
> };
>
> struct bpf_mem_caches {
> @@ -214,25 +215,24 @@ static void alloc_bulk(struct bpf_mem_cache *c, int cnt, int node, bool atomic)
> gfp = __GFP_NOWARN | __GFP_ACCOUNT;
> gfp |= atomic ? GFP_NOWAIT : GFP_KERNEL;
>
> - for (i = 0; i < cnt; i++) {
> - /*
> - * For every 'c' llist_del_first(&c->free_by_rcu_ttrace); is
> - * done only by one CPU == current CPU. Other CPUs might
> - * llist_add() and llist_del_all() in parallel.
> - */
> - obj = llist_del_first(&c->free_by_rcu_ttrace);
> - if (!obj)
> - break;
> - add_obj_to_free_list(c, obj);
> - }
> - if (i >= cnt)
> - return;
> + /*
> + * c->lock serializes concurrent llist_del_first() against
> + * llist_del_all() in __free_rcu() and do_call_rcu_ttrace().
> + */
> + scoped_guard(raw_spinlock_irqsave, &c->lock) {
> + for (i = 0; i < cnt; i++) {
> + obj = llist_del_first(&c->free_by_rcu_ttrace);
> + if (!obj)
> + break;
> + add_obj_to_free_list(c, obj);
> + }
>
> - for (; i < cnt; i++) {
> - obj = llist_del_first(&c->waiting_for_gp_ttrace);
> - if (!obj)
> - break;
> - add_obj_to_free_list(c, obj);
> + for (; i < cnt; i++) {
> + obj = llist_del_first(&c->waiting_for_gp_ttrace);
> + if (!obj)
> + break;
> + add_obj_to_free_list(c, obj);
> + }
> }
> if (i >= cnt)
> return;
> @@ -279,8 +279,12 @@ static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool per
> 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;
> +
> + scoped_guard(raw_spinlock_irqsave, &c->lock)
> + llnode = llist_del_all(&c->waiting_for_gp_ttrace);
>
> - free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size);
> + free_all(c, llnode, !!c->percpu_size);
> atomic_set(&c->call_rcu_ttrace_in_progress, 0);
> }
>
> @@ -300,7 +304,8 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c)
>
> if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) {
> if (unlikely(READ_ONCE(c->draining))) {
> - llnode = llist_del_all(&c->free_by_rcu_ttrace);
> + scoped_guard(raw_spinlock_irqsave, &c->lock)
> + llnode = llist_del_all(&c->free_by_rcu_ttrace);
> free_all(c, llnode, !!c->percpu_size);
> }
> return;
> @@ -535,6 +540,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu)
> c->objcg = objcg;
> c->percpu_size = percpu_size;
> c->tgt = c;
> + raw_spin_lock_init(&c->lock);
> init_refill_work(c);
> prefill_mem_cache(c, cpu);
> }
> @@ -557,7 +563,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu)
> c->objcg = objcg;
> c->percpu_size = percpu_size;
> c->tgt = c;
> -
> + raw_spin_lock_init(&c->lock);
> init_refill_work(c);
> prefill_mem_cache(c, cpu);
> }
> @@ -609,7 +615,7 @@ int bpf_mem_alloc_percpu_unit_init(struct bpf_mem_alloc *ma, int size)
> c->objcg = objcg;
> c->percpu_size = percpu_size;
> c->tgt = c;
> -
> + raw_spin_lock_init(&c->lock);
> init_refill_work(c);
> prefill_mem_cache(c, cpu);
> }
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
2026-09-11 1:17 ` Pu Lehui
@ 2026-09-11 15:29 ` Alexei Starovoitov
0 siblings, 0 replies; 8+ messages in thread
From: Alexei Starovoitov @ 2026-09-11 15:29 UTC (permalink / raw)
To: Pu Lehui, bpf, linux-kernel, Alexei Starovoitov, Hou Tao, Leon Hwang
Cc: Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Yonghong Song,
Song Liu, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, Pu Lehui
On Thu Sep 10, 2026 at 6:17 PM PDT, Pu Lehui wrote:
> Gentle ping~
>
> Hi all, Is this commit looks proper?
Sorry. swamped. It's on my todo list.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
2026-09-05 2:11 [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk Pu Lehui
` (2 preceding siblings ...)
2026-09-11 1:17 ` Pu Lehui
@ 2026-09-14 2:48 ` Alexei Starovoitov
2026-09-14 2:50 ` patchwork-bot+netdevbpf
4 siblings, 0 replies; 8+ messages in thread
From: Alexei Starovoitov @ 2026-09-14 2:48 UTC (permalink / raw)
To: Pu Lehui
Cc: bpf, LKML, Alexei Starovoitov, Hou Tao, Leon Hwang,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Yonghong Song,
Song Liu, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai, Pu Lehui
On Fri, Sep 4, 2026 at 7:06 PM Pu Lehui <pulehui@huaweicloud.com> 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
> ...
> 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))
Thank you both for the fix!
Took me some time to page-in all this tricky logic.
Applied.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
2026-09-05 2:11 [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk Pu Lehui
` (3 preceding siblings ...)
2026-09-14 2:48 ` Alexei Starovoitov
@ 2026-09-14 2:50 ` patchwork-bot+netdevbpf
4 siblings, 0 replies; 8+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-14 2:50 UTC (permalink / raw)
To: Pu Lehui
Cc: bpf, linux-kernel, ast, houtao, leon.hwang, daniel, andrii,
eddyz87, memxor, martin.lau, yonghong.song, song, jolsa, emil,
ihor.solodrai, pulehui
Hello:
This patch was applied to bpf/bpf.git (master)
by Alexei Starovoitov <ast@kernel.org>:
On Sat, 5 Sep 2026 02:11:39 +0000 you 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
> ...
> 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
>
> [...]
Here is the summary with links:
- [bpf,v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
https://git.kernel.org/bpf/bpf/c/1c21452d02ee
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-14 2:51 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-05 2:11 [PATCH bpf v4] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk Pu Lehui
2026-09-05 3:00 ` bot+bpf-ci
2026-09-05 3:57 ` Pu Lehui
2026-09-07 2:40 ` Hou Tao
2026-09-11 1:17 ` Pu Lehui
2026-09-11 15:29 ` Alexei Starovoitov
2026-09-14 2:48 ` Alexei Starovoitov
2026-09-14 2:50 ` patchwork-bot+netdevbpf
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®