Skip to content

Commit 9d573d1

Browse files
l1kbjorn-helgaas
authored andcommitted
PCI: pciehp: Detect device replacement during system sleep
Ricky reports that replacing a device in a hotplug slot during ACPI sleep state S3 does not cause re-enumeration on resume, as one would expect. Instead, the new device is treated as if it was the old one. There is no bulletproof way to detect device replacement, but as a heuristic, check whether the device identity in config space matches cached data in struct pci_dev (Vendor ID, Device ID, Class Code, Revision ID, Subsystem Vendor ID, Subsystem ID). Additionally, cache and compare the Device Serial Number (PCIe r6.2 sec 7.9.3). If a mismatch is detected, mark the old device disconnected (to prevent its driver from accessing the new device) and synthesize a Presence Detect Changed event. The device identity in config space which is compared here is the same as the one included in the signed Subject Alternative Name per PCIe r6.1 sec 6.31.3. Thus, the present commit prevents attacks where a valid device is replaced with a malicious device during system sleep and the valid device's driver obliviously accesses the malicious device. This is about as much as can be done at the PCI layer. Drivers may have additional ways to identify devices (such as reading a WWID from some register) and may trigger re-enumeration when detecting an identity change on resume. Link: https://lore.kernel.org/r/a1afaa12f341d146ecbea27c1743661c71683833.1716992815.git.lukas@wunner.de Reported-by: Ricky Wu <[email protected]> Closes: https://lore.kernel.org/r/[email protected] Tested-by: Ricky Wu <[email protected]> Signed-off-by: Lukas Wunner <[email protected]> Signed-off-by: Bjorn Helgaas <[email protected]>
1 parent 1613e60 commit 9d573d1

File tree

4 files changed

+54
-1
lines changed

4 files changed

+54
-1
lines changed

drivers/pci/hotplug/pciehp.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ extern int pciehp_poll_time;
4646
/**
4747
* struct controller - PCIe hotplug controller
4848
* @pcie: pointer to the controller's PCIe port service device
49+
* @dsn: cached copy of Device Serial Number of Function 0 in the hotplug slot
50+
* (PCIe r6.2 sec 7.9.3); used to determine whether a hotplugged device
51+
* was replaced with a different one during system sleep
4952
* @slot_cap: cached copy of the Slot Capabilities register
5053
* @inband_presence_disabled: In-Band Presence Detect Disable supported by
5154
* controller and disabled per spec recommendation (PCIe r5.0, appendix I
@@ -87,6 +90,7 @@ extern int pciehp_poll_time;
8790
*/
8891
struct controller {
8992
struct pcie_device *pcie;
93+
u64 dsn;
9094

9195
u32 slot_cap; /* capabilities and quirks */
9296
unsigned int inband_presence_disabled:1;

drivers/pci/hotplug/pciehp_core.c

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,32 @@ static int pciehp_suspend(struct pcie_device *dev)
284284
return 0;
285285
}
286286

287+
static bool pciehp_device_replaced(struct controller *ctrl)
288+
{
289+
struct pci_dev *pdev __free(pci_dev_put);
290+
u32 reg;
291+
292+
pdev = pci_get_slot(ctrl->pcie->port->subordinate, PCI_DEVFN(0, 0));
293+
if (!pdev)
294+
return true;
295+
296+
if (pci_read_config_dword(pdev, PCI_VENDOR_ID, &reg) ||
297+
reg != (pdev->vendor | (pdev->device << 16)) ||
298+
pci_read_config_dword(pdev, PCI_CLASS_REVISION, &reg) ||
299+
reg != (pdev->revision | (pdev->class << 8)))
300+
return true;
301+
302+
if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL &&
303+
(pci_read_config_dword(pdev, PCI_SUBSYSTEM_VENDOR_ID, &reg) ||
304+
reg != (pdev->subsystem_vendor | (pdev->subsystem_device << 16))))
305+
return true;
306+
307+
if (pci_get_dsn(pdev) != ctrl->dsn)
308+
return true;
309+
310+
return false;
311+
}
312+
287313
static int pciehp_resume_noirq(struct pcie_device *dev)
288314
{
289315
struct controller *ctrl = get_service_data(dev);
@@ -293,9 +319,23 @@ static int pciehp_resume_noirq(struct pcie_device *dev)
293319
ctrl->cmd_busy = true;
294320

295321
/* clear spurious events from rediscovery of inserted card */
296-
if (ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE)
322+
if (ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE) {
297323
pcie_clear_hotplug_events(ctrl);
298324

325+
/*
326+
* If hotplugged device was replaced with a different one
327+
* during system sleep, mark the old device disconnected
328+
* (to prevent its driver from accessing the new device)
329+
* and synthesize a Presence Detect Changed event.
330+
*/
331+
if (pciehp_device_replaced(ctrl)) {
332+
ctrl_dbg(ctrl, "device replaced during system sleep\n");
333+
pci_walk_bus(ctrl->pcie->port->subordinate,
334+
pci_dev_set_disconnected, NULL);
335+
pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
336+
}
337+
}
338+
299339
return 0;
300340
}
301341
#endif

drivers/pci/hotplug/pciehp_hpc.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,11 @@ struct controller *pcie_init(struct pcie_device *dev)
10551055
}
10561056
}
10571057

1058+
pdev = pci_get_slot(subordinate, PCI_DEVFN(0, 0));
1059+
if (pdev)
1060+
ctrl->dsn = pci_get_dsn(pdev);
1061+
pci_dev_put(pdev);
1062+
10581063
return ctrl;
10591064
}
10601065

drivers/pci/hotplug/pciehp_pci.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ int pciehp_configure_device(struct controller *ctrl)
7272
pci_bus_add_devices(parent);
7373
down_read_nested(&ctrl->reset_lock, ctrl->depth);
7474

75+
dev = pci_get_slot(parent, PCI_DEVFN(0, 0));
76+
ctrl->dsn = pci_get_dsn(dev);
77+
pci_dev_put(dev);
78+
7579
out:
7680
pci_unlock_rescan_remove();
7781
return ret;

0 commit comments

Comments
 (0)