Message ID | 4d4ac43578f9aad9d7eee85a51833ae5e4b7cdb3.1722335040.git.lukas@wunner.de (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | [5.10-stable] PCI/DPC: Fix use-after-free on concurrent DPC and hot-removal | expand |
On Tue, Jul 30, 2024 at 12:26:55PM +0200, Lukas Wunner wrote: > commit 11a1f4bc47362700fcbde717292158873fb847ed upstream. > > Keith reports a use-after-free when a DPC event occurs concurrently to > hot-removal of the same portion of the hierarchy: > > The dpc_handler() awaits readiness of the secondary bus below the > Downstream Port where the DPC event occurred. To do so, it polls the > config space of the first child device on the secondary bus. If that > child device is concurrently removed, accesses to its struct pci_dev > cause the kernel to oops. > > That's because pci_bridge_wait_for_secondary_bus() neglects to hold a > reference on the child device. Before v6.3, the function was only > called on resume from system sleep or on runtime resume. Holding a > reference wasn't necessary back then because the pciehp IRQ thread > could never run concurrently. (On resume from system sleep, IRQs are > not enabled until after the resume_noirq phase. And runtime resume is > always awaited before a PCI device is removed.) > > However starting with v6.3, pci_bridge_wait_for_secondary_bus() is also > called on a DPC event. Commit 53b54ad074de ("PCI/DPC: Await readiness > of secondary bus after reset"), which introduced that, failed to > appreciate that pci_bridge_wait_for_secondary_bus() now needs to hold a > reference on the child device because dpc_handler() and pciehp may > indeed run concurrently. The commit was backported to v5.10+ stable > kernels, so that's the oldest one affected. > > Add the missing reference acquisition. > > Abridged stack trace: > > BUG: unable to handle page fault for address: 00000000091400c0 > CPU: 15 PID: 2464 Comm: irq/53-pcie-dpc 6.9.0 > RIP: pci_bus_read_config_dword+0x17/0x50 > pci_dev_wait() > pci_bridge_wait_for_secondary_bus() > dpc_reset_link() > pcie_do_recovery() > dpc_handler() > > Fixes: 53b54ad074de ("PCI/DPC: Await readiness of secondary bus after reset") > Closes: https://lore.kernel.org/r/20240612181625.3604512-3-kbusch@meta.com/ > Link: https://lore.kernel.org/linux-pci/8e4bcd4116fd94f592f2bf2749f168099c480ddf.1718707743.git.lukas@wunner.de > Reported-by: Keith Busch <kbusch@kernel.org> > Tested-by: Keith Busch <kbusch@kernel.org> > Signed-off-by: Lukas Wunner <lukas@wunner.de> > Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org> > Reviewed-by: Keith Busch <kbusch@kernel.org> > Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com> > Cc: stable@vger.kernel.org # v5.10+ > --- > drivers/pci/pci.c | 19 ++++++++++++------- > 1 file changed, 12 insertions(+), 7 deletions(-) > Both now queued up, thanks. greg k-h
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 530ced8f7abd..09d5fa637b98 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4817,7 +4817,7 @@ int pci_bridge_wait_for_secondary_bus(struct pci_dev *dev, char *reset_type, int timeout) { struct pci_dev *child; - int delay; + int delay, ret = 0; if (pci_dev_is_disconnected(dev)) return 0; @@ -4845,8 +4845,8 @@ int pci_bridge_wait_for_secondary_bus(struct pci_dev *dev, char *reset_type, return 0; } - child = list_first_entry(&dev->subordinate->devices, struct pci_dev, - bus_list); + child = pci_dev_get(list_first_entry(&dev->subordinate->devices, + struct pci_dev, bus_list)); up_read(&pci_bus_sem); /* @@ -4856,7 +4856,7 @@ int pci_bridge_wait_for_secondary_bus(struct pci_dev *dev, char *reset_type, if (!pci_is_pcie(dev)) { pci_dbg(dev, "waiting %d ms for secondary bus\n", 1000 + delay); msleep(1000 + delay); - return 0; + goto put_child; } /* @@ -4877,7 +4877,7 @@ int pci_bridge_wait_for_secondary_bus(struct pci_dev *dev, char *reset_type, * until the timeout expires. */ if (!pcie_downstream_port(dev)) - return 0; + goto put_child; if (pcie_get_speed_cap(dev) <= PCIE_SPEED_5_0GT) { pci_dbg(dev, "waiting %d ms for downstream link\n", delay); @@ -4888,11 +4888,16 @@ int pci_bridge_wait_for_secondary_bus(struct pci_dev *dev, char *reset_type, if (!pcie_wait_for_link_delay(dev, true, delay)) { /* Did not train, no need to wait any further */ pci_info(dev, "Data Link Layer Link Active not set in 1000 msec\n"); - return -ENOTTY; + ret = -ENOTTY; + goto put_child; } } - return pci_dev_wait(child, reset_type, timeout - delay); + ret = pci_dev_wait(child, reset_type, timeout - delay); + +put_child: + pci_dev_put(child); + return ret; } void pci_reset_secondary_bus(struct pci_dev *dev)