Skip to content

Commit 420d599

Browse files
author
Requiem
committed
First detection ever for GPU passthrough on QEMU
1 parent 026bb4d commit 420d599

3 files changed

Lines changed: 110 additions & 8 deletions

File tree

docs/documentation.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ VMAware provides a convenient way to not only check for VMs, but also have the f
441441
| `VM::CPU_BRAND` | Check if CPU brand model contains any VM-specific string snippets | 🐧🪟🍏 | 50% | | | | | [link](https://github.com/kernelwernel/VMAware/blob/8cb2491b1c7d2cb7300d1d698b7c64c953b4ae75/src/vmaware.hpp#L2545) |
442442
| `VM::HYPERVISOR_BIT` | Check if hypervisor feature bit in CPUID eax bit 31 is enabled (always false for physical CPUs) | 🐧🪟🍏 | 100% | | | | | [link](https://github.com/kernelwernel/VMAware/blob/8cb2491b1c7d2cb7300d1d698b7c64c953b4ae75/src/vmaware.hpp#L2601) |
443443
| `VM::HYPERVISOR_STR` | Check for hypervisor brand string length (would be around 2 characters in a host machine) | 🐧🪟🍏 | 75% | | | | | [link](https://github.com/kernelwernel/VMAware/blob/8cb2491b1c7d2cb7300d1d698b7c64c953b4ae75/src/vmaware.hpp#L2622) |
444-
| `VM::TIMER` | Check for timing anomalies in the system | 🐧🪟🍏 | 45% | | | | Unsafe to run under binary emulators | [link](https://github.com/kernelwernel/VMAware/blob/8cb2491b1c7d2cb7300d1d698b7c64c953b4ae75/src/vmaware.hpp#L8169 ) |
444+
| `VM::TIMER` | Check for timing anomalies in the system | 🐧🪟🍏 | 45% | | | | | [link](https://github.com/kernelwernel/VMAware/blob/8cb2491b1c7d2cb7300d1d698b7c64c953b4ae75/src/vmaware.hpp#L8169 ) |
445445
| `VM::THREADCOUNT` | Check if there are only 1 or 2 threads, which is a common pattern in VMs with default settings (nowadays physical CPUs should have at least 4 threads for modern CPUs) | 🐧🪟🍏 | 35% | | | | | [link](https://github.com/kernelwernel/VMAware/blob/8cb2491b1c7d2cb7300d1d698b7c64c953b4ae75/src/vmaware.hpp#L2649) |
446446
| `VM::MAC` | Check if mac address starts with certain VM designated values | 🐧🪟 | 20% | | | | | [link](https://github.com/kernelwernel/VMAware/blob/8cb2491b1c7d2cb7300d1d698b7c64c953b4ae75/src/vmaware.hpp#L2671) |
447447
| `VM::TEMPERATURE` | Check if thermal directory in linux is present, might not be present in VMs | 🐧 | 15% | | | | | [link](https://github.com/kernelwernel/VMAware/blob/8cb2491b1c7d2cb7300d1d698b7c64c953b4ae75/src/vmaware.hpp#L2804) |
@@ -544,6 +544,8 @@ VMAware provides a convenient way to not only check for VMs, but also have the f
544544
| `VM::PCI_VM` | Check for PCIe bridge names for known VM keywords and brands | 🐧 | 100% | | | | Disabled by default | [link](https://github.com/kernelwernel/VMAware/blob/8cb2491b1c7d2cb7300d1d698b7c64c953b4ae75/src/vmaware.hpp#L10142) |
545545
| `VM::TPM` | Check if the system has a physical TPM by matching the TPM manufacturer against known physical TPM chip vendors | 🪟 | 50% | | | | | [link](https://github.com/kernelwernel/VMAware/blob/fb66db9fdd7894edebe5eeade4b0148a08bd5514/src/vmaware.hpp#L10011)|
546546
| `VM::PCI_VM_DEVICE_ID` | Check for PCI vendor and device IDs that are VM-specific | 🐧 | 90% | | | | | |
547+
| `VM::QEMU_PASSTHROUGH` | Check for QEMU's hot-plug signature | 🪟 | 100% | | | | | |
548+
547549
<!-- ADD TECHNIQUE DETAILS HERE -->
548550

549551
<br>

src/cli.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ bool is_unsupported(VM::enum_flags flag) {
470470
case VM::FIRMWARE:
471471
case VM::UNKNOWN_MANUFACTURER:
472472
case VM::TPM:
473+
case VM::QEMU_PASSTHROUGH:
473474
// ADD WINDOWS FLAG
474475
return false;
475476
default: return true;
@@ -977,6 +978,8 @@ void general() {
977978
checker(VM::PCI_VM, "PCIe bridge ports");
978979
checker(VM::TPM, "TPM manufacturer");
979980
checker(VM::PCI_VM_DEVICE_ID, "PCI vendor/device ID");
981+
checker(VM::QEMU_PASSTHROUGH, "QEMU passthrough");
982+
980983
// ADD NEW TECHNIQUE CHECKER HERE
981984

982985
std::printf("\n");

src/vmaware.hpp

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@
366366
#include <wrl/client.h>
367367
#include <tbs.h>
368368
#include <mutex>
369+
#include <initguid.h>
370+
#include <devpkey.h>
371+
#include <devguid.h>
369372

370373
#pragma comment(lib, "winmm.lib")
371374
#pragma comment(lib, "setupapi.lib")
@@ -658,6 +661,7 @@ struct VM {
658661
PCI_VM,
659662
TPM,
660663
PCI_VM_DEVICE_ID,
664+
QEMU_PASSTHROUGH,
661665
// ADD NEW TECHNIQUE ENUM NAME HERE
662666

663667
// special flags, different to settings
@@ -2410,6 +2414,34 @@ struct VM {
24102414
}
24112415
}
24122416
}
2417+
2418+
[[nodiscard]] static bool is_running_under_emulator() {
2419+
#if (WINDOWS)
2420+
u8 ver = get_windows_version();
2421+
if (ver == 10 || ver == 11) {
2422+
USHORT procMachine = 0, nativeMachine = 0;
2423+
if (IsWow64Process2(GetCurrentProcess(), &procMachine, &nativeMachine)) {
2424+
if (nativeMachine == IMAGE_FILE_MACHINE_ARM64 &&
2425+
(procMachine == IMAGE_FILE_MACHINE_AMD64 ||
2426+
procMachine == IMAGE_FILE_MACHINE_I386))
2427+
{
2428+
return true;
2429+
}
2430+
}
2431+
}
2432+
#endif
2433+
2434+
if (cpu::is_leaf_supported(cpu::leaf::hypervisor)) {
2435+
std::string vendor = cpu::cpu_manufacturer(cpu::leaf::hypervisor);
2436+
if (vendor == "VirtualApple" || // Apple Rosetta
2437+
vendor == "PowerVM Lx86") // IBM PowerVM Lx86
2438+
{
2439+
return true;
2440+
}
2441+
}
2442+
2443+
return false;
2444+
}
24132445
#endif
24142446
};
24152447

@@ -3653,11 +3685,6 @@ struct VM {
36533685
/* GPL */ SP_DEVINFO_DATA DeviceInfoData{};
36543686
/* GPL */ DWORD i;
36553687
/* GPL */
3656-
/* GPL */ constexpr GUID GUID_DEVCLASS_DISKDRIVE = {
3657-
/* GPL */ 0x4d36e967L, 0xe325, 0x11ce,
3658-
/* GPL */ { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 }
3659-
/* GPL */ };
3660-
/* GPL */
36613688
/* GPL */ hDevInfo = SetupDiGetClassDevs((LPGUID)&GUID_DEVCLASS_DISKDRIVE,
36623689
/* GPL */ 0,
36633690
/* GPL */ 0,
@@ -7500,6 +7527,11 @@ struct VM {
75007527
return false;
75017528
}
75027529

7530+
if (util::is_running_under_emulator()) {
7531+
debug("Running inside binary translation layer.");
7532+
return false;
7533+
}
7534+
75037535
// to minimize context switching/scheduling
75047536
#if (WINDOWS)
75057537
const HANDLE hThread = GetCurrentThread();
@@ -7540,7 +7572,7 @@ struct VM {
75407572
// 1. TSC Synchronization Check Across Cores
75417573
// Try reading the invariant TSC on two different cores to attempt to detect vCPU timers being shared
75427574
constexpr u8 tscIterations = 10;
7543-
constexpr u16 tscSyncDiffThreshold = 5000;
7575+
constexpr u16 tscSyncDiffThreshold = 500;
75447576

75457577
bool tscSyncDetected = false;
75467578
tscSyncDetected = [&]() noexcept -> bool {
@@ -9985,6 +10017,69 @@ struct VM {
998510017
#endif
998610018
}
998710019

10020+
10021+
/**
10022+
* @brief Check for QEMU's hot-plug signature
10023+
* @category Windows
10024+
* @author Requiem (https://github.com/NotRequiem)
10025+
* @implements VM::QEMU_PASSTHROUGH
10026+
*/
10027+
[[nodiscard]] static bool qemu_passthrough()
10028+
{
10029+
#if (!WINDOWS)
10030+
return false;
10031+
#else
10032+
// QEMU passthrough location paths
10033+
static const std::wregex busRegex(L"PCIROOT\\(0\\)#PCI\\(0202\\)");
10034+
static const std::wregex acpiSlotRegex(L"#ACPI\\(S[0-9]{2}_\\)");
10035+
10036+
HDEVINFO hDevInfo = SetupDiGetClassDevsW(
10037+
&GUID_DEVCLASS_DISPLAY,
10038+
nullptr,
10039+
nullptr,
10040+
DIGCF_PRESENT);
10041+
if (hDevInfo == INVALID_HANDLE_VALUE)
10042+
return false;
10043+
10044+
SP_DEVINFO_DATA devInfo = {};
10045+
devInfo.cbSize = sizeof(devInfo);
10046+
const DEVPROPKEY key = DEVPKEY_Device_LocationPaths;
10047+
10048+
for (DWORD idx = 0; SetupDiEnumDeviceInfo(hDevInfo, idx, &devInfo); ++idx)
10049+
{
10050+
DEVPROPTYPE propType = 0;
10051+
DWORD requiredSize = 0;
10052+
10053+
SetupDiGetDevicePropertyW(
10054+
hDevInfo, &devInfo, &key, &propType,
10055+
nullptr, 0, &requiredSize, 0);
10056+
10057+
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || requiredSize == 0)
10058+
continue;
10059+
10060+
std::vector<BYTE> buffer(requiredSize);
10061+
if (!SetupDiGetDevicePropertyW(
10062+
hDevInfo, &devInfo, &key, &propType,
10063+
buffer.data(), requiredSize, &requiredSize, 0))
10064+
continue;
10065+
10066+
const wchar_t* ptr = reinterpret_cast<const wchar_t*>(buffer.data());
10067+
while (*ptr) {
10068+
std::wstring path(ptr);
10069+
if (std::regex_search(path, busRegex) ||
10070+
std::regex_search(path, acpiSlotRegex))
10071+
{
10072+
SetupDiDestroyDeviceInfoList(hDevInfo);
10073+
return true;
10074+
}
10075+
ptr += path.size() + 1;
10076+
}
10077+
}
10078+
10079+
SetupDiDestroyDeviceInfoList(hDevInfo);
10080+
return false;
10081+
#endif
10082+
}
998810083
// ADD NEW TECHNIQUE FUNCTION HERE
998910084

999010085

@@ -11280,8 +11375,8 @@ struct VM {
1128011375
case NSJAIL_PID: return "NSJAIL_PID";
1128111376
case PCI_VM: return "PCI_VM";
1128211377
case TPM: return "TPM";
11283-
1128411378
case PCI_VM_DEVICE_ID: return "PCI_VM_DEVICE_ID";
11379+
case QEMU_PASSTHROUGH: return "QEMU_PASSTHROUGH";
1128511380
// ADD NEW CASE HERE FOR NEW TECHNIQUE
1128611381
default: return "Unknown flag";
1128711382
}
@@ -11846,6 +11941,8 @@ std::pair<VM::enum_flags, VM::core::technique> VM::core::technique_list[] = {
1184611941
std::make_pair(VM::PCI_VM, VM::core::technique(100, VM::lspci)),
1184711942
std::make_pair(VM::TPM, VM::core::technique(50, VM::tpm)),
1184811943
std::make_pair(VM::PCI_VM_DEVICE_ID, VM::core::technique(90, VM::pci_vm_device_id)),
11944+
std::make_pair(VM::QEMU_PASSTHROUGH, VM::core::technique(90, VM::qemu_passthrough)),
11945+
1184911946
// ADD NEW TECHNIQUE STRUCTURE HERE
1185011947
};
1185111948

0 commit comments

Comments
 (0)