diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2014-01-14 19:04:51 (GMT) |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2014-01-15 17:34:13 (GMT) |
commit | 8a4c5c329de716996eea03d93753ccbb5406072b (patch) | |
tree | 4a9c74354b7f7bbb39ae0426246d4b9baff3cc7f /drivers/pci | |
parent | a83919e0940f6eb8f77ab1d602a063f8a6703117 (diff) | |
download | linux-8a4c5c329de716996eea03d93753ccbb5406072b.tar.xz |
PCI: Check parent kobject in pci_destroy_dev()
If pci_stop_and_remove_bus_device() is run concurrently for a device and
its parent bridge via remove_callback(), both code paths attempt to acquire
pci_rescan_remove_lock. If the child device removal acquires it first,
there will be no problems. However, if the parent bridge removal acquires
it first, it will eventually execute pci_destroy_dev() for the child
device, but that device object will not be freed yet due to the reference
held by the concurrent child removal. Consequently, both
pci_stop_bus_device() and pci_remove_bus_device() will be executed for that
device unnecessarily and pci_destroy_dev() will see a corrupted list head
in that object. Moreover, an excess put_device() will be executed for that
device in that case which may lead to a use-after-free in the final
kobject_put() done by sysfs_schedule_callback_work().
To avoid that problem, make pci_destroy_dev() check if the device's parent
kobject is NULL, which only happens after device_del() has already run for
it. Make pci_destroy_dev() return immediately whithout doing anything in
that case.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/remove.c | 3 |
1 files changed, 3 insertions, 0 deletions
diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 10fa13f..4ff36bf 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -20,6 +20,9 @@ static void pci_stop_dev(struct pci_dev *dev) static void pci_destroy_dev(struct pci_dev *dev) { + if (!dev->dev.kobj.parent) + return; + device_del(&dev->dev); put_device(&dev->dev); |