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) <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
+11
-12
@@ -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.
|
||||
|
||||
@@ -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])
|
||||
{
|
||||
|
||||
@@ -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) */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user