vm_detect/
lib.rs

1#![no_std]
2#![cfg(any(target_arch = "x86_64", target_arch = "x86"))]
3use core::arch::x86_64::{__cpuid, _rdtsc};
4
5bitflags::bitflags! {
6    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
7    pub struct Detection: u32 {
8        /// Measures the time/instruction count it takes to execute the cpuid instruction
9        const RDTSC = 0b00001;
10        /// Checks the hypervisor present bit in ecx after running cpuid leaf 1
11        const HYPERVISOR_BIT = 0b00010;
12        /// Checks if the CPU manufacturer id is of a known hypervisor
13        const HYPERVISOR_CPU_VENDOR = 0b00100;
14        /// Checks if the CPU manufacturer id is not GenuineIntel or AuthenticAMD
15        const UNEXPECTED_CPU_VENDOR = 0b01000;
16    }
17}
18
19const KNOWN_HYPERVISORS: [u32; 7] = [
20    fnv1(b"bhyve bhyve "),
21    fnv1(b"KVMKVMKVM\0\0\0"),
22    fnv1(b"TCGTCGTCGTCG"),
23    fnv1(b"Microsoft Hv"),
24    fnv1(b" lrpepyh  vr"),
25    fnv1(b"VMwareVMware"),
26    fnv1(b"XenVMMXenVMM"),
27];
28
29const EXPECTED_VENDORS: [u32; 2] = [fnv1(b"GenuineIntel"), fnv1(b"AuthenticAMD")];
30
31/// Run basic hypervisor detection
32///
33/// You can inspect the returned [`Detection`] to see which checks triggered (if any).
34pub fn vm_detect() -> Detection {
35    let mut detection = Detection::empty();
36
37    if rdtsc_detection() {
38        detection |= Detection::RDTSC;
39    }
40
41    if hypervisor_vendor().is_some() {
42        detection |= Detection::HYPERVISOR_BIT;
43    }
44
45    let vendor_hash = fnv1(&cpu_vendor());
46
47    if KNOWN_HYPERVISORS.iter().any(|&hash| hash == vendor_hash) {
48        detection |= Detection::HYPERVISOR_CPU_VENDOR;
49    } else if !EXPECTED_VENDORS.iter().any(|&hash| hash == vendor_hash) {
50        detection |= Detection::UNEXPECTED_CPU_VENDOR;
51    }
52
53    detection
54}
55
56#[inline(always)]
57const fn fnv1(data: &[u8; 12]) -> u32 {
58    let mut res: u32 = 0x811c9dc5;
59
60    let mut i = 0;
61    while i < data.len() {
62        res = res.wrapping_mul(0x01000193) ^ (data[i] as u32);
63
64        i += 1;
65    }
66
67    res
68}
69
70#[allow(dead_code)]
71fn brand_name() -> Option<[u8; 16 * 3]> {
72    if unsafe { __cpuid(0x80000000) }.eax < 0x80000004 {
73        return None;
74    }
75    let cpuids: [_; 3] = unsafe {
76        [
77            __cpuid(0x80000002),
78            __cpuid(0x80000003),
79            __cpuid(0x80000004),
80        ]
81    };
82
83    let mut result = [0; 16 * 3];
84
85    for (i, cpuid) in cpuids.into_iter().enumerate() {
86        for (j, data) in [cpuid.eax, cpuid.ebx, cpuid.ecx, cpuid.edx]
87            .into_iter()
88            .enumerate()
89        {
90            let index = i * 16 + j * 4;
91            result[index..index + 4].copy_from_slice(&data.to_le_bytes());
92        }
93    }
94
95    Some(result)
96}
97
98fn rdtsc_detection() -> bool {
99    const ITERATIONS: u64 = 1000;
100
101    let sum: u64 = (0..ITERATIONS)
102        .map(|_| unsafe {
103            let start = _rdtsc();
104            let _ = __cpuid(0);
105            let end = _rdtsc();
106            end - start
107        })
108        .sum();
109
110    let avg = sum / ITERATIONS;
111    avg > 500
112}
113
114fn cpu_vendor() -> [u8; 12] {
115    let cpuid = unsafe { __cpuid(0) };
116
117    let mut result = [0; 12];
118
119    for (i, data) in [cpuid.ebx, cpuid.edx, cpuid.ecx].into_iter().enumerate() {
120        let index = i * 4;
121        result[index..index + 4].copy_from_slice(&data.to_le_bytes());
122    }
123
124    result
125}
126
127fn hypervisor_vendor() -> Option<[u8; 12]> {
128    let processor_info = unsafe { __cpuid(1) };
129    // Check Hypervisor Present bit
130    if processor_info.ecx >> 31 & 1 == 0 {
131        return None;
132    }
133
134    let cpuid = unsafe { __cpuid(0x40000000) };
135
136    let mut result = [0; 12];
137
138    for (i, data) in [cpuid.ebx, cpuid.ecx, cpuid.edx].into_iter().enumerate() {
139        let index = i * 4;
140        result[index..index + 4].copy_from_slice(&data.to_le_bytes());
141    }
142
143    Some(result)
144}