diff options
| author | Bradley Morgan <include@grrlz.net> | 2026-08-13 07:43:43 +0000 |
|---|---|---|
| committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2026-09-11 15:53:53 +0200 |
| commit | 61f7eee7d4664079fbce821576891c3ee5ccf597 (patch) | |
| tree | 7bb29c305adf8ad6f804c27fea020a84ff5ec6df /kernel | |
| parent | 04cb4f75f314e137f0a0c38f9b4fd5c2b4b5667d (diff) | |
| download | linux-next-61f7eee7d4664079fbce821576891c3ee5ccf597.tar.gz linux-next-61f7eee7d4664079fbce821576891c3ee5ccf597.zip | |
PM: CPU: Restore synchronize_rcu() to cpu_pm_unregister_notifier()
cpu_pm_notify() walks the notifier chain lockless under only
rcu_read_lock(). That only works if a removed notifier block is not
freed until every concurrent walker is done with it.
The chain used to be an atomic_notifier, whose unregister ends in
synchronize_rcu(). Commit b2f6662ac08d ("PM: cpu: Make notifier chain
use a raw_spinlock_t") switched it over to a raw_notifier, and in
doing so replaced that with raw_notifier_chain_unregister(), which
does not synchronize. The grace period quietly went away, so a driver
that frees the memory holding its notifier block right after
cpu_pm_unregister_notifier() returns can race with a concurrent
walker:
cpu1 (idle exit) cpu2 (driver remove)
---------------- --------------------
cpu_pm_notify(CPU_PM_EXIT)
rcu_read_lock()
nb = rcu_dereference_raw(*nl)
cpu_pm_unregister_notifier(&od->nb)
unlink, no grace period
remove() frees od (devm)
nb->notifier_call(nb, ...)
/* use after free */
The rcu_read_lock() on cpu1 does not stop cpu2 from freeing the
block, and nothing else does. The read side is the idle exit path,
so this can hit on any system where a driver with a cpu_pm notifier
gets unbound.
Add the grace period back. It is only needed when a notifier was
actually removed, so wait on success and return -ENOENT without
waiting otherwise. The kerneldoc gets back the "may sleep" note that
the same commit dropped.
Fixes: b2f6662ac08d ("PM: cpu: Make notifier chain use a raw_spinlock_t")
Cc: All applicable <stable@vger.kernel.org>
Signed-off-by: Bradley Morgan <include@grrlz.net>
[ rjw: Subject tweak ]
Link: https://patch.msgid.link/20260813074343.26460-3-include@grrlz.net
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/cpu_pm.c | 9 |
1 files changed, 8 insertions, 1 deletions
diff --git a/kernel/cpu_pm.c b/kernel/cpu_pm.c index 7481fbb947d3..a2a598ad6e6d 100644 --- a/kernel/cpu_pm.c +++ b/kernel/cpu_pm.c @@ -10,6 +10,7 @@ #include <linux/cpu_pm.h> #include <linux/module.h> #include <linux/notifier.h> +#include <linux/rcupdate.h> #include <linux/spinlock.h> #include <linux/syscore_ops.h> @@ -76,7 +77,8 @@ EXPORT_SYMBOL_GPL(cpu_pm_register_notifier); * * Remove a driver from the CPU PM notifier list. * - * This function has the same return conditions as raw_notifier_chain_unregister. + * This function may sleep, and has the same return conditions as + * raw_notifier_chain_unregister. */ int cpu_pm_unregister_notifier(struct notifier_block *nb) { @@ -86,6 +88,11 @@ int cpu_pm_unregister_notifier(struct notifier_block *nb) raw_spin_lock_irqsave(&cpu_pm_notifier.lock, flags); ret = raw_notifier_chain_unregister(&cpu_pm_notifier.chain, nb); raw_spin_unlock_irqrestore(&cpu_pm_notifier.lock, flags); + + /* Wait for the rcu_read_lock() walkers in cpu_pm_notify(). */ + if (!ret) + synchronize_rcu(); + return ret; } EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier); |
