From 3268fe11e13cf9b102106ddb4b416a0cd3340e93 Mon Sep 17 00:00:00 2001 From: Gregory Lirent Date: Thu, 11 Jun 2026 22:35:47 +0300 Subject: [PATCH] spoof: class-bound nvme PCI vendor:device (extend 0018) Override the qemu-nvme controller vendor:device (Red Hat 1b36:0010) to a real NVMe vendor matched to the spoofed model brand (Samsung/WD/Kioxia/Kingston/Intel/SMI/ hynix/Micron/Phison), with a coherent subsystem. Safe: NVMe binds by class code, not id, and this runs before nvme_init_ctrl so the IDENTIFY vid/ssvid stay aligned. virtio/GPU vendor:device untouched (load-bearing). Inert without a seed. Co-Authored-By: Claude Opus 4.8 (1M context) --- patches/0018-pci-subsystem-id.patch | 43 ++++++++++++++++++++--------- patches/README.md | 23 ++++++++------- src/spoof-storage.c | 26 +++++++++++++++++ src/spoof.h | 2 ++ 4 files changed, 69 insertions(+), 25 deletions(-) diff --git a/patches/0018-pci-subsystem-id.patch b/patches/0018-pci-subsystem-id.patch index 45c2c73..161558d 100644 --- a/patches/0018-pci-subsystem-id.patch +++ b/patches/0018-pci-subsystem-id.patch @@ -1,17 +1,17 @@ -qemu-spoof: OEM-brand emulated PCI subsystem ids (realize-time) +qemu-spoof: OEM-brand emulated PCI subsystem ids + class-bound nvme vendor:device -QEMU stamps a constant subsystem id (default 1b36:1100) on every emulated PCI -device -- a fleet fingerprint. The subsystem id is not used for guest driver -binding, so override it at realize time with an OEM-branded SVID/SSID (chipset -vendor + per-role device id) for non-bridge devices. Red Hat/virtio (0x1af4) is -skipped: legacy-virtio encodes the device type in the subsystem id, so changing -it would break the guest driver (virtio is de-fingerprinted by device choice -instead). vfio passthrough overwrites this with the real hardware SSID afterwards. -This is the realize-time override path the deferred 0018 note called for; it does -NOT touch vendor/device ids (those ARE driver-binding and would break drivers). -Inert unless a spoof-seed is set. +Realize-time overrides in do_pci_register_device (inert without a spoof-seed): +1. Subsystem id (SVID/SSID) of every non-bridge emulated device -> an OEM-branded + value. QEMU stamps a constant 1b36:1100 on all of them (a fleet fingerprint); the + subsystem id is not used for driver binding so this is safe. Red Hat/virtio + (0x1af4) is skipped -- legacy-virtio encodes the device type in the subsystem id. +2. The qemu-nvme controller (1b36:0010) vendor:device -> the real vendor:device of + the spoofed NVMe model brand (Samsung/WD/Kioxia/...), with a matching subsystem. + Safe because NVMe binds by class code, not id; runs before nvme_init_ctrl so the + IDENTIFY vid/ssvid stay coherent. virtio/GPU vendor:device are NOT touched (their + id IS the driver-binding contract). vfio passthrough overwrites with real hw ids. diff --git a/hw/pci/pci.c b/hw/pci/pci.c -index 2c3657d..e3da2ab 100644 +index 2c3657d..c6eb0ef 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -23,6 +23,7 @@ @@ -22,7 +22,7 @@ index 2c3657d..e3da2ab 100644 #include "qemu/datadir.h" #include "qemu/units.h" #include "hw/core/irq.h" -@@ -1417,6 +1418,27 @@ static PCIDevice *do_pci_register_device(PCIDevice *pci_dev, +@@ -1417,6 +1418,44 @@ static PCIDevice *do_pci_register_device(PCIDevice *pci_dev, assert(!pc->subsystem_vendor_id); assert(!pc->subsystem_id); } @@ -46,6 +46,23 @@ index 2c3657d..e3da2ab 100644 + pci_set_word(pci_dev->config + PCI_SUBSYSTEM_ID, + spoof_pci_subdevice(role, + pci_get_word(pci_dev->config + PCI_SUBSYSTEM_ID))); ++ } ++ /* ++ * qemu-spoof: class-bound device ids may be set to a REAL vendor:device safely, ++ * because the guest binds these by class code, not by id. qemu-nvme (Red Hat ++ * 1b36:0010) -> the vendor:device of the spoofed NVMe model's brand, with a ++ * matching subsystem (a real NVMe controller's SSVID equals its VID). This runs ++ * before nvme_init_ctrl, so the NVMe IDENTIFY vid/ssvid stay coherent. virtio / ++ * GPU ids are NOT touched here (their id IS the driver-binding contract). ++ */ ++ if (spoof_enabled() && ++ !strcmp(object_get_typename(OBJECT(pci_dev)), "nvme")) { ++ uint16_t vid = spoof_nvme_pci_vendor(pci_get_word(pci_dev->config + PCI_VENDOR_ID)); ++ uint16_t did = spoof_nvme_pci_device(pci_get_word(pci_dev->config + PCI_DEVICE_ID)); ++ pci_config_set_vendor_id(pci_dev->config, vid); ++ pci_config_set_device_id(pci_dev->config, did); ++ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_VENDOR_ID, vid); ++ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_ID, did); + } pci_init_cmask(pci_dev); pci_init_wmask(pci_dev); diff --git a/patches/README.md b/patches/README.md index 3e7c7c1..50e8344 100644 --- a/patches/README.md +++ b/patches/README.md @@ -20,30 +20,29 @@ match exactly. Naming: `0002` = infra, `0010+` = one aspect each. | 0015-smbios-vm-bit | clear the SMBIOS type0 "VM" characteristic bit | | 0016-edid-monitor | EDID vendor / name / model / serial / manufacture-date | | 0017-storage-identity | IDE model / serial / fw / cdrom + NVMe model / serial / fw | -| 0018-pci-subsystem-id | realize-time OEM-brand of emulated PCI subsystem ids (SVID/SSID) | +| 0018-pci-subsystem-id | realize-time OEM-brand of PCI subsystem ids + class-bound nvme vendor:device | | 0019-machine-desc | machine `desc` string → platform board model | | 0021-smbios-identity | SMBIOS type3 chassis / type4 socket + cpu-mfr / type11 OEM / type17 memory | | 0022-storage-extra | IDE WWN + rotation rate; NVMe EUI-64 + NGUID | | 0023-cpu-microcode | CPU microcode revision (IA32_UCODE_REV), vendor-positioned | | 0024-smbios-bios-type0 | SMBIOS type0 BIOS vendor / version / date (platform-anchored OEM firmware) | -## Why 0018 is subsystem-only (the PCI-id problem) +## The PCI-id problem (why 0018 is careful) The PCI **vendor:device** id IS the guest driver-binding contract: NetKVM binds `1af4:1000`, the ivshmem driver binds `1af4:1110`, the GPU driver binds its id. -Spoofing those breaks the device (no driver matches). So `0018` only rewrites the -**subsystem** id (not used for binding) — and even that skips `0x1af4`, because -*legacy*-virtio encodes the device type in the subsystem id. Class-bound devices -(NVMe / xHCI / AHCI bind by class code, not id) *could* take a real vendor:device, -but that needs a per-role allowlist. virtio / ivshmem / GPU ids are instead -de-fingerprinted by **device choice** (`e1000e` / SATA) and vfio `x-pci-*`, not by -id spoofing. +Spoofing those breaks the device (no driver matches). So `0018` rewrites the +**subsystem** id of all emulated devices (not used for binding; skips `0x1af4` +because legacy-virtio encodes the device type there), and overrides the full +**vendor:device** ONLY for class-bound controllers that bind by class code rather +than id — currently **qemu-nvme** → a real NVMe vendor:device matched to the spoofed +model brand. virtio / ivshmem / GPU ids are NOT touched (their id is load-bearing); +they are de-fingerprinted by **device choice** (`e1000e` / SATA) and vfio `x-pci-*`. ## Out of scope (handled by config or upstream, not these patches) -- **PCI vendor:device id** for class-bound devices (qemu-nvme `1b36:0010` → a real - NVMe vendor; `nec-usb-xhci`): safe in principle, would need a per-role allowlist in - the `0018` realize hook. +- **xHCI vendor:device**: also class-bound; prefer the `nec-usb-xhci` device (real + NEC id) via config over spoofing qemu-xhci. (qemu-nvme is handled by `0018`.) - **HDA codec id** (`QEMU_HDA_ID_VENDOR 0x1af4`): a compile-time constant baked into a `static const` codec descriptor; spoofing needs a runtime override of the GET_PARAMETER verb response. diff --git a/src/spoof-storage.c b/src/spoof-storage.c index b92e4bb..f152135 100644 --- a/src/spoof-storage.c +++ b/src/spoof-storage.c @@ -79,6 +79,20 @@ static uint64_t brand_oui(const char *m) if (!strncmp(m, "CT", 2) || !strncmp(m, "Crucial", 7)) return 0x00A075; /* Micron */ return 0x0024E9; } +/* Real NVMe controller PCI vendor:device, matched to the spoofed model's brand + * (the guest binds NVMe by class code, so this is safe to set on the QEMU ctrl). */ +static void nvme_pci_id(const char *m, uint16_t *vid, uint16_t *did) +{ + if (!strncmp(m, "Samsung", 7)) { *vid = 0x144d; *did = 0xa80a; } /* Samsung */ + else if (!strncmp(m, "WD", 2)) { *vid = 0x15b7; *did = 0x5017; } /* WD/SanDisk */ + else if (strstr(m, "TOSHIBA") || !strncmp(m, "KXG", 3)) { *vid = 0x1e0f; *did = 0x0001; } /* Kioxia */ + else if (!strncmp(m, "KINGSTON", 8)){ *vid = 0x2646; *did = 0x5013; } /* Kingston */ + else if (!strncmp(m, "INTEL", 5)) { *vid = 0x8086; *did = 0x390b; } /* Intel */ + else if (!strncmp(m, "ADATA", 5)) { *vid = 0x126f; *did = 0x2263; } /* Silicon Motion */ + else if (strstr(m, "Hynix")) { *vid = 0x1c5c; *did = 0x1d59; } /* SK hynix */ + else if (!strncmp(m, "CT", 2) || !strncmp(m, "Micron", 6)) { *vid = 0x1344; *did = 0x5407; } /* Micron */ + else { *vid = 0x1987; *did = 0x5012; } /* Phison */ +} static struct { const char *model; char serial[24], fw[12]; uint64_t wwn; int rpm; @@ -119,6 +133,18 @@ const char *spoof_nvme_model(const char *def) { if (!spoof_on()) return def; v_ const char *spoof_nvme_serial(const char *def) { if (!spoof_on()) return def; v_init(); return v.serial; } const char *spoof_nvme_fw(const char *def) { if (!spoof_on()) return def; v_init(); return v.fw; } uint64_t spoof_nvme_eui64(uint64_t def) { if (!spoof_on()) return def; v_init(); return v.eui64; } +uint16_t spoof_nvme_pci_vendor(uint16_t def) +{ + if (!spoof_on()) return def; + v_init(); + uint16_t vid, did; nvme_pci_id(v.model, &vid, &did); return vid; +} +uint16_t spoof_nvme_pci_device(uint16_t def) +{ + if (!spoof_on()) return def; + v_init(); + uint16_t vid, did; nvme_pci_id(v.model, &vid, &did); return did; +} void spoof_nvme_nguid(uint8_t out[16]) { diff --git a/src/spoof.h b/src/spoof.h index 1765711..6b18205 100644 --- a/src/spoof.h +++ b/src/spoof.h @@ -92,6 +92,8 @@ const char *spoof_nvme_serial(const char *def); const char *spoof_nvme_fw(const char *def); /* 8-char rev (fixes FR leak) */ uint64_t spoof_nvme_eui64(uint64_t def); /* OUI + vendor */ void spoof_nvme_nguid(uint8_t out[16]); /* filled iff enabled */ +uint16_t spoof_nvme_pci_vendor(uint16_t def); /* class-bound: real NVMe vendor */ +uint16_t spoof_nvme_pci_device(uint16_t def); /* brand-matched to the model */ const char *spoof_cdrom_model(const char *def); int spoof_disk_rotation(int def); /* 1=SSD, else RPM (matches model) */