The PCIe Device Serial Number (DSN) Extended Capability reports a unique, persistent hardware serial number (PCIe r6.2 sec 7.9.3). The value identifies the physical component: per the spec the functions of a Multi-Function Device that implement the capability report the same DSN, so a single value can fingerprint a whole physical device. Root Complex integrated Endpoints (RCiEPs) may implement the capability independently and are not required to share their value with other RCiEPs of the same Root Complex, but each such value is still a stable, host-specific identifier.
vfio-pci has no perm_bits entry for the DSN capability, so guest reads of the serial number fall through to the physical device. This leaks a stable hardware identifier into the guest that can be used to fingerprint the host device and correlate it across VMs, which is undesirable for multi-tenant passthrough. Add a perm_bits initializer that fully virtualizes the DSN capability: the header dword and both serial-number dwords (PCI_DSN_CAP, PCI_DSN_LOW_DW, PCI_DSN_HIGH_DW) are marked ALL_VIRT/NO_WRITE so all reads are served from the virtualized config space rather than hardware and writes are denied (the DSN is read-only state). Scrub the serial in vconfig during vfio_ecap_init() so guests read a zeroed serial while the capability remains visible. Add the DSN register-offset defines to pci_regs.h. The capability length used by alloc_perm_bits() comes from the existing pci_ext_cap_length[PCI_EXT_CAP_ID_DSN] = PCI_EXT_CAP_DSN_SIZEOF (12), which covers the 4-byte header plus the 8-byte serial. SR-IOV: per the SR-IOV spec a PF's DSN applies to all of its VFs, and a VF that implements the DSN capability must report the same value as its PF. vfio-pci exposes only the assigned device's own config space to the guest, and neither the vconfig fill nor the vfio_ecap_init() walk applies any VF-specific transform to the DSN bytes, so the DSN capability is virtualized and scrubbed for a VF exactly as for a PF. Therefore a VF that implements DSN (whose value is the PF's host serial) is scrubbed, and a VF that omits the capability (the spec-recommended case) exposes no serial to scrub. Variant drivers (mlx5-vfio-pci, hisi-acc-vfio-pci, nvgrace-gpu-vfio-pci, virtio-vfio-pci, etc.) build on vfio-pci-core and share this config-space path and perm_bits table, so the scrub applies to them as well. The scrub is the default because a guest cannot rely on a meaningful DSN through vfio-pci in general (it identifies the physical device, which varies across assignment and migration). The following patch lets the VMM present a chosen serial where a stable identity is required. Signed-off-by: Pranjal Arya <[email protected]> --- drivers/vfio/pci/vfio_pci_config.c | 39 ++++++++++++++++++++++++++++++++++++++ include/uapi/linux/pci_regs.h | 5 +++++ 2 files changed, 44 insertions(+) diff --git a/drivers/vfio/pci/vfio_pci_config.c b/drivers/vfio/pci/vfio_pci_config.c index a10ed733f0e3..24dfeb43cb71 100644 --- a/drivers/vfio/pci/vfio_pci_config.c +++ b/drivers/vfio/pci/vfio_pci_config.c @@ -1085,6 +1085,31 @@ static int __init init_pci_ext_cap_pwr_perm(struct perm_bits *perm) return 0; } +/* + * The Device Serial Number is a unique, persistent, per-device identifier. + * Passing the physical serial number through to a guest leaks an identifier + * that can be used to fingerprint and correlate the host device across VMs + * and tenants. Virtualize the whole capability so reads come from vconfig + * (which is scrubbed during init, see vfio_ecap_init()) instead of hardware, + * and disallow writes (the DSN is read-only hardware state anyway). + */ +static int __init init_pci_ext_cap_dsn_perm(struct perm_bits *perm) +{ + if (alloc_perm_bits(perm, pci_ext_cap_length[PCI_EXT_CAP_ID_DSN])) + return -ENOMEM; + + /* + * Virtualize the whole capability: the header (offset 0) plus the + * two serial-number dwords (offsets 4 and 8). All reads are then + * served from vconfig (scrubbed in vfio_ecap_init()) rather than + * hardware, and writes are denied since the DSN is read-only state. + */ + p_setd(perm, 0, ALL_VIRT, NO_WRITE); + p_setd(perm, 4, ALL_VIRT, NO_WRITE); + p_setd(perm, 8, ALL_VIRT, NO_WRITE); + return 0; +} + /* * Initialize the shared permission tables */ @@ -1100,6 +1125,7 @@ void vfio_pci_uninit_perm_bits(void) free_perm_bits(&ecap_perms[PCI_EXT_CAP_ID_ERR]); free_perm_bits(&ecap_perms[PCI_EXT_CAP_ID_PWR]); + free_perm_bits(&ecap_perms[PCI_EXT_CAP_ID_DSN]); } int __init vfio_pci_init_perm_bits(void) @@ -1120,6 +1146,7 @@ int __init vfio_pci_init_perm_bits(void) /* Extended capabilities */ ret |= init_pci_ext_cap_err_perm(&ecap_perms[PCI_EXT_CAP_ID_ERR]); ret |= init_pci_ext_cap_pwr_perm(&ecap_perms[PCI_EXT_CAP_ID_PWR]); + ret |= init_pci_ext_cap_dsn_perm(&ecap_perms[PCI_EXT_CAP_ID_DSN]); ecap_perms[PCI_EXT_CAP_ID_VNDR].writefn = vfio_raw_config_write; ecap_perms[PCI_EXT_CAP_ID_DVSEC].writefn = vfio_raw_config_write; @@ -1702,6 +1729,18 @@ static int vfio_ecap_init(struct vfio_pci_core_device *vdev) if (ret) return ret; + /* + * Scrub the physical Device Serial Number from the + * virtualized config space so the guest cannot read the + * host device's unique identifier. The capability is fully + * virtualized (see init_pci_ext_cap_dsn_perm()), so reads + * return this scrubbed value rather than hardware. The user + * can present a chosen serial via VFIO_DEVICE_FEATURE_PCI_DSN. + */ + if (ecap == PCI_EXT_CAP_ID_DSN) + memset(&vdev->vconfig[epos + PCI_DSN_LOW_DW], 0, + sizeof(__le64)); + /* * If we're just using this capability to anchor the list, * hide the real ID. Only count real ecaps. XXX PCI spec diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index facaa324bd86..bd0ae9decc00 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -768,6 +768,11 @@ #define PCI_EXT_CAP_DSN_SIZEOF 12 #define PCI_EXT_CAP_MCAST_ENDPOINT_SIZEOF 40 +/* Device Serial Number */ +#define PCI_DSN_CAP 0x00 /* Capability header */ +#define PCI_DSN_LOW_DW 0x04 /* Serial number, lower dword */ +#define PCI_DSN_HIGH_DW 0x08 /* Serial number, upper dword */ + /* Advanced Error Reporting */ #define PCI_ERR_UNCOR_STATUS 0x04 /* Uncorrectable Error Status */ #define PCI_ERR_UNC_UND 0x00000001 /* Undefined */ -- 2.34.1

