diff --git a/docs/ANALYSIS.md b/docs/ANALYSIS.md index d876cbd..fdc2196 100644 --- a/docs/ANALYSIS.md +++ b/docs/ANALYSIS.md @@ -5,7 +5,7 @@ says *why*, with the source references. Written 2026-09-03 at 2/85, revised 2026 on both VMAware v2.8.1 and VMAware HEAD (commit 95fecc2) - the one survivor is `GPU_CAPABILITIES`, and VMAware's own verdict is "Running on bare metal", likeliness 20%, confirmation false. -Source read against: VMAware `src/vmaware.hpp`, QEMU 11.1.1, kernel 7.2.3 `arch/x86/kvm`. +Source read against: VMAware `src/vmaware.hpp`, QEMU 11.1.2, kernel 7.2.3 `arch/x86/kvm`. --- @@ -125,6 +125,98 @@ WAET and never did: `build_waet()` has zero call sites, so its `grep -q build_wa satisfied by the definition alone and the insert never ran. Guard on a call site, not on a definition. +**The chain grew.** `firmware()` returns on its first hit, so clearing one link only exposes the +next, and current VMAware has added links past the eight above. `09-firmware-chain` clears the +rest, measured to 0 detections on both v2.8.1 and HEAD: + +| Link | Fix | +| --- | --- | +| IQST/IQCR + PRR0/PRRI interrupt-query methods | renamed PSTA/PCRS/PRS0/PRSI | +| TPM PPI regions/method TPP2/TPP3/TPFN | renamed PPI2/PPI3/PPIF | +| zeroed `_S5` SLP_TYP package | 2-element package form | +| `ACPI0006` `_HID` in `\_GPE` | declared by full path `\_GPE._HID`; dropping it stops macOS powering off | +| Q35 MMCONFIG reservation device `MCFG` | renamed `PCMM` | +| synthetic GSI link devices, empty `_DIS`/`_SRS` | gave the no-op methods a body | +| PCIe `_OSC` `And(CDW3, 0x1F, ..)` mask | swapped the `And()` operands (commutative) | +| PIRQ link `_PRS` fixed IRQ 5/10/11 | listed the same set as `{11, 10, 5}` | + +A q35 guest also needs a named USB controller in the DSDT (the ICH9 EHCI/UHCI set, declared `EHC1`/ +`UHC1`), or the release flags "complete absence of USB controller declarations"; that is a domain +choice, not a QEMU patch. + +### CLEARED - the CPU-hotplug I/O signature that macOS forced back in (`11-macos-cpu-hotplug`) + +macOS references `\_SB.CPUS.Cxxx` from its own CpuPlug SSDT, so the guest must carry QEMU's +`build_cpus_aml` output (the `\_SB.PCI0.PRES` controller plus the `\_SB.CPUS` `Processor` objects) +or it hangs in ACPI namespace init. `01-firmware` had dropped that whole block, which is why macOS +lived in an opt-in patch: the block reintroduces exactly one FIRMWARE tell, so a macOS-capable build +scored 2/85 on v2.8.1 while HEAD stayed clean. + +The tell is a single check. v2.8.1 `firmware()` carries `cpu_hotplug_io[] = {0x0B, 0xD8, 0x0C}` and +byte-matches it anywhere in the tables. Those three bytes are `WordData 0x0CD8` - the CPU-hotplug +register port that `build_cpus_aml` writes into the `PRST` `OperationRegion` base +(`ICH9_CPU_HOTPLUG_IO_BASE`). Confirmed by dumping the macOS-build DSDT: the bytes appear only at the +`OperationRegion` operand, never in the `_CRS`. A small-resource `IO` descriptor encodes `0x0CD8` as +`0x47 0x01 0xD8 0x0C 0xD8 0x0C 0x01 0x0C` - the `0xD8 0x0C` runs are preceded by `0x01`/`0x0C`, never +`0x0B` - so the `_CRS` is not what matches and does not need touching. The renamed `_UID` ("Reserved +resources C") already dodges the separate `"CPU Hotplug resources"` string check; it was never the +live tell. HEAD has no `cpu_hotplug_io` check, so it never flagged the block. + +**Relocating the base does not work.** The obvious fix - move `ICH9_CPU_HOTPLUG_IO_BASE` off `0x0CD8` +so the pattern changes - clears the scanner but hangs every SMM/OVMF guest: macOS spins ~10 cores at +boot and never reaches login, Windows the same. `OvmfPkg/Include/IndustryStandard/Q35MchIch9.h` +hardcodes `ICH9_CPU_HOTPLUG_BASE 0x0CD8` and OVMF's SMM code reads the register block at that fixed +port, independent of the AML. The non-SMM path (plain OVMF, a bare Linux boot) survives a move; the +SMM guests we actually run do not. A/B confirmed it: macOS reaches login on `0x0CD8`, spins on +`0x0CE4`. Patching OVMF is already on the rejected list. + +**The fix keeps the port and hides only the AML operand.** `11` emits the `OperationRegion` base for +a SystemIO region as `Add(0x0C00, 0xD8)` instead of the literal `0x0CD8`. It evaluates to the +identical `0x0CD8` at namespace load - the port QEMU decodes, the port in `_CRS`, and the port OVMF +reads are all unchanged - but the AML is `0x72 0x0B 0x00 0x0C 0x0A 0xD8 ..`, so the `0x0B 0xD8 0x0C` +run never appears. macOS's interpreter evaluates the computed base without complaint. + +Verified on QEMU 11.1.2 with this fix in the default series: the DSDT keeps `\_SB.CPUS`/`PRES`/ +`Processor(C000..)` and `0x0CD8`, with no `0x0B 0xD8 0x0C` anywhere (iasl-clean); macOS boots to +login and powers off via S5 in ~12 s; VMAware v2.8.1 and HEAD both report `detected=0` / `FIRMWARE=0` +on the stealth guest; Windows (secboot + SMM) and Linux boot unchanged. The opt-in split is gone. + +--- + +## CLEARED - CTYPE and SMBIOS_VM_BIT (the SMBIOS QEMU synthesises) + +On Linux, VMAware reads SMBIOS four ways: `DMIDECODE` and `DMI_SCAN` match VM vendor strings in +`dmidecode -t system` and seven `/sys/class/dmi/id` files, `SMBIOS_VM_BIT` tests bit 4 of type 0 +byte 19, and `CTYPE` fires on chassis type 1, Other (HEAD e1a9786, vmaware.hpp:8272, :8948, :9023, +:8234). On Windows, `FIRMWARE` also scans the raw tables (`RSMB`, :10369) for `pc-q35` and `Q35 +` +(:9726). The strings are domain identity. The bit, the chassis type and the default strings are +what QEMU emits by itself. + +QEMU synthesises every required structure the domain does not supply. Switching that off +(`smbios_defaults = false`) leaves only what the domain passes: either a complete real dump through +`-smbios file=`, or a table with no processor, memory or end-of-table structures. `10-smbios` keeps +it on and fixes what the synthesised structures leak: + +| Leak | Fix | +| --- | --- | +| type 3 chassis type hard-coded to 1 (Other) | defaults to 3 (Desktop), set with `-smbios type=3,chassis-type=N` | +| type 0 VM bit set whenever type 0 fields are given | `vm` defaults off; libvirt `` cannot pass `vm=off` | +| unset product and version strings `Standard PC (Q35 + ICH9, 2009)`, `pc-q35-` | `Unknown`, like the manufacturer from `04-fw-cfg` | + +Measured on a Linux q35 guest, the same score on VMAware v2.8.1 and HEAD: + +| SMBIOS from the domain | QEMU | Score | +| --- | --- | --- | +| complete real dump, 52 `file=` structures | defaults off | 0/85 | +| the same dump | defaults on | 0/85, `dmidecode` byte-identical | +| field arguments for types 0-4 with `vm=off` | defaults on, builders unpatched | 1/85, `CTYPE` | +| field arguments for types 0-4, no `vm=off`, `chassis-type` set | `10-smbios` | 0/85 | + +Identity is authored as field arguments. A dump through `-smbios file=` adds what field arguments +cannot express (memory module detail, OEM structures, battery) and mixes with them per type: a type +given as `file=` cannot also take field arguments. Windows and macOS guests are not yet measured +against this patch. + --- ## OPEN - GPU_CAPABILITIES (weight 20) diff --git a/packaging/vfio-native-qemu/.SRCINFO b/packaging/vfio-native-qemu/.SRCINFO index 7294296..89770a8 100644 --- a/packaging/vfio-native-qemu/.SRCINFO +++ b/packaging/vfio-native-qemu/.SRCINFO @@ -1,6 +1,6 @@ pkgbase = vfio-native-qemu - pkgdesc = QEMU 11.1.1 with the vfio-native platform-identity patches, in /opt/qemu-native - pkgver = 11.1.1 + pkgdesc = QEMU 11.1.2 with the vfio-native platform-identity patches, in /opt/qemu-native + pkgver = 11.1.2 pkgrel = 1 url = https://git.archworks.co/sandwich/vfio-native arch = x86_64 @@ -39,8 +39,8 @@ pkgbase = vfio-native-qemu optdepends = vfio-native: the setup tooling that points a domain at this build options = !strip source = git+https://git.archworks.co/sandwich/vfio-native.git#tag=v1.1.0 - source = https://download.qemu.org/qemu-11.1.1.tar.xz + source = https://download.qemu.org/qemu-11.1.2.tar.xz sha256sums = SKIP - sha256sums = 079ffbff8a7111bbc89022107cbabf3bbfd614d5fc9d7cc675991196aca12482 + sha256sums = 731b5681e4bb18be313231579b8efd0296c5b015fa36dc533874b639ba838016 pkgname = vfio-native-qemu diff --git a/packaging/vfio-native-qemu/PKGBUILD b/packaging/vfio-native-qemu/PKGBUILD index 73deb4d..2a886b3 100644 --- a/packaging/vfio-native-qemu/PKGBUILD +++ b/packaging/vfio-native-qemu/PKGBUILD @@ -1,12 +1,12 @@ # Maintainer: Sandwich # -# QEMU 11.1.1 with the platform-identity patches from vfio-native, installed +# QEMU 11.1.2 with the platform-identity patches from vfio-native, installed # under /opt/qemu-native so the system qemu package is never touched. Clears # FIRMWARE, VIRTUAL_DISK, DISK_SERIAL, PCI_VENDOR, DEVICES, QEMU_FW_CFG and # QEMU_USB. Only the x86_64 system emulator is built. pkgname=vfio-native-qemu -_qver=11.1.1 +_qver=11.1.2 _tag=1.1.0 pkgver=${_qver} pkgrel=1 @@ -24,11 +24,11 @@ options=('!strip') source=("git+${url}.git#tag=v${_tag}" "https://download.qemu.org/qemu-${_qver}.tar.xz") sha256sums=('SKIP' - '079ffbff8a7111bbc89022107cbabf3bbfd614d5fc9d7cc675991196aca12482') + '731b5681e4bb18be313231579b8efd0296c5b015fa36dc533874b639ba838016') prepare() { cd "qemu-${_qver}" - for p in "${srcdir}"/vfio-native/patches/qemu/0[1-8]-*.patch; do + for p in "${srcdir}"/vfio-native/patches/qemu/[0-9][0-9]-*.patch; do msg2 "applying ${p##*/}" patch -Np1 --no-backup-if-mismatch -i "$p" done diff --git a/patches/README.md b/patches/README.md index adeee2e..a2958e0 100644 --- a/patches/README.md +++ b/patches/README.md @@ -34,7 +34,7 @@ enabled through the `cpuid_passthrough` module parameter after the guest has boo ## QEMU - `qemu/` -Against QEMU v11.1.1. Apply in numeric order. +Against QEMU v11.1.2. Apply in numeric order. | Patch | Clears | | --- | --- | @@ -46,11 +46,25 @@ Against QEMU v11.1.1. Apply in numeric order. | `06-audio` | `DEVICES` - HDA codec and controller identity | | `07-edid` | none. Carried for other detectors; VMAware does not read EDID. | | `08-cpu-misc` | none. CPU feature plumbing and one upstream backport. | +| `09-firmware-chain` | `FIRMWARE` - the DSDT links current VMAware added after `01-firmware` | +| `10-smbios` | `CTYPE`, `SMBIOS_VM_BIT`, `FIRMWARE` - what QEMU's synthesised SMBIOS leaks: chassis type Other, the type 0 VM bit, `pc-q35` default strings; adds `-smbios type=3,chassis-type=N` | +| `11-macos-cpu-hotplug` | `FIRMWARE` - re-adds the `\_SB.CPUS` CPU objects macOS needs, with the CPU-hotplug I/O base `0x0CD8` hidden from the scanner (renamed `_UID`, `Add()`-split OperationRegion base) rather than removed | -`patch -p1 < qemu/01-firmware.patch` and so on, then build with the prefix set to `/opt` so the -system QEMU package is left alone: +The numbered patches are the default series and score 0/85 on both VMAware HEAD and v2.8.1. +`03-pci-ids` scopes the virtio subsystem-id change so OVMF still binds virtio (its generic default +goes to Intel/0, virtio devices keep the Red Hat subsystem ids their drivers and OVMF expect). + +`11-macos-cpu-hotplug` makes the series universal: macOS boots (it needs the `\_SB.CPUS.Cxxx` +objects from `build_cpus_aml`) and both VMAware builds still read bare metal, with no opt-in. The +block's only tell, the `0x0CD8` CPU-hotplug port that v2.8.1 FIRMWARE byte-matches, is removed at +the AML level without moving the port, so the same series boots Windows and Linux unchanged. The +earlier opt-in split and the relocate-the-base approach are in "Approaches tried and rejected". + +Apply in numeric order, then build with the prefix set to `/opt` so the system QEMU package is left +alone: ```sh +for p in qemu/[0-9][0-9]-*.patch; do patch -p1 < "$p"; done ./configure --prefix=/opt/qemu-native --target-list=x86_64-softmmu ninja -C build && ninja -C build install ``` @@ -64,6 +78,8 @@ ninja -C build && ninja -C build install | CPUID leaf override (Hypervisor-Phantom) | Halved the timing signature, still detected, and *added* a `SINGLE_STEP` detection. | | Clearing `INTERCEPT_CPUID` outright | Guest never boots. | | Intercept clearing, minimal hypercall | Triple fault, or no effect. | +| Opt-in macOS CPU block (`optional/macos-cpu-hotplug`) | Worked but split the series in two; a macOS-capable build scored 2/85 on v2.8.1. Folded into `11` instead. | +| Relocating the CPU-hotplug I/O base off `0x0CD8` | Clears the scanner but hangs every SMM/OVMF guest (macOS spins 10 cores at boot, Windows too): OVMF hardcodes `0x0CD8` in `OvmfPkg` and reads the register block directly. `11` keeps the port and hides only the AML operand. | `TIMER` has two detectors that OR together. The exception-latency one passes on stock KVM once it is measured the way VMAware measures it; the instruction-latency one is removable with diff --git a/patches/qemu/01-firmware.patch b/patches/qemu/01-firmware.patch index fa843e5..3ca4cbd 100644 --- a/patches/qemu/01-firmware.patch +++ b/patches/qemu/01-firmware.patch @@ -5,7 +5,7 @@ Targets VMAware check: FIRMWARE ACPI table fingerprints: PCI link-device names, PNP0A06 resource stubs, the FADT C-state latencies and the AML the checker string-matches on. -Applies to QEMU v11.1.1. Apply from the source root with: +Applies to QEMU v11.1.2. Apply from the source root with: patch -p1 < 01-firmware.patch --- @@ -193,7 +193,7 @@ index 8837b69..4001d9d 100644 if (pcms->wdat_enabled == true) { g_assert(pm.tco_io_base); diff --git a/hw/acpi/pcihp.c b/hw/acpi/pcihp.c -index 87162ff..4c303cb 100644 +index 87162ff2c..3616c580a 100644 --- a/hw/acpi/pcihp.c +++ b/hw/acpi/pcihp.c @@ -687,10 +687,10 @@ void build_append_pcihp_resources(Aml *scope /* \\_SB.PCI0 */, @@ -209,7 +209,7 @@ index 87162ff..4c303cb 100644 /* device present, functioning, decoding, not shown in UI */ aml_append(dev, aml_name_decl("_STA", aml_int(0xB))); crs = aml_resource_template(); -@@ -699,6 +699,83 @@ void build_append_pcihp_resources(Aml *scope /* \\_SB.PCI0 */, +@@ -699,6 +699,89 @@ void build_append_pcihp_resources(Aml *scope /* \\_SB.PCI0 */, aml_append(scope, dev); } @@ -251,7 +251,7 @@ index 87162ff..4c303cb 100644 + case 5: + // Slot 5: USB XHCI (QEMU default often) + if (func == 0) sprintf(name, "XHC1"); -+ else sprintf(name, "XHC%X", func); ++ else sprintf(name, "XHC%X", func + 1); + break; + + case 6: @@ -261,19 +261,25 @@ index 87162ff..4c303cb 100644 + break; + + case 29: -+ // Slot 29 (0x1D): USB EHCI (Intel Standard) -+ if (func == 0) sprintf(name, "EHC1"); -+ else sprintf(name, "EHC%X", func); ++ // Slot 29 (0x1D): ICH9 USB, UHCI at functions 0-2 and EHCI at 7 ++ if (func == 7) sprintf(name, "EHC1"); ++ else sprintf(name, "UHC%X", func + 1); + break; + + case 20: -+ // Slot 20 (0x14): LPC / SMBus / SATA 2 -+ if (func == 0) sprintf(name, "LPCB"); // ISA Bridge -+ else if (func == 2) sprintf(name, "SAT1"); // 2nd SATA ++ // Slot 20 (0x14): SMBus / SATA 2; the q35 LPC bridge is 1f.0 ++ if (func == 2) sprintf(name, "SATA"); // 2nd SATA; slot 6 owns SAT0-SAT7 + else if (func == 3) sprintf(name, "SBUS"); // SMBus + else sprintf(name, "S20%X", func); // Fallback + break; + ++ case 31: ++ // Slot 31 (0x1F): the ICH9 LPC bridge, named as 03-pci-ids' PIRQ field expects ++ if (func == 0) { ++ sprintf(name, "LPCB"); ++ break; ++ } ++ /* fall through */ + default: + /* + * For generic slots, we use "RP" (Root Port) + devfn (Hex). @@ -293,7 +299,7 @@ index 87162ff..4c303cb 100644 bool build_append_notification_callback(Aml *parent_scope, const PCIBus *bus) { Aml *method; -@@ -912,7 +989,11 @@ void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus) +@@ -912,7 +995,11 @@ void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus) } /* start to compose PCI device descriptor */ diff --git a/patches/qemu/02-disk-identity.patch b/patches/qemu/02-disk-identity.patch index ed8ace3..1c2e064 100644 --- a/patches/qemu/02-disk-identity.patch +++ b/patches/qemu/02-disk-identity.patch @@ -5,7 +5,7 @@ Targets VMAware check: VIRTUAL_DISK / DISK_SERIAL Storage that announces itself as emulated: 'QEMU HARDDISK', 'QEMU DVD-ROM', empty serials and the QEMU vendor string in SCSI inquiry data. -Applies to QEMU v11.1.1. Apply from the source root with: +Applies to QEMU v11.1.2. Apply from the source root with: patch -p1 < 02-disk-identity.patch --- @@ -13,7 +13,7 @@ diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c index 1b0cce1..0e8ded4 100644 --- a/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c -@@ -2551,7 +2551,7 @@ static void scsi_realize(SCSIDevice *dev, Error **errp) +@@ -2556,7 +2556,7 @@ static void scsi_realize(SCSIDevice *dev, Error **errp) s->version = g_strdup(QEMU_HW_VERSION); } if (!s->vendor) { @@ -22,7 +22,7 @@ index 1b0cce1..0e8ded4 100644 } if (s->serial && strlen(s->serial) > MAX_SERIAL_LEN) { error_setg(errp, "The serial number can't be longer than %d characters", -@@ -2615,7 +2615,7 @@ static void scsi_hd_realize(SCSIDevice *dev, Error **errp) +@@ -2620,7 +2620,7 @@ static void scsi_hd_realize(SCSIDevice *dev, Error **errp) s->qdev.blocksize = s->qdev.conf.logical_block_size; s->qdev.type = TYPE_DISK; if (!s->product) { @@ -31,7 +31,7 @@ index 1b0cce1..0e8ded4 100644 } scsi_realize(&s->qdev, errp); } -@@ -2642,7 +2642,7 @@ static void scsi_cd_realize(SCSIDevice *dev, Error **errp) +@@ -2647,7 +2647,7 @@ static void scsi_cd_realize(SCSIDevice *dev, Error **errp) s->qdev.type = TYPE_ROM; s->features |= 1 << SCSI_DISK_F_REMOVABLE; if (!s->product) { @@ -59,7 +59,7 @@ diff --git a/hw/ide/core.c b/hw/ide/core.c index fb9bf11..7f44de2 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c -@@ -2640,21 +2640,20 @@ int ide_init_drive(IDEState *s, IDEDevice *dev, IDEDriveKind kind, Error **errp) +@@ -2648,21 +2648,20 @@ int ide_init_drive(IDEState *s, IDEDevice *dev, IDEDriveKind kind, Error **errp) if (dev->serial) { pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), dev->serial); } else { diff --git a/patches/qemu/03-pci-ids.patch b/patches/qemu/03-pci-ids.patch index 5e61d7e..99227cf 100644 --- a/patches/qemu/03-pci-ids.patch +++ b/patches/qemu/03-pci-ids.patch @@ -5,7 +5,10 @@ Targets VMAware check: PCI_VENDOR / DEVICES PCI vendor, device and subsystem IDs that read as Red Hat / QEMU, and the ICH9 southbridge layout OVMF expects to find at 1f:0. -Applies to QEMU v11.1.1. Apply from the source root with: +Virtio devices keep Red Hat's subsystem IDs: their vendor ID names them +anyway, and OVMF and the guest virtio drivers only bind with them. + +Applies to QEMU v11.1.2. Apply from the source root with: patch -p1 < 03-pci-ids.patch --- @@ -24,19 +27,6 @@ index f2448e9..4eea405 100644 #define PCI_DEVICE_ID_QEMU_IPMI 0x1112 /* VMWare (0x15ad) */ -@@ -77,9 +77,9 @@ extern bool pci_available; - #define PCI_DEVICE_ID_INTEL_82801IR 0x2922 - - /* Red Hat / Qumranet (for QEMU) -- see pci-ids.txt */ --#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 --#define PCI_SUBVENDOR_ID_REDHAT_QUMRANET 0x1af4 --#define PCI_SUBDEVICE_ID_QEMU 0x1100 -+#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 // VirtIO devices | IVSHMEM Driver | Looking Glass -+#define PCI_SUBVENDOR_ID_REDHAT_QUMRANET 0x8086 // VirtIO devices | IVSHMEM Driver | Looking Glass -+#define PCI_SUBDEVICE_ID_QEMU 0x0000 // VirtIO devices | IVSHMEM Driver | Looking Glass - - /* legacy virtio-pci devices */ - #define PCI_DEVICE_ID_VIRTIO_NET 0x1000 @@ -100,21 +100,21 @@ extern bool pci_available; */ #define PCI_DEVICE_ID_VIRTIO_10_BASE 0x1040 @@ -166,16 +156,32 @@ index e2aab3d..9131b5d 100644 dc->user_creatable = true; vc->get_id = device_vmstate_if_get_id; rc->get_state = device_get_reset_state; -diff --git a/hw/i386/pc.c b/hw/i386/pc.c -index f064aa2..cfdbbc1 100644 ---- a/hw/i386/pc.c -+++ b/hw/i386/pc.c -@@ -1689,7 +1689,7 @@ static void pc_machine_class_init(ObjectClass *oc, const void *data) +diff --git a/hw/pci/pci.c b/hw/pci/pci.c +index d319160..7206e3d 100644 +--- a/hw/pci/pci.c ++++ b/hw/pci/pci.c +@@ -355,8 +355,8 @@ static void pci_irq_handler(void *opaque, int irq_num, int level); + static void pci_add_option_rom(PCIDevice *pdev, bool is_default_rom, Error **); + static void pci_del_option_rom(PCIDevice *pdev); - pcmc->pci_enabled = true; - pcmc->has_acpi_build = true; -- pcmc->smbios_defaults = true; -+ pcmc->smbios_defaults = false; - pcmc->gigabyte_align = true; - pcmc->has_reserved_memory = true; - pcmc->enforce_amd_1tb_hole = true; +-static uint16_t pci_default_sub_vendor_id = PCI_SUBVENDOR_ID_REDHAT_QUMRANET; +-static uint16_t pci_default_sub_device_id = PCI_SUBDEVICE_ID_QEMU; ++static uint16_t pci_default_sub_vendor_id = PCI_VENDOR_ID_INTEL; ++static uint16_t pci_default_sub_device_id = 0; + + PCIHostStateList pci_host_bridges; + +diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c +index 6f5db5f..a7e07b8 100644 +--- a/hw/virtio/virtio-pci.c ++++ b/hw/virtio/virtio-pci.c +@@ -2029,6 +2029,9 @@ static void virtio_pci_device_plugged(DeviceState *d, Error **errp) + modern = virtio_pci_modern(proxy); + + config = proxy->pci_dev.config; ++ /* virtio drivers match Red Hat's subsystem IDs; OVMF needs a device ID >= 0x40 */ ++ pci_set_word(config + PCI_SUBSYSTEM_VENDOR_ID, PCI_SUBVENDOR_ID_REDHAT_QUMRANET); ++ pci_set_word(config + PCI_SUBSYSTEM_ID, PCI_SUBDEVICE_ID_QEMU); + if (proxy->class_code) { + pci_config_set_class(config, proxy->class_code); + } diff --git a/patches/qemu/04-fw-cfg.patch b/patches/qemu/04-fw-cfg.patch index a6abff0..0e5798d 100644 --- a/patches/qemu/04-fw-cfg.patch +++ b/patches/qemu/04-fw-cfg.patch @@ -4,7 +4,7 @@ Targets VMAware check: QEMU_FW_CFG The fw_cfg device signature, which names QEMU literally. -Applies to QEMU v11.1.1. Apply from the source root with: +Applies to QEMU v11.1.2. Apply from the source root with: patch -p1 < 04-fw-cfg.patch --- diff --git a/patches/qemu/05-usb-hid.patch b/patches/qemu/05-usb-hid.patch index 1be743e..b3119cb 100644 --- a/patches/qemu/05-usb-hid.patch +++ b/patches/qemu/05-usb-hid.patch @@ -4,7 +4,7 @@ Targets VMAware check: QEMU_USB USB HID descriptors carrying QEMU vendor strings and product IDs. -Applies to QEMU v11.1.1. Apply from the source root with: +Applies to QEMU v11.1.2. Apply from the source root with: patch -p1 < 05-usb-hid.patch --- diff --git a/patches/qemu/06-audio.patch b/patches/qemu/06-audio.patch index 0560a53..e9cc886 100644 --- a/patches/qemu/06-audio.patch +++ b/patches/qemu/06-audio.patch @@ -4,7 +4,7 @@ Targets VMAware check: DEVICES Intel HDA codec and controller identity strings. -Applies to QEMU v11.1.1. Apply from the source root with: +Applies to QEMU v11.1.2. Apply from the source root with: patch -p1 < 06-audio.patch --- diff --git a/patches/qemu/07-edid.patch b/patches/qemu/07-edid.patch index 66e7a68..656a3f2 100644 --- a/patches/qemu/07-edid.patch +++ b/patches/qemu/07-edid.patch @@ -5,7 +5,7 @@ Targets VMAware check: (display identity) Generated EDID vendor and model. Note VMAware does not read EDID - this is carried for other detectors, not for the score. -Applies to QEMU v11.1.1. Apply from the source root with: +Applies to QEMU v11.1.2. Apply from the source root with: patch -p1 < 07-edid.patch --- diff --git a/patches/qemu/08-cpu-misc.patch b/patches/qemu/08-cpu-misc.patch index 06eadf7..04ef0a8 100644 --- a/patches/qemu/08-cpu-misc.patch +++ b/patches/qemu/08-cpu-misc.patch @@ -4,7 +4,7 @@ Targets VMAware check: (CPU model plumbing) CPU feature-word plumbing and an upstream backport; not aimed at a check. -Applies to QEMU v11.1.1. Apply from the source root with: +Applies to QEMU v11.1.2. Apply from the source root with: patch -p1 < 08-cpu-misc.patch --- diff --git a/patches/qemu/09-firmware-chain.patch b/patches/qemu/09-firmware-chain.patch new file mode 100644 index 0000000..3d185a8 --- /dev/null +++ b/patches/qemu/09-firmware-chain.patch @@ -0,0 +1,239 @@ +Subject: [PATCH] acpi: clear the remaining VMAware FIRMWARE chain links + +Targets VMAware check: FIRMWARE + +01-firmware cleared the FIRMWARE links known when it was written. Current +VMAware adds more DSDT fingerprints to the same chain, and firmware() returns +on the first match, so the technique keeps firing until every link is gone. +This clears the rest, taking a q35 guest to 0 detections on both the v2.8.1 +release and current HEAD. Each change is a rename or a byte-neutral rewrite of +emitted AML; none alters ACPI semantics. + +Links cleared: + - PCI interrupt-link query methods IQST/IQCR and the PRR0/PRRI resource + template/field -> PSTA/PCRS/PRS0/PRSI + - TPM Physical Presence Interface regions/method TPP2/TPP3/TPFN -> PPI2/PPI3/PPIF + - zeroed _S5 SLP_TYP: emit the 2-element package form + - ACPI0006 _HID in \_GPE: declared by its full path \_GPE._HID; the + object stays, because macOS will not power off without it + - Q35 MMCONFIG reservation device MCFG -> PCMM + - synthetic GSI link devices: give the no-op _DIS/_SRS a body so the + empty-stub descriptor no longer matches + - PCIe _OSC capability mask: And() operands swapped (commutative) + - PIRQ link _PRS: list the identical IRQ set as {11, 10, 5} so the fixed + 5/10/11 descriptor differs + +Applies to QEMU v11.1.2, on top of 01-firmware. From the source root: + patch -p1 < 09-firmware-chain.patch + +diff --git a/hw/acpi/pci.c b/hw/acpi/pci.c +index 8c7ed1047..0d6a95d67 100644 +--- a/hw/acpi/pci.c ++++ b/hw/acpi/pci.c +@@ -326,8 +326,8 @@ Aml *build_pci_host_bridge_osc_method(bool enable_native_pcie_hotplug) + * Allow SHPC (PCI bridges can have SHPC controller) + * Disable PCIe Native Hot-plug if ACPI PCI Hot-plug is enabled. + */ +- aml_append(if_ctx, aml_and(a_ctrl, +- aml_int(0x1E | (enable_native_pcie_hotplug ? 0x1 : 0x0)), a_ctrl)); ++ aml_append(if_ctx, aml_and( ++ aml_int(0x1E | (enable_native_pcie_hotplug ? 0x1 : 0x0)), a_ctrl, a_ctrl)); + + if_ctx2 = aml_if(aml_lnot(aml_equal(aml_arg(1), aml_int(1)))); + /* Unknown revision */ +diff --git a/hw/acpi/tpm.c b/hw/acpi/tpm.c +index e70377598..d7c234cc5 100644 +--- a/hw/acpi/tpm.c ++++ b/hw/acpi/tpm.c +@@ -39,10 +39,10 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base) + * the PPI code and parameter (PPRQ, PPRM) to the firmware. + */ + aml_append(dev, +- aml_operation_region("TPP2", AML_SYSTEM_MEMORY, ++ aml_operation_region("PPI2", AML_SYSTEM_MEMORY, + aml_int(ppi_base + 0x100), + 0x5A)); +- field = aml_field("TPP2", AML_ANY_ACC, AML_NOLOCK, AML_PRESERVE); ++ field = aml_field("PPI2", AML_ANY_ACC, AML_NOLOCK, AML_PRESERVE); + aml_append(field, aml_named_field("PPIN", 8)); + aml_append(field, aml_named_field("PPIP", 32)); + aml_append(field, aml_named_field("PPRP", 32)); +@@ -55,11 +55,11 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base) + + aml_append(dev, + aml_operation_region( +- "TPP3", AML_SYSTEM_MEMORY, ++ "PPI3", AML_SYSTEM_MEMORY, + aml_int(ppi_base + + 0x15a /* movv, docs/specs/tpm.rst */), + 0x1)); +- field = aml_field("TPP3", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE); ++ field = aml_field("PPI3", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE); + aml_append(field, aml_named_field("MOVV", 8)); + aml_append(dev, field); + +@@ -67,7 +67,7 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base) + * DerefOf in Windows is broken with SYSTEM_MEMORY. Use a dynamic + * operation region inside of a method for getting FUNC[op]. + */ +- method = aml_method("TPFN", 1, AML_SERIALIZED); ++ method = aml_method("PPIF", 1, AML_SERIALIZED); + { + Aml *op = aml_arg(0); + ifctx = aml_if(aml_lgreater_equal(op, aml_int(0x100))); +@@ -164,7 +164,7 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base) + + /* get opcode flags */ + aml_append(ifctx2, +- aml_store(aml_call1("TPFN", op), op_flags)); ++ aml_store(aml_call1("PPIF", op), op_flags)); + + /* if func[opcode] & TPM_PPI_FUNC_NOT_IMPLEMENTED */ + ifctx3 = aml_if( +@@ -325,7 +325,7 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base) + op)); + + /* get opcode flags */ +- aml_append(ifctx2, aml_store(aml_call1("TPFN", op), ++ aml_append(ifctx2, aml_store(aml_call1("PPIF", op), + op_flags)); + /* if func[opcode] & TPM_PPI_FUNC_NOT_IMPLEMENTED */ + ifctx3 = aml_if( +@@ -397,7 +397,7 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base) + op)); + + /* get opcode flags */ +- aml_append(ifctx2, aml_store(aml_call1("TPFN", op), ++ aml_append(ifctx2, aml_store(aml_call1("PPIF", op), + op_flags)); + /* return confirmation status code */ + aml_append(ifctx2, +diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c +index 4001d9d69..087d5f778 100644 +--- a/hw/i386/acpi-build.c ++++ b/hw/i386/acpi-build.c +@@ -515,7 +515,7 @@ static Aml *build_link_dev(const char *name, uint8_t uid, Aml *reg) + Aml *dev; + Aml *crs; + Aml *method; +- uint32_t irqs[] = {5, 10, 11}; ++ uint32_t irqs[] = {11, 10, 5}; + + dev = aml_device("%s", name); + aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0C0F"))); +@@ -527,7 +527,7 @@ static Aml *build_link_dev(const char *name, uint8_t uid, Aml *reg) + aml_append(dev, aml_name_decl("_PRS", crs)); + + method = aml_method("_STA", 0, AML_NOTSERIALIZED); +- aml_append(method, aml_return(aml_call1("IQST", reg))); ++ aml_append(method, aml_return(aml_call1("PSTA", reg))); + aml_append(dev, method); + + method = aml_method("_DIS", 0, AML_NOTSERIALIZED); +@@ -535,12 +535,12 @@ static Aml *build_link_dev(const char *name, uint8_t uid, Aml *reg) + aml_append(dev, method); + + method = aml_method("_CRS", 0, AML_NOTSERIALIZED); +- aml_append(method, aml_return(aml_call1("IQCR", reg))); ++ aml_append(method, aml_return(aml_call1("PCRS", reg))); + aml_append(dev, method); + + method = aml_method("_SRS", 1, AML_NOTSERIALIZED); +- aml_append(method, aml_create_dword_field(aml_arg(0), aml_int(5), "PRRI")); +- aml_append(method, aml_store(aml_name("PRRI"), reg)); ++ aml_append(method, aml_create_dword_field(aml_arg(0), aml_int(5), "PRSI")); ++ aml_append(method, aml_store(aml_name("PRSI"), reg)); + aml_append(dev, method); + + return dev; +@@ -569,9 +569,11 @@ static Aml *build_gsi_link_dev(const char *name, uint8_t uid, uint8_t gsi) + * _DIS can be no-op because the interrupt cannot be disabled. + */ + method = aml_method("_DIS", 0, AML_NOTSERIALIZED); ++ aml_append(method, aml_store(aml_int(0), aml_local(0))); + aml_append(dev, method); + + method = aml_method("_SRS", 1, AML_NOTSERIALIZED); ++ aml_append(method, aml_store(aml_int(0), aml_local(0))); + aml_append(dev, method); + + return dev; +@@ -582,28 +584,28 @@ static Aml *build_iqcr_method(bool is_piix4) + { + Aml *if_ctx; + uint32_t irqs; +- Aml *method = aml_method("IQCR", 1, AML_SERIALIZED); ++ Aml *method = aml_method("PCRS", 1, AML_SERIALIZED); + Aml *crs = aml_resource_template(); + + irqs = 0; + aml_append(crs, aml_interrupt(AML_CONSUMER, AML_LEVEL, + AML_ACTIVE_HIGH, AML_SHARED, &irqs, 1)); +- aml_append(method, aml_name_decl("PRR0", crs)); ++ aml_append(method, aml_name_decl("PRS0", crs)); + + aml_append(method, +- aml_create_dword_field(aml_name("PRR0"), aml_int(5), "PRRI")); ++ aml_create_dword_field(aml_name("PRS0"), aml_int(5), "PRSI")); + + if (is_piix4) { + if_ctx = aml_if(aml_lless(aml_arg(0), aml_int(0x80))); +- aml_append(if_ctx, aml_store(aml_arg(0), aml_name("PRRI"))); ++ aml_append(if_ctx, aml_store(aml_arg(0), aml_name("PRSI"))); + aml_append(method, if_ctx); + } else { + aml_append(method, + aml_store(aml_and(aml_arg(0), aml_int(0xF), NULL), +- aml_name("PRRI"))); ++ aml_name("PRSI"))); + } + +- aml_append(method, aml_return(aml_name("PRR0"))); ++ aml_append(method, aml_return(aml_name("PRS0"))); + return method; + } + +@@ -611,7 +613,7 @@ static Aml *build_iqcr_method(bool is_piix4) + static Aml *build_irq_status_method(void) + { + Aml *if_ctx; +- Aml *method = aml_method("IQST", 1, AML_NOTSERIALIZED); ++ Aml *method = aml_method("PSTA", 1, AML_NOTSERIALIZED); + + if_ctx = aml_if(aml_and(aml_int(0x80), aml_arg(0), NULL)); + aml_append(if_ctx, aml_return(aml_int(0x09))); +@@ -801,7 +803,7 @@ static Aml *build_q35_dram_controller(const AcpiMcfgInfo *mcfg) + Aml *resource_template; + + /* DRAM controller */ +- dev = aml_device("MCFG"); ++ dev = aml_device("PCMM"); + aml_append(dev, aml_name_decl("_HID", aml_string("PNP0C01"))); + + resource_template = aml_resource_template(); +@@ -986,7 +988,11 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, + + scope = aml_scope("_GPE"); + { +- aml_append(scope, aml_name_decl("_HID", aml_string("ACPI0006"))); ++ /* ++ * macOS needs the GPE block device to reach S5; naming it by full ++ * path keeps it without the byte pattern VMAware matches. ++ */ ++ aml_append(scope, aml_name_decl("\\_GPE._HID", aml_string("ACPI0006"))); + if (machine->nvdimms_state->is_enabled) { + method = aml_method("_E04", 0, AML_NOTSERIALIZED); + aml_append(method, aml_notify(aml_name("\\_SB.NVDR"), +@@ -1193,11 +1199,9 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, + aml_append(scope, aml_name_decl("_S4", pkg)); + } + +- pkg = aml_package(4); ++ pkg = aml_package(2); + aml_append(pkg, aml_int(0)); /* PM1a_CNT.SLP_TYP */ + aml_append(pkg, aml_int(0)); /* PM1b_CNT.SLP_TYP not impl. */ +- aml_append(pkg, aml_int(0)); /* reserved */ +- aml_append(pkg, aml_int(0)); /* reserved */ + aml_append(scope, aml_name_decl("_S5", pkg)); + aml_append(dsdt, scope); + diff --git a/patches/qemu/10-smbios.patch b/patches/qemu/10-smbios.patch new file mode 100644 index 0000000..f4e4881 --- /dev/null +++ b/patches/qemu/10-smbios.patch @@ -0,0 +1,97 @@ +Subject: [PATCH] smbios: CTYPE / SMBIOS_VM_BIT / FIRMWARE + +Targets VMAware check: CTYPE / SMBIOS_VM_BIT / FIRMWARE + +QEMU keeps synthesising every required SMBIOS structure the domain does not +supply, so identity can be set with -smbios type=N field arguments and real +structures passed with -smbios file= only where they are wanted. What QEMU +fills in by itself must not read as a VM: + + - type 3 chassis type was hard-coded to 1 (Other), which CTYPE flags. It + defaults to 3 (Desktop) and is set with type=3,chassis-type=N. + - the type 0 "virtual machine" characteristics bit now defaults off, so a + type 0 from libvirt , which cannot pass vm=off, stays clean. + - unset product and version strings in types 1-4 default to "Unknown" + instead of the machine description and name ("Standard PC (Q35 + ICH9, + 2009)", "pc-q35-"), which FIRMWARE matches when it scans SMBIOS on + Windows. + +Applies to QEMU v11.1.2, on top of 04-fw-cfg. Apply from the source root with: + patch -p1 < 10-smbios.patch + +--- +diff --git a/hw/i386/fw_cfg.c b/hw/i386/fw_cfg.c +index e5fddbc..bfc193c 100644 +--- a/hw/i386/fw_cfg.c ++++ b/hw/i386/fw_cfg.c +@@ -71,13 +71,12 @@ void fw_cfg_build_smbios(PCMachineState *pcms, FWCfgState *fw_cfg, + unsigned i, array_count; + MachineState *ms = MACHINE(pcms); + PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms); +- MachineClass *mc = MACHINE_GET_CLASS(pcms); + X86CPU *cpu = X86_CPU(ms->possible_cpus->cpus[0].cpu); + int nr_e820; + + if (pcmc->smbios_defaults) { + /* These values are guest ABI, do not change */ +- smbios_set_defaults("Unknown", mc->desc, mc->name); ++ smbios_set_defaults("Unknown", "Unknown", "Unknown"); + } + + /* tell smbios about cpuid version and features */ +diff --git a/hw/smbios/smbios.c b/hw/smbios/smbios.c +index 7d71418..c557069 100644 +--- a/hw/smbios/smbios.c ++++ b/hw/smbios/smbios.c +@@ -62,7 +62,8 @@ static struct { + + static struct { + const char *manufacturer, *version, *serial, *asset, *sku; +-} type3; ++ uint64_t chassis_type; ++} type3 = { .chassis_type = 0x03 }; /* Desktop: QEMU's "Other" reads as a VM */ + + /* + * SVVP requires max_speed and current_speed to be set and not being +@@ -282,6 +283,10 @@ static const QemuOptDesc qemu_smbios_type3_opts[] = { + .name = "sku", + .type = QEMU_OPT_STRING, + .help = "SKU number", ++ },{ ++ .name = "chassis-type", ++ .type = QEMU_OPT_NUMBER, ++ .help = "chassis type", + }, + { /* end of list */ } + }; +@@ -656,7 +661,7 @@ static void smbios_build_type_3_table(void) + SMBIOS_BUILD_TABLE_PRE(3, T3_BASE, true); /* required */ + + SMBIOS_TABLE_SET_STR(3, manufacturer_str, type3.manufacturer); +- t->type = 0x01; /* Other */ ++ t->type = type3.chassis_type; + SMBIOS_TABLE_SET_STR(3, version_str, type3.version); + SMBIOS_TABLE_SET_STR(3, serial_number_str, type3.serial); + SMBIOS_TABLE_SET_STR(3, asset_tag_number_str, type3.asset); +@@ -1413,7 +1418,7 @@ void smbios_entry_add(QemuOpts *opts, Error **errp) + save_opt(&smbios_type0.version, opts, "version"); + save_opt(&smbios_type0.date, opts, "date"); + smbios_type0.uefi = qemu_opt_get_bool(opts, "uefi", false); +- smbios_type0.vm = qemu_opt_get_bool(opts, "vm", true); ++ smbios_type0.vm = qemu_opt_get_bool(opts, "vm", false); + + val = qemu_opt_get(opts, "release"); + if (val) { +@@ -1465,6 +1470,12 @@ void smbios_entry_add(QemuOpts *opts, Error **errp) + save_opt(&type3.serial, opts, "serial"); + save_opt(&type3.asset, opts, "asset"); + save_opt(&type3.sku, opts, "sku"); ++ type3.chassis_type = qemu_opt_get_number(opts, "chassis-type", ++ type3.chassis_type); ++ if (type3.chassis_type > UINT8_MAX) { ++ error_setg(errp, "SMBIOS chassis type is too large (> %d)", ++ UINT8_MAX); ++ } + return; + case 4: + if (!qemu_opts_validate(opts, qemu_smbios_type4_opts, errp)) { diff --git a/patches/qemu/11-macos-cpu-hotplug.patch b/patches/qemu/11-macos-cpu-hotplug.patch new file mode 100644 index 0000000..00ee3a5 --- /dev/null +++ b/patches/qemu/11-macos-cpu-hotplug.patch @@ -0,0 +1,91 @@ +Subject: [PATCH] acpi: keep the CPU objects macOS needs without the FIRMWARE tell + +Targets VMAware check: FIRMWARE (CPU hotplug I/O signature) + +macOS references \_SB.CPUS.Cxxx from its own CpuPlug SSDT, so the guest needs +QEMU's build_cpus_aml output (the \_SB.PCI0.PRES controller and the \_SB.CPUS +Processor objects) or it hangs in ACPI namespace init. 01-firmware dropped that +block because build_cpus_aml emits the CPU-hotplug register region at I/O port +0x0CD8, which VMAware v2.8.1 FIRMWARE byte-matches as the WordData sequence +0x0B 0xD8 0x0C and flips to a QEMU verdict. HEAD has no such check. + +This re-adds the block for every guest (no opt-in) and removes that one tell at +the source rather than removing the whole block: + + - the controller _UID is renamed from "CPU Hotplug resources" to "Reserved + resources C", clearing the FIRMWARE string match the same way 01-firmware + renamed the other reserved-resource devices. + + - the PRST OperationRegion base is emitted as Add(0x0C00, 0xD8) for a SystemIO + base. It evaluates to the identical 0x0CD8 at namespace load, so the port + QEMU decodes and the port OVMF's SMM code reads directly are both unchanged. + Relocating the base instead was tried and breaks every SMM/OVMF guest (macOS + hangs at boot, Windows too), because OVMF hardcodes 0x0CD8. Splitting the + operand keeps 0x0CD8 live but never places the three matched bytes next to + each other in the AML. + +The _CRS IO descriptor keeps 0x0CD8 untouched: a small-resource IO descriptor +encodes its base as 0xD8 0x0C preceded by the 0x47/0x01 descriptor bytes, never +by 0x0B, so it is not the sequence the scanner matches. + +Verified against QEMU 11.1.2: macOS boots to login and powers off via S5; +VMAware v2.8.1 and HEAD both read bare metal with FIRMWARE clear on the stealth +guest; Windows and Linux boot unchanged. + +Applies to QEMU v11.1.2, on top of 01-firmware. From the source root: + patch -p1 < 11-macos-cpu-hotplug.patch + +--- a/hw/i386/acpi-build.c ++++ b/hw/i386/acpi-build.c +@@ -1002,6 +1002,16 @@ + } + aml_append(dsdt, scope); + ++ { ++ CPUHotplugFeatures opts = { ++ .acpi_1_compatible = true, ++ .smi_path = pm->smi_on_cpuhp ? "\\_SB.PCI0.SMI0.SMIC" : NULL, ++ .fw_unplugs_cpu = pm->smi_on_cpu_unplug, ++ }; ++ build_cpus_aml(dsdt, machine, opts, pc_madt_cpu_entry, ++ pm->cpu_hp_io_base, "\\_SB.PCI0", "\\_GPE._E02", ++ AML_SYSTEM_IO); ++ } + + crs_range_set_init(&crs_range_set); + bus = PC_MACHINE(machine)->pcibus; +--- a/hw/acpi/cpu.c ++++ b/hw/acpi/cpu.c +@@ -363,7 +363,7 @@ + aml_append(cpu_ctrl_dev, + aml_name_decl("_HID", aml_eisaid("PNP0A06"))); + aml_append(cpu_ctrl_dev, +- aml_name_decl("_UID", aml_string("CPU Hotplug resources"))); ++ aml_name_decl("_UID", aml_string("Reserved resources C"))); + aml_append(cpu_ctrl_dev, aml_mutex(CPU_LOCK, 0)); + + assert((rs == AML_SYSTEM_IO) || (rs == AML_SYSTEM_MEMORY)); +@@ -379,9 +379,21 @@ + + aml_append(cpu_ctrl_dev, aml_name_decl("_CRS", crs)); + +- /* declare CPU hotplug MMIO region with related access fields */ ++ /* ++ * Declare the CPU hotplug register block. The region is at the same ++ * fixed base QEMU decodes and firmware (OVMF SMM) reads directly, so ++ * that is unchanged. Only the base operand's AML encoding is split ++ * across an Add() for a SystemIO base: emitting the 16-bit port as a ++ * single WordData leaks the exact three-byte sequence a VM-detection ++ * scanner byte-matches on. The Add() evaluates to the identical base ++ * at namespace load but never places those bytes contiguously. ++ */ ++ Aml *region_base = (rs == AML_SYSTEM_IO) ++ ? aml_add(aml_int(base_addr & 0xff00), ++ aml_int(base_addr & 0x00ff), NULL) ++ : aml_int(base_addr); + aml_append(cpu_ctrl_dev, +- aml_operation_region("PRST", rs, aml_int(base_addr), ++ aml_operation_region("PRST", rs, region_base, + ACPI_CPU_HOTPLUG_REG_LEN)); + + field = aml_field("PRST", AML_BYTE_ACC, AML_NOLOCK,