PCI: Fix NULL dereference in SR-IOV VF creation error path
authorShay Drory <shayd@nvidia.com>
Mon, 10 Mar 2025 08:45:24 +0000 (10:45 +0200)
committerBjorn Helgaas <bhelgaas@google.com>
Fri, 21 Mar 2025 19:54:16 +0000 (14:54 -0500)
Clean up when virtfn setup fails to prevent NULL pointer dereference
during device removal. The kernel oops below occurred due to incorrect
error handling flow when pci_setup_device() fails.

Add pci_iov_scan_device(), which handles virtfn allocation and setup and
cleans up if pci_setup_device() fails, so pci_iov_add_virtfn() doesn't need
to call pci_stop_and_remove_bus_device().  This prevents accessing
partially initialized virtfn devices during removal.

  BUG: kernel NULL pointer dereference, address: 00000000000000d0
  RIP: 0010:device_del+0x3d/0x3d0
  Call Trace:
   pci_remove_bus_device+0x7c/0x100
   pci_iov_add_virtfn+0xfa/0x200
   sriov_enable+0x208/0x420
   mlx5_core_sriov_configure+0x6a/0x160 [mlx5_core]
   sriov_numvfs_store+0xae/0x1a0

Link: https://lore.kernel.org/r/20250310084524.599225-1-shayd@nvidia.com
Fixes: e3f30d563a38 ("PCI: Make pci_destroy_dev() concurrent safe")
Signed-off-by: Shay Drory <shayd@nvidia.com>
[bhelgaas: commit log, return ERR_PTR(-ENOMEM) directly]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Cc: Keith Busch <kbusch@kernel.org>
drivers/pci/iov.c

index 121540f57d4bff562effb98dab8f75e7e63e5228..10693b5d7eb66bbbfb9b70ffe6e89eb89c8dc3a3 100644 (file)
@@ -285,23 +285,16 @@ const struct attribute_group sriov_vf_dev_attr_group = {
        .is_visible = sriov_vf_attrs_are_visible,
 };
 
-int pci_iov_add_virtfn(struct pci_dev *dev, int id)
+static struct pci_dev *pci_iov_scan_device(struct pci_dev *dev, int id,
+                                          struct pci_bus *bus)
 {
-       int i;
-       int rc = -ENOMEM;
-       u64 size;
-       struct pci_dev *virtfn;
-       struct resource *res;
        struct pci_sriov *iov = dev->sriov;
-       struct pci_bus *bus;
-
-       bus = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id));
-       if (!bus)
-               goto failed;
+       struct pci_dev *virtfn;
+       int rc;
 
        virtfn = pci_alloc_dev(bus);
        if (!virtfn)
-               goto failed0;
+               return ERR_PTR(-ENOMEM);
 
        virtfn->devfn = pci_iov_virtfn_devfn(dev, id);
        virtfn->vendor = dev->vendor;
@@ -314,8 +307,35 @@ int pci_iov_add_virtfn(struct pci_dev *dev, int id)
                pci_read_vf_config_common(virtfn);
 
        rc = pci_setup_device(virtfn);
-       if (rc)
-               goto failed1;
+       if (rc) {
+               pci_dev_put(dev);
+               pci_bus_put(virtfn->bus);
+               kfree(virtfn);
+               return ERR_PTR(rc);
+       }
+
+       return virtfn;
+}
+
+int pci_iov_add_virtfn(struct pci_dev *dev, int id)
+{
+       struct pci_bus *bus;
+       struct pci_dev *virtfn;
+       struct resource *res;
+       int rc, i;
+       u64 size;
+
+       bus = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id));
+       if (!bus) {
+               rc = -ENOMEM;
+               goto failed;
+       }
+
+       virtfn = pci_iov_scan_device(dev, id, bus);
+       if (IS_ERR(virtfn)) {
+               rc = PTR_ERR(virtfn);
+               goto failed0;
+       }
 
        virtfn->dev.parent = dev->dev.parent;
        virtfn->multifunction = 0;