inside_vm_arch_support/
lib.rs

1//! # inside-vm
2//!
3//! Detect if code is running inside a virtual machine.
4//!
5//! > Only works on x86 and x86-64.
6//!
7//! ## How does it work
8//!
9//! Measure average cpu cycles when calling [`cpuid`](https://en.wikipedia.org/wiki/CPUID) and compare to a threshold, if the value is high assume code is running inside a VM.
10//!
11//! ## Quick Start
12//!
13//! ```
14//! use inside_vm_arch_support::{inside_vm, inside_vm_custom, cpuid_cycle_count_avg};
15//!
16//! let inside = inside_vm();
17//! println!("inside vm: {}", inside);
18//!
19//! let inside = inside_vm_custom(5, 100, 5, 1200);
20//! println!("inside vm: {}", inside);
21//!
22//! let average_cpu_cyles = cpuid_cycle_count_avg(5, 100, 5);
23//! println!("average __cpuid cpu cycles: {}", average_cpu_cyles);
24//! ```
25//!
26//!## Credits
27//!
28//!https://evasions.checkpoint.com/techniques/timing.html#difference-vm-hosts
29
30#[cfg(target_arch = "x86")]
31use std::arch::x86::{CpuidResult, __cpuid, _rdtsc};
32#[cfg(target_arch = "x86_64")]
33use std::arch::x86_64::{CpuidResult, __cpuid, _rdtsc};
34
35// Arm not working
36// #[cfg(target_arch = "aarch64")]
37// use std::arch::aarch64::{CpuidResult, __cpuid, _rdtsc};
38
39// use std::arch::x86_64::{CpuidResult, __cpuid, _rdtsc};
40
41/// Compute cpuid cpu cycles average.
42///
43/// Perform `low + samples + high` measurements,
44/// discard `low` and `high` (outliers),
45/// compute average using the remaining `samples` measurements.
46///
47/// Prefer `inside_vm::inside_vm()` or `inside_vm::inside_vm_custom()`.
48///
49/// This function uses `unsafe`.
50///
51/// ```
52/// use inside_vm_arch_support::cpuid_cycle_count_avg;
53/// // perform 5 + 100 + 10 = 115 measurements
54/// // discard 5 lowest and 10 highest measurements
55/// // compute average over the 100 remaining measurements
56/// let avg = cpuid_cycle_count_avg(5, 100, 10);
57/// ```
58pub fn cpuid_cycle_count_avg(low: usize, samples: usize, high: usize) -> u64 {
59    let mut tsc1: u64;
60    let mut tsc2: u64;
61    let mut cycles: Vec<u64> = vec![];
62    let mut cpuid = CpuidResult {
63        eax: 0,
64        ebx: 0,
65        ecx: 0,
66        edx: 0,
67    };
68    for _ in 0..(low + samples + high) {
69        unsafe {
70            tsc1 = _rdtsc();
71            cpuid = __cpuid(0);
72            tsc2 = _rdtsc();
73        }
74        cycles.push(tsc2 - tsc1);
75    }
76    unsafe {
77        // call to __cpuid would be optimized away by the compiler in release mode
78        // if it were not for this call
79        std::ptr::read_volatile(&cpuid);
80    }
81
82    // remove low and high outliers, keep samples
83    cycles.sort_unstable();
84    let cycles_without_outliers = &cycles[low..low + samples];
85
86    // compute average cycle count without outliers, make sure we do not divide by zero
87    let avg = cycles_without_outliers.iter().sum::<u64>() / std::cmp::max(samples as u64, 1);
88    avg
89}
90
91/// Detect if inside vm by computing cpuid cpu cycles average and compare to `threshold`.
92///
93/// Perform `low + samples + high` measurements,
94/// discard `low` and `high` (outliers),
95/// compute average using the remaining `samples` measurements.
96///
97/// Compare average to `threshold`, if above return true else false.
98///
99/// Example
100/// ```
101/// use inside_vm_arch_support::inside_vm_custom;
102/// let inside: bool = inside_vm_arch_support::inside_vm_custom(5, 100, 5, 1_000);
103/// ```
104pub fn inside_vm_custom(low: usize, samples: usize, high: usize, threshold: u64) -> bool {
105    cpuid_cycle_count_avg(low, samples, high) > threshold
106}
107
108/// Compute cpuid cpu cycles average and compare to threshold.
109///
110/// Same as `inside_vm_custom(5, 100, 5, 1_000)`
111///
112/// Example:
113/// ```
114/// use inside_vm_arch_support::inside_vm;
115/// let inside: bool = inside_vm_arch_support::inside_vm();
116/// ```
117pub fn inside_vm() -> bool {
118    inside_vm_custom(5, 100, 5, 1_000)
119}
120
121#[cfg(test)]
122mod tests {
123    use crate::cpuid_cycle_count_avg;
124
125    #[test]
126    fn test_cpuid_cycle_count_avg() {
127        let avg = cpuid_cycle_count_avg(5, 100, 5);
128        assert!(avg < 1000); // may fail if test is run on a VM
129    }
130}