perf: Fix UAF in attach_task_ctx_data()

struct perf_ctx_data is RCU protected. In attach_task_ctx_data(),
the cmpxchg retry loop runs without holding rcu_read_lock(). If
task->perf_ctx_data already holds a pointer (call it `cd1`), then
try_cmpxchg(&task->perf_ctx_data, &old, cd) fails and loads old = cd1.
A concurrent thread running detach_task_ctx_data() could free cd1
leading to UAF and further memory corruption when cd1 is reclaimed by
another thread.

  CPU 0: attach_task_ctx_data()     CPU 1: detach_task_ctx_data()
  -----------------------------     -----------------------------
  try_cmpxchg(&task->perf_ctx_data,
              &old, cd2)
    -> fails; loads old = cd1

                                    refcount_dec_and_test(&cd1->refcount)
                                    try_cmpxchg(&task->perf_ctx_data,
                                                &cd1, NULL)
                                    perf_free_ctx_data_rcu(cd1)
                                    [ RCU grace period -> kfree(cd1) ]

  refcount_inc_not_zero(&old->refcount)
    -> UAF on freed cd1

Fix this by taking rcu_read_lock across the race window.

Fixes: 506e64e710 ("perf: attach/detach PMU specific data")
Signed-off-by: Lee Jia Jie <mkofdwu@gmail.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
This commit is contained in:
Lee Jia Jie 2026-10-22 20:58:48 +08:00 committed by Peter Zijlstra
parent 2b774b5bbf
commit 64ea0e8226

View File

@ -5434,6 +5434,9 @@ attach_task_ctx_data(struct task_struct *task, struct kmem_cache *ctx_cache,
return -ENOMEM;
for (;;) {
/* Protect @old until inc_not_zero(&old->refcount) */
guard(rcu)();
if (try_cmpxchg(&task->perf_ctx_data, &old, cd)) {
if (old)
perf_free_ctx_data_rcu(old);