[PATCH 1/2] PCI: Bail out of pci_read_bridge_bases() for SR-IOV virtual buses

From: Yuguo Li

Date: Wed Jun 10 2026 - 10:08:07 EST


pci_iov_add_virtfn() routes through virtfn_add_bus(), which calls
pci_add_new_bus(parent, NULL, busnr) whenever a VF lands on a bus
number different from its PF. This produces a pci_bus with a valid
parent but no bridge device (bus->self == NULL). There is no bridge
to read, and the SR-IOV add path never invokes pci_scan_child_bus_*(),
so bus->is_added stays 0 on these buses.

That stays harmless until something invokes pci_rescan_bus() on a
virtual bus, e.g. via the per-device sysfs entry:

echo 1 > /sys/bus/pci/devices/<VF>/rescan

On x86, pci_scan_child_bus_extend() then sees !bus->is_added and
calls pcibios_fixup_bus(), which unconditionally calls
pci_read_bridge_bases(). The function only guards against root
buses, so it dereferences child->self and oopses. The fault address
is the offset of pci_dev->transparent, reached via the dev->transparent
test in the pci_info() call.

Reproduced on mainline 7.1.0-rc7+ on x86_64 with an SR-IOV-capable PF
whose VFs span multiple bus numbers, by writing "1" to a VF's sysfs
rescan attribute (LTP's tpci test does this implicitly via
pci_rescan_bus()):

BUG: kernel NULL pointer dereference, address: 0000000000000860
#PF: supervisor read access in kernel mode
Oops: Oops: 0000 [#1] SMP NOPTI
CPU: 12 ... 7.1.0-rc7+ ... PREEMPTLAZY
RIP: 0010:pci_read_bridge_bases+0x39/0x120
RBX: 0000000000000000 (= bus->self)
Call Trace:
<TASK>
pcibios_fixup_bus+0xe/0xd0
pci_scan_child_bus_extend+0x6b/0x2e0
pci_rescan_bus+0x11/0x30
... (sysfs write to .../rescan)
do_syscall_64+0xab/0x500

With this patch applied to the same tree, the trigger sequence
completes without crashing.

Triggering this only requires:

- x86_64 (or any arch whose pcibios_fixup_bus() calls
pci_read_bridge_bases()),
- any SR-IOV-capable PF (e.g. mlx5, i40e, ixgbe, igb) where
sriov_numvfs is large enough that at least one VF crosses to a
new bus number, and
- a write to that VF's sysfs rescan attribute.

The same NULL self pattern on SR-IOV virtual buses was already
addressed for the MSI IRQ domain path in commit 38ea72bdb65d
("PCI/MSI: Fix MSI IRQ domains for VFs on virtual buses"), but
pci_read_bridge_bases() was not updated in step. The hidden
assumption that non-root buses always have a bridge dates back to
commit f92d4e29d785 ("PCI: fix wrong assumption in
pci_read_bridge_bases"), which tightened the entry guard from
"if (!dev)" to "if (!child->parent)" to handle root buses that do
have a self, but inadvertently exposed the "non-root + self == NULL"
SR-IOV virtual bus case.

Add an explicit early return for bus->self == NULL. There are no
bridge windows or transparent decode flags to propagate when no
bridge device exists, so returning early is semantically correct and
matches the existing pci_is_root_bus() bail-out.

Fixes: f92d4e29d785 ("PCI: fix wrong assumption in pci_read_bridge_bases")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yuguo Li <hugoolli@xxxxxxxxxxx>
---
drivers/pci/probe.c | 10 ++++++++++
1 file changed, 10 insertions(+)

diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b63cd0c310bc..6bcc3b58031b 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -577,6 +577,16 @@ void pci_read_bridge_bases(struct pci_bus *child)
if (pci_is_root_bus(child)) /* It's a host bus, nothing to read */
return;

+ /*
+ * SR-IOV virtual buses are created by virtfn_add_bus() via
+ * pci_add_new_bus(parent, NULL, busnr) when a VF lands on a bus
+ * number different from its PF. Such buses have a valid parent
+ * but no bridge device (->self == NULL), so there are no bridge
+ * windows to read. Bail out before dereferencing @dev.
+ */
+ if (!dev)
+ return;
+
pci_info(dev, "PCI bridge to %pR%s\n",
&child->busn_res,
dev->transparent ? " (subtractive decode)" : "");
--
2.43.7