perf/core: Fix perf_pmu_register() vs. perf_init_event()
[ Upstream commit 003659fec9f6d8c04738cb74b5384398ae8a7e88 ] 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>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
fbd3a04bd8
commit
7364420090
+26
-2
@@ -11737,6 +11737,21 @@ free_dev:
|
|||||||
static struct lock_class_key cpuctx_mutex;
|
static struct lock_class_key cpuctx_mutex;
|
||||||
static struct lock_class_key cpuctx_lock;
|
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 perf_pmu_register(struct pmu *pmu, const char *name, int type)
|
||||||
{
|
{
|
||||||
int cpu, ret, max = PERF_TYPE_MAX;
|
int cpu, ret, max = PERF_TYPE_MAX;
|
||||||
@@ -11763,7 +11778,7 @@ int perf_pmu_register(struct pmu *pmu, const char *name, int type)
|
|||||||
if (type >= 0)
|
if (type >= 0)
|
||||||
max = type;
|
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)
|
if (ret < 0)
|
||||||
goto free_pdc;
|
goto free_pdc;
|
||||||
|
|
||||||
@@ -11771,6 +11786,7 @@ int perf_pmu_register(struct pmu *pmu, const char *name, int type)
|
|||||||
|
|
||||||
type = ret;
|
type = ret;
|
||||||
pmu->type = type;
|
pmu->type = type;
|
||||||
|
atomic_set(&pmu->exclusive_cnt, 0);
|
||||||
|
|
||||||
if (pmu_bus_running && !pmu->dev) {
|
if (pmu_bus_running && !pmu->dev) {
|
||||||
ret = pmu_dev_alloc(pmu);
|
ret = pmu_dev_alloc(pmu);
|
||||||
@@ -11819,14 +11835,22 @@ int perf_pmu_register(struct pmu *pmu, const char *name, int type)
|
|||||||
if (!pmu->event_idx)
|
if (!pmu->event_idx)
|
||||||
pmu->event_idx = perf_event_idx_default;
|
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);
|
list_add_rcu(&pmu->entry, &pmus);
|
||||||
atomic_set(&pmu->exclusive_cnt, 0);
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
unlock:
|
unlock:
|
||||||
mutex_unlock(&pmus_lock);
|
mutex_unlock(&pmus_lock);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
free_context:
|
||||||
|
free_percpu(pmu->cpu_pmu_context);
|
||||||
|
|
||||||
free_dev:
|
free_dev:
|
||||||
if (pmu->dev && pmu->dev != PMU_NULL_DEV) {
|
if (pmu->dev && pmu->dev != PMU_NULL_DEV) {
|
||||||
device_del(pmu->dev);
|
device_del(pmu->dev);
|
||||||
|
|||||||
Reference in New Issue
Block a user