diff mbox series

PCI: fix possible null-ptr-deref in device_attach()

Message ID 20221026092101.619888-1-yangyingliang@huawei.com (mailing list archive)
State New, archived
Delegated to: Bjorn Helgaas
Headers show
Series PCI: fix possible null-ptr-deref in device_attach() | expand

Commit Message

Yang Yingliang Oct. 26, 2022, 9:21 a.m. UTC
After commit 4f535093cf8f ("PCI: Put pci_dev in device tree as early
as possible"), pcibios_add_device() and device_add() are moved to
pci_device_add(), and it never return error code in error cases.

If pcibios_device_add() or device_add() returns error in pci_device_add(),
the 'dev->p' is not set which will lead null-ptr-deref in device_attach()
called by pci_bus_add_device().

Change return type of pci_device_add() to int, and handle error in it
if pcibios_device_add() or device_add() fails. So callers can check the
return value and handle it.

Fixes: 4f535093cf8f ("PCI: Put pci_dev in device tree as early as possible")
Signed-off-by: Yang Yingliang <yangyingliang@huawei.com>
---
 drivers/pci/probe.c | 21 +++++++++++++++++----
 include/linux/pci.h |  2 +-
 2 files changed, 18 insertions(+), 5 deletions(-)
diff mbox series

Patch

diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 03b926d7c7ec..792dfee9cfd7 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2517,7 +2517,7 @@  static void pci_set_msi_domain(struct pci_dev *dev)
 	dev_set_msi_domain(&dev->dev, d);
 }
 
-void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
+int pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
 {
 	int ret;
 
@@ -2552,7 +2552,8 @@  void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
 	up_write(&pci_bus_sem);
 
 	ret = pcibios_device_add(dev);
-	WARN_ON(ret < 0);
+	if (WARN_ON(ret < 0))
+		goto err;
 
 	/* Set up MSI IRQ domain */
 	pci_set_msi_domain(dev);
@@ -2560,7 +2561,16 @@  void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
 	/* Notifier could use PCI capabilities */
 	dev->match_driver = false;
 	ret = device_add(&dev->dev);
-	WARN_ON(ret < 0);
+	if (WARN_ON(ret < 0))
+		goto err;
+
+	return 0;
+
+err:
+	down_write(&pci_bus_sem);
+	list_del(&dev->bus_list);
+	up_write(&pci_bus_sem);
+	return ret;
 }
 
 struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
@@ -2577,7 +2587,10 @@  struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
 	if (!dev)
 		return NULL;
 
-	pci_device_add(dev, bus);
+	if (pci_device_add(dev, bus)) {
+		pci_dev_put(dev);
+		return NULL;
+	}
 
 	return dev;
 }
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 2bda4a4e47e8..3292261ea9c8 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -1124,7 +1124,7 @@  static inline void pci_dev_assign_slot(struct pci_dev *dev) { }
 #endif
 int pci_scan_slot(struct pci_bus *bus, int devfn);
 struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn);
-void pci_device_add(struct pci_dev *dev, struct pci_bus *bus);
+int pci_device_add(struct pci_dev *dev, struct pci_bus *bus);
 unsigned int pci_scan_child_bus(struct pci_bus *bus);
 void pci_bus_add_device(struct pci_dev *dev);
 void pci_read_bridge_bases(struct pci_bus *child);