# Why each VMAware check fires, and what it took to clear it The engineering record behind the fidelity work. `README.md` says *what to apply and when*; this says *why*, with the source references. Written 2026-09-03 at 2/85, revised 2026-09-05 at **1/85** 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`. --- ## CLEARED - CPUID_SIGNATURE **Not about hypervisor leaves.** The guest's `0x40000000` reads all zeros, and so does the bare-metal host, so it was never the trigger. The AMD branch (`cpuid_signature()`, vmaware.hpp:6941-6970) reads **leaf 7 subleaf 0, EDX** and returns true if bit 26 (`IBRS`/`IBPB`), 27 (`STIBP`) or 31 (`SSBD`) is set. AMD reserves those three to zero and enumerates its mitigations in `0x80000008.EBX` instead. Measured with an unprivileged CPUID probe: | | leaf 7.0 EDX | bits 26/27/31 | `0x80000008.EBX` 12/14/15/24/25 | | --- | --- | --- | --- | | host, bare metal | `0x10000010` | `0 0 0` | `1 1 1 1 1` | | guest, before | `0x9c000010` | set | passed through | | guest, after | `0x30000010` | `0 0 0` | `1 1 1 1 1` | KVM synthesises the Intel-style `SPEC_CTRL` interface on AMD, and `host-passthrough` enables it. Three `` lines fix it. All three are needed - the check ORs the bits. Verified in-guest afterwards that every AMD-native mitigation bit survives, so the guest keeps the interface Windows actually uses on AMD. Residual: KVM also synthesises bit 29 (`arch-capabilities`), which bare metal lacks. Nothing reads it, so it is left alone. --- ## CLEARED - SVM_EXCEPTIONS **A bug KVM documents against itself.** `svm.c:272` is unconditional: ```c svm->vmcb->save.efer = efer | EFER_SVME; ``` Hardware therefore never sees the guest's real `SVME=0`. A CPL3 `VMLOAD` passes the SVME check, hardware notices CPL is not zero, and injects `#GP` *before* the VMLOAD intercept can fire. Bare metal raises `#UD`, and `#UD` is the one exception `svm_exceptions()` treats as innocent (vmaware.hpp:14551). There is a `FIXME` at `svm.c:1076-1080` describing exactly this. The diagnostic that pinned it: the scored run printed `[ DETECTED ]` with **no debug line**. Both noisy exits in the function call `vma_debug`, so it must have fallen through to the bare `return true` at vmaware.hpp:14570 - which forces `svm_visible == true` and a non-`#UD` fault. Fix, in `svm_recalc_instruction_intercepts()`: ```c if (!(vcpu->arch.efer & EFER_SVME)) { svm_set_intercept(svm, INTERCEPT_VMLOAD); svm_set_intercept(svm, INTERCEPT_VMSAVE); svm_set_intercept(svm, INTERCEPT_CLGI); svm_set_intercept(svm, INTERCEPT_STGI); set_exception_intercept(svm, GP_VECTOR); /* added */ } else { ``` `gp_interception()` then decodes the SVM opcode, sees `!is_guest_mode`, and calls `svm_invoke_exit_handler(SVM_EXIT_VMLOAD)` -> `vmload_interception` -> `nested_svm_check_permissions` (nested.c:1664), which queues `UD_VECTOR`. The shipped `0001` also keeps the intercept across a guest clearing `SVME` later (the stock `svm_set_efer()` dropped it there), drops it again on `SVME=1` where the erratum workaround does not need it, and never arms it for SEV guests, whose instructions KVM cannot decode - the same exclusion the erratum path already makes. **Do NOT disable `svm` in guest CPUID as an alternative.** It does not change the exception, and it moves the check into its `!svm_visible` branch, which is *worse* - "SVM hypervisor hiding CPU capabilities", weight 150. **Do NOT use `kvm.enable_vmware_backdoor=1`** as the no-rebuild shortcut. It arms the same intercept, but permanently opens two well-known VMware detection surfaces in every guest on the host: `emulate.c:2562` stops faulting CPL3 `IN`/`OUT` on ports `0x5658`/`0x5659`, and `emulate.c:3905` makes CPL3 `RDPMC` of pseudo-counters `0x10000`-`0x10002` return host TSC, which is architecturally impossible on real silicon. VMAware is blind to both. pafish, al-khaser and other detection tooling are not. --- ## CLEARED - FIRMWARE **A chain that reports only its first hit.** `firmware()` (vmaware.hpp:8860) runs six sections in fixed order against each firmware buffer, DSDT first, and the first match returns. Fixing one link costs a full QEMU rebuild and boot and only reveals the next - so bundle. Links, in order: 1. ~~SMI Resources reservation string~~ - `_UID` renamed 2. ~~PRTP/PRTA routing symmetry~~ - renamed `IRQP`/`IRQA` 3. ~~Sequential PIRQ names, vmaware.hpp:9142~~ - `GSIA-H` -> `APCA-H` 4. ~~PNP0A06 resource stubs, vmaware.hpp:9148~~ - `GPER` -> `RSRA`, `PHPR` -> `RSRB` 5. ~~FACP C2/C3 latencies, vmaware.hpp:9242~~ - `0xfff` -> `0xffe` 6. Debug Port OperationRegion at `0x0402` - already dead on this tree 7. HPET register-validation loop - dormant, domain sets `hpet present='no'`. Re-arms if enabled. 8. Dummy SATA, DMAR, APIC source overrides - already dead on this tree Link 3 requires **all four** of `LNKE`, `LNKH`, `GSIE`, `GSIH`, which is QEMU's eight-link plus eight-GSI layout. Real boards have four PCI link devices and no GSI-named ones, so rename the `GSI*` half and leave `LNK*` alone. Those names are declared by `build_gsi_link_dev()` and referenced only through `build_q35_routing_table("GSI")`, which derives every name from one 3-character prefix - so a prefix change moves every reference with it. Do not pick `IRQ` as the replacement: `_SB.PCI0.IRQA` is already the APIC routing package and a bare NameSeg would bind to it. `0xffe` keeps "C-state not supported" semantics (anything above 100 / 1000 means unsupported) while not matching the check's exact-equality test against `0x0FFF`. **WAET must stay out.** `"WAET"` is target index 18 in the section-2 scan and the table's signature sits at offset 0, i.e. an instant hit. An earlier sed-based patcher claimed it restored WAET and never did: `build_waet()` has zero call sites, so its `grep -q build_waet` guard was satisfied by the definition alone and the insert never ran. Guard on a call site, not on a definition. --- ## OPEN - GPU_CAPABILITIES (weight 20) **Not a GPU check.** Four lines: `GetDC(nullptr)`, then `GetDeviceCaps(hdc, COLORMGMTCAPS)`, DETECTED if the result lacks `CM_GAMMA_RAMP` (0x2) or is `CM_NONE`. No adapter enumeration, no DXGI, no WMI, no device blacklist, no EDID - so the patched `SAM`/`SyncMaster` EDID override is irrelevant to this check. A plausible theory said it was a harness artefact: scoring runs over SSH, OpenSSH on Windows lands in session 0, and a non-interactive window station has no gamma LUT. **Tested and disproven.** Re-ran as a scheduled task with `/it`, confirmed `SessionId=1` and `ScreenBounds={0,0,640,480}` - same score, still fires. So it is genuine: `