]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
PM: sleep: wakeirq: harden dev_pm_clear_wake_irq() against races
authorGui-Dong Han <hanguidong02@gmail.com>
Tue, 3 Feb 2026 03:19:43 +0000 (11:19 +0800)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Tue, 3 Feb 2026 21:22:37 +0000 (22:22 +0100)
dev_pm_clear_wake_irq() currently uses a dangerous pattern where
dev->power.wakeirq is read and checked for NULL outside the lock.
If two callers invoke this function concurrently, both might see
a valid pointer and proceed. This could result in a double-free
when the second caller acquires the lock and tries to release the
same object.

Address this by removing the lockless check of dev->power.wakeirq.
Instead, acquire dev->power.lock immediately to ensure the check and
the subsequent operations are atomic. If dev->power.wakeirq is NULL
under the lock, simply unlock and return. This guarantees that
concurrent calls cannot race to free the same object.

Based on a quick scan of current users, I did not find an actual bug as
drivers seem to rely on their own synchronization. However, since
asynchronous usage patterns exist (e.g., in
drivers/net/wireless/ti/wlcore), I believe a race is theoretically
possible if the API is used less carefully in the future. This change
hardens the API to be robust against such cases.

Fixes: 4990d4fe327b ("PM / Wakeirq: Add automated device wake IRQ handling")
Signed-off-by: Gui-Dong Han <hanguidong02@gmail.com>
Link: https://patch.msgid.link/20260203031943.1924-1-hanguidong02@gmail.com
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/base/power/wakeirq.c

index 8aa28c08b2891f3af490175362cc1a759069bd50..c0809d18fc5406b224206e0c8e5028aa8f4183b0 100644 (file)
@@ -83,13 +83,16 @@ EXPORT_SYMBOL_GPL(dev_pm_set_wake_irq);
  */
 void dev_pm_clear_wake_irq(struct device *dev)
 {
-       struct wake_irq *wirq = dev->power.wakeirq;
+       struct wake_irq *wirq;
        unsigned long flags;
 
-       if (!wirq)
+       spin_lock_irqsave(&dev->power.lock, flags);
+       wirq = dev->power.wakeirq;
+       if (!wirq) {
+               spin_unlock_irqrestore(&dev->power.lock, flags);
                return;
+       }
 
-       spin_lock_irqsave(&dev->power.lock, flags);
        device_wakeup_detach_irq(dev);
        dev->power.wakeirq = NULL;
        spin_unlock_irqrestore(&dev->power.lock, flags);