Skip to content

Commit 0552e05

Browse files
committed
PM: core: Fix handling of devices deleted during system-wide resume
If a device is deleted by one of its system-wide resume callbacks (for example, because it does not appear to be present or accessible any more) along with its children, the resume of the children may continue leading to use-after-free errors and other issues (potentially). Namely, if the device's children are resumed asynchronously, their resume may have been scheduled already before the device's callback runs and so the device may be deleted while dpm_wait_for_superior() is being executed for them. The memory taken up by the parent device object may be freed then while dpm_wait() is waiting for the parent's resume callback to complete, which leads to a use-after-free. Moreover, the resume of the children is really not expected to continue after they have been unregistered, so it must be terminated right away in that case. To address this problem, modify dpm_wait_for_superior() to check if the target device is still there in the system-wide PM list of devices and if so, to increment its parent's reference counter, both under dpm_list_mtx which prevents device_del() running for the child from dropping the parent's reference counter prematurely. If the device is not present in the system-wide PM list of devices any more, the resume of it cannot continue, so check that again after dpm_wait() returns, which means that the parent's callback has been completed, and pass the result of that check to the caller of dpm_wait_for_superior() to allow it to abort the device's resume if it is not there any more. Link: https://lore.kernel.org/linux-pm/[email protected] Reported-by: Chanho Min <[email protected]> Cc: All applicable <[email protected]> Signed-off-by: Rafael J. Wysocki <[email protected]> Acked-by: Greg Kroah-Hartman <[email protected]>
1 parent d229290 commit 0552e05

File tree

1 file changed

+37
-5
lines changed

1 file changed

+37
-5
lines changed

drivers/base/power/main.c

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,38 @@ static void dpm_wait_for_suppliers(struct device *dev, bool async)
273273
device_links_read_unlock(idx);
274274
}
275275

276-
static void dpm_wait_for_superior(struct device *dev, bool async)
276+
static bool dpm_wait_for_superior(struct device *dev, bool async)
277277
{
278-
dpm_wait(dev->parent, async);
278+
struct device *parent;
279+
280+
/*
281+
* If the device is resumed asynchronously and the parent's callback
282+
* deletes both the device and the parent itself, the parent object may
283+
* be freed while this function is running, so avoid that by reference
284+
* counting the parent once more unless the device has been deleted
285+
* already (in which case return right away).
286+
*/
287+
mutex_lock(&dpm_list_mtx);
288+
289+
if (!device_pm_initialized(dev)) {
290+
mutex_unlock(&dpm_list_mtx);
291+
return false;
292+
}
293+
294+
parent = get_device(dev->parent);
295+
296+
mutex_unlock(&dpm_list_mtx);
297+
298+
dpm_wait(parent, async);
299+
put_device(parent);
300+
279301
dpm_wait_for_suppliers(dev, async);
302+
303+
/*
304+
* If the parent's callback has deleted the device, attempting to resume
305+
* it would be invalid, so avoid doing that then.
306+
*/
307+
return device_pm_initialized(dev);
280308
}
281309

282310
static void dpm_wait_for_consumers(struct device *dev, bool async)
@@ -621,7 +649,8 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn
621649
if (!dev->power.is_noirq_suspended)
622650
goto Out;
623651

624-
dpm_wait_for_superior(dev, async);
652+
if (!dpm_wait_for_superior(dev, async))
653+
goto Out;
625654

626655
skip_resume = dev_pm_may_skip_resume(dev);
627656

@@ -829,7 +858,8 @@ static int device_resume_early(struct device *dev, pm_message_t state, bool asyn
829858
if (!dev->power.is_late_suspended)
830859
goto Out;
831860

832-
dpm_wait_for_superior(dev, async);
861+
if (!dpm_wait_for_superior(dev, async))
862+
goto Out;
833863

834864
callback = dpm_subsys_resume_early_cb(dev, state, &info);
835865

@@ -944,7 +974,9 @@ static int device_resume(struct device *dev, pm_message_t state, bool async)
944974
goto Complete;
945975
}
946976

947-
dpm_wait_for_superior(dev, async);
977+
if (!dpm_wait_for_superior(dev, async))
978+
goto Complete;
979+
948980
dpm_watchdog_set(&wd, dev);
949981
device_lock(dev);
950982

0 commit comments

Comments
 (0)