Skip to content

Commit 5b0c35b

Browse files
authored
Merge pull request #537 from NotRequiem/dev
feat: improved RDTSC trap check
2 parents 46af3be + 5b44495 commit 5b0c35b

1 file changed

Lines changed: 16 additions & 11 deletions

File tree

src/vmaware.hpp

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4391,6 +4391,12 @@ struct VM {
43914391
cycleThreshold = 25000; // if we're running under Hyper-V, attempt to detect nested virtualization only
43924392
}
43934393

4394+
#if (WINDOWS)
4395+
const HANDLE th = GetCurrentThread();
4396+
const DWORD_PTR wantedMask = (DWORD_PTR)1;
4397+
const DWORD_PTR prevMask = SetThreadAffinityMask(th, wantedMask);
4398+
#endif
4399+
43944400
// Case A - Hypervisor without RDTSC patch
43954401
thread_local unsigned int aux = 0;
43964402
// Check for RDTSCP support
@@ -4451,13 +4457,13 @@ struct VM {
44514457
if (!QueryPerformanceFrequency(&freq)) // NtPowerInformation and NtQueryPerformanceCounter are avoided as some hypervisors downscale tsc only if we triggered a context switch from userspace
44524458
return false;
44534459

4454-
// calculates the invariant TSC base rate, not the dynamic core frequency, similar to what CallNtPowerInformation would give you
4460+
// calculates the invariant TSC base rate (on modern CPUs), not the dynamic core frequency, similar to what CallNtPowerInformation would give you
44554461
LARGE_INTEGER t1q, t2q;
44564462
const u64 t1 = __rdtsc();
44574463
QueryPerformanceCounter(&t1q); // uses RDTSCP under the hood unless platformclock (a bcdedit setting) is set, which then would use HPET or ACPI PM via NtQueryPerformanceCounter
44584464
SleepEx(50, 0);
44594465
QueryPerformanceCounter(&t2q);
4460-
const u64 t2 = __rdtsc();
4466+
const u64 t2 = __rdtscp(&aux);
44614467

44624468
const double elapsedSec = double(t2q.QuadPart - t1q.QuadPart) / double(freq.QuadPart); // the performance counter frequency is always 10MHz when running under Hyper-V
44634469
const double tscHz = double(t2 - t1) / elapsedSec;
@@ -4478,16 +4484,13 @@ struct VM {
44784484
}
44794485
else {
44804486
debug("TIMER: Processor base speed -> ", static_cast<double>(baseMHz), " MHz");
4487+
// this -650 delta accounts for older CPUs, it's better to use this rather than calling CPUID to know if the CPU supports invariant TSC, as it can be spoofed
44814488
if (tscMHz <= static_cast<double>(baseMHz) - 650.0) {
44824489
return true;
44834490
}
44844491
}
44854492

44864493
// Case C - Hypervisor with RDTSC patch + useplatformclock = false
4487-
const HANDLE th = GetCurrentThread();
4488-
const DWORD_PTR wantedMask = (DWORD_PTR)1;
4489-
const DWORD_PTR prevMask = SetThreadAffinityMask(th, wantedMask);
4490-
44914494
const ULONG64 count_first = 20000000ULL;
44924495
const ULONG64 count_second = 200000000ULL;
44934496
static thread_local volatile unsigned long long g_sink = 0; // so that it doesnt need to be captured by the lambda
@@ -4509,7 +4512,7 @@ struct VM {
45094512
using fn_t = unsigned long long (*)();
45104513

45114514
// make the pointer volatile so the compiler treats the call as opaque/indirect
4512-
volatile fn_t rd_ptr = +rd_lambda; // +lambda forces conversion to function ptr, so it won't be inlined, we need this for the trick
4515+
volatile fn_t rd_ptr = +rd_lambda; // +lambda forces conversion to function ptr, so it won't be inlined, we need this to prevent some optimizatons by the compiler
45134516
volatile fn_t xor_ptr = +xor_lambda;
45144517

45154518
ULONG64 beforeqit = 0;
@@ -4541,15 +4544,17 @@ struct VM {
45414544

45424545
const ULONG64 secondRatio = (aftertsc2 - beforetsc2) / (afterqit2 - beforeqit2);
45434546

4544-
ULONG64 diff = firstRatio - secondRatio;
4545-
ULONG64 difference = (diff ^ ((LONG64)diff >> 63)) - ((LONG64)diff >> 63);
4547+
const ULONG64 diff = firstRatio - secondRatio;
4548+
const ULONG64 difference = (diff ^ ((LONG64)diff >> 63)) - ((LONG64)diff >> 63);
4549+
4550+
debug("TIMER: RDTSC -> ", firstRatio, ", QIT -> ", secondRatio, ", Ratio: ", difference);
45464551

45474552
if (prevMask != 0) {
45484553
SetThreadAffinityMask(th, prevMask);
45494554
}
45504555

4551-
if (difference > 25) {
4552-
return true; // both ratios will always differ since the hypervisor can't account for the XOR loop
4556+
if (difference > 20) {
4557+
return true; // both ratios will always differ under a RDTSC trap since the hypervisor can't account for the XOR loop
45534558
}
45544559
// TLB flushes or side channel cache attacks are not even tried due to how ineffective they are against stealthy hypervisors
45554560
#endif

0 commit comments

Comments
 (0)