Skip to content

Commit

Permalink
perf/core: Fix perf_pmu_register() vs. perf_init_event()
Browse files Browse the repository at this point in the history
[ Upstream commit 003659f ]

There is a fairly obvious race between perf_init_event() doing
idr_find() and perf_pmu_register() doing idr_alloc() with an
incompletely initialized PMU pointer.

Avoid by doing idr_alloc() on a NULL pointer to register the id, and
swizzling the real struct pmu pointer at the end using idr_replace().

Also making sure to not set struct pmu members after publishing
the struct pmu, duh.

[ introduce idr_cmpxchg() in order to better handle the idr_replace()
  error case -- if it were to return an unexpected pointer, it will
  already have replaced the value and there is no going back. ]

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Link: https://lore.kernel.org/r/20241104135517.858805880@infradead.org
Signed-off-by: Sasha Levin <sashal@kernel.org>
  • Loading branch information
Peter Zijlstra authored and Greg Kroah-Hartman committed Apr 10, 2025
1 parent 11e2ae4 commit 68ee6f7
Showing 1 changed file with 26 additions and 2 deletions.
28 changes: 26 additions & 2 deletions kernel/events/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -11556,6 +11556,21 @@ static int pmu_dev_alloc(struct pmu *pmu)
static struct lock_class_key cpuctx_mutex;
static struct lock_class_key cpuctx_lock;

static bool idr_cmpxchg(struct idr *idr, unsigned long id, void *old, void *new)
{
void *tmp, *val = idr_find(idr, id);

if (val != old)
return false;

tmp = idr_replace(idr, new, id);
if (IS_ERR(tmp))
return false;

WARN_ON_ONCE(tmp != val);
return true;
}

int perf_pmu_register(struct pmu *pmu, const char *name, int type)
{
int cpu, ret, max = PERF_TYPE_MAX;
Expand All @@ -11577,14 +11592,15 @@ int perf_pmu_register(struct pmu *pmu, const char *name, int type)
if (type >= 0)
max = type;

ret = idr_alloc(&pmu_idr, pmu, max, 0, GFP_KERNEL);
ret = idr_alloc(&pmu_idr, NULL, max, 0, GFP_KERNEL);
if (ret < 0)
goto free_pdc;

WARN_ON(type >= 0 && ret != type);

type = ret;
pmu->type = type;
atomic_set(&pmu->exclusive_cnt, 0);

if (pmu_bus_running && !pmu->dev) {
ret = pmu_dev_alloc(pmu);
Expand Down Expand Up @@ -11633,14 +11649,22 @@ int perf_pmu_register(struct pmu *pmu, const char *name, int type)
if (!pmu->event_idx)
pmu->event_idx = perf_event_idx_default;

/*
* Now that the PMU is complete, make it visible to perf_try_init_event().
*/
if (!idr_cmpxchg(&pmu_idr, pmu->type, NULL, pmu))
goto free_context;
list_add_rcu(&pmu->entry, &pmus);
atomic_set(&pmu->exclusive_cnt, 0);

ret = 0;
unlock:
mutex_unlock(&pmus_lock);

return ret;

free_context:
free_percpu(pmu->cpu_pmu_context);

free_dev:
if (pmu->dev && pmu->dev != PMU_NULL_DEV) {
device_del(pmu->dev);
Expand Down

0 comments on commit 68ee6f7

Please sign in to comment.