[PATCH OLK-6.6] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk
maillist inclusion category: bugfix bugzilla: https://atomgit.com/openeuler/kernel/issues/9873 Reference: https://lore.kernel.org/bpf/20260901150307.3474349-1-pulehui@huaweicloud.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 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 RCU read-side CS (!PREEMPT_RT runs in IRQ context, PREEMPT_RT runs with guard(rcu)), an RCU Tasks Trace GP cannot complete while alloc_bulk() is accessing the list. Instead, the UAF occurs after the GP expires: when the __free_rcu() callback runs, there is no synchronization protecting llist_del_all() against concurrent alloc_bulk() operates 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> Conflicts: kernel/bpf/memalloc.c [not merged bpf_mem_alloc_percpu_unit_init] Signed-off-by: Pu Lehui <pulehui@huawei.com> --- kernel/bpf/memalloc.c | 49 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c index b4f27c34dd94..afdc0fab5ccd 100644 --- a/kernel/bpf/memalloc.c +++ b/kernel/bpf/memalloc.c @@ -120,6 +120,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 { @@ -216,25 +217,25 @@ 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; + scoped_guard(raw_spinlock_irqsave, &c->lock) { + 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); + } - 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; @@ -284,8 +285,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; - free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size); + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->waiting_for_gp_ttrace); + + free_all(c, llnode, !!c->percpu_size); atomic_set(&c->call_rcu_ttrace_in_progress, 0); } @@ -316,7 +321,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; @@ -532,6 +538,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); } @@ -556,7 +563,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu) c->unit_size = sizes[i]; c->objcg = objcg; c->tgt = c; - + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } -- 2.34.1
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://atomgit.com/openeuler/kernel/merge_requests/27228 邮件列表地址:https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/ZVC... FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://atomgit.com/openeuler/kernel/merge_requests/27228 Mailing list address: https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/ZVC...
participants (2)
-
patchwork bot -
Pu Lehui