Skip to main content

vmaware/
util.rs

1//! Utility helpers – mirrors vmaware_util.c.
2//!
3//! Platform checks, file helpers, admin detection, hyper-x classification,
4//! thread counting and string utilities.
5
6use crate::types::{HyperXState, Technique};
7use crate::{cpu, memo};
8
9// ── Platform checks ───────────────────────────────────────────────────────────
10
11/// Returns true when the given technique is unsupported on the current platform
12/// (so the caller should skip it without counting it as a VM indicator).
13pub fn is_unsupported(tech: Technique) -> bool {
14    use Technique::*;
15
16    #[cfg(windows)]
17    {
18        // Linux / macOS only
19        const LINUX_ONLY: &[Technique] = &[
20            SmbiosVmBit, Kmsg, Cvendor, QemuFwCfg, Systemd, Ctype, Dockerenv,
21            Dmidecode, Dmesg, Hwmon, LinuxUserHost, VmwareIomem, VmwareIoports,
22            VmwareScsi, VmwareDmesg, QemuVirtualDmi, QemuUsb, HypervisorDir,
23            UmlCpu, VboxModule, SysinfoProc, DmiScan, PodmanFile, WslProc,
24            FileAccessHistory, Mac, NsjailPid, BluestacksFolders, AmdSevMsr,
25            Temperature, Processes, ThreadCount,
26            MacMemsize, MacIokit, MacSip, IoregGrep, Hwmodel, MacSys,
27        ];
28        return LINUX_ONLY.contains(&tech);
29    }
30
31    #[cfg(all(target_os = "linux", not(windows)))]
32    {
33        // Windows-only
34        const WIN_ONLY: &[Technique] = &[
35            GpuCapabilities, AcpiSignature, PowerCapabilities, DiskSerial,
36            Ivshmem, Drivers, Handles, VirtualProcessors, HypervisorQuery,
37            Audio, Display, Dll, VmwareBackdoor, Wine, VirtualRegistry,
38            Mutex, DeviceString, VpcInvalid, VmwareStr, Gamarue, CuckooDir,
39            CuckooPipe, BootLogo, Trap, Ud, Blockstep, DbvmHypercall,
40            KernelObjects, Nvram, Edid, CpuHeuristic, Clock, Msr,
41            KvmInterception, Breakpoint,
42            MacMemsize, MacIokit, MacSip, IoregGrep, Hwmodel, MacSys,
43        ];
44        return WIN_ONLY.contains(&tech);
45    }
46
47    #[cfg(target_os = "macos")]
48    {
49        const MACOS_EXCL: &[Technique] = &[
50            GpuCapabilities, AcpiSignature, PowerCapabilities, DiskSerial,
51            Ivshmem, Drivers, Handles, VirtualProcessors, HypervisorQuery,
52            Audio, Display, Dll, VmwareBackdoor, Wine, VirtualRegistry,
53            Mutex, DeviceString, VpcInvalid, VmwareStr, Gamarue, CuckooDir,
54            CuckooPipe, BootLogo, Trap, Ud, Blockstep, DbvmHypercall,
55            KernelObjects, Nvram, Edid, CpuHeuristic, Clock, Msr,
56            KvmInterception, Breakpoint,
57            SmbiosVmBit, Kmsg, Cvendor, QemuFwCfg, Systemd, Ctype, Dockerenv,
58            Dmidecode, Dmesg, Hwmon, LinuxUserHost, VmwareIomem, VmwareIoports,
59            VmwareScsi, VmwareDmesg, QemuVirtualDmi, QemuUsb, HypervisorDir,
60            UmlCpu, VboxModule, SysinfoProc, DmiScan, PodmanFile, WslProc,
61            FileAccessHistory, Mac, NsjailPid, BluestacksFolders, AmdSevMsr,
62            Temperature, Processes,
63        ];
64        return MACOS_EXCL.contains(&tech);
65    }
66
67    // Fallback for unknown platforms (cross-platform only)
68    #[allow(unreachable_code)]
69    {
70        let cross = [
71            Technique::HypervisorBit, Technique::Vmid, Technique::ThreadMismatch,
72            Technique::Timer, Technique::CpuBrand, Technique::HypervisorStr,
73            Technique::CpuidSignature, Technique::BochsCpu, Technique::KgtSignature,
74        ];
75        !cross.contains(&tech)
76    }
77}
78
79// ── File helpers ──────────────────────────────────────────────────────────────
80
81/// Returns true when `path` exists on the filesystem.
82pub fn exists(path: &str) -> bool {
83    std::path::Path::new(path).exists()
84}
85
86/// Reads a file to a String. Returns `None` on any error.
87pub fn read_file(path: &str) -> Option<String> {
88    std::fs::read_to_string(path).ok()
89}
90
91/// Returns true when a file exists and its contents contain `needle`.
92pub fn file_contains(path: &str, needle: &str) -> bool {
93    read_file(path).map(|c| c.contains(needle)).unwrap_or(false)
94}
95
96// ── Admin / elevated privilege check ─────────────────────────────────────────
97
98/// Returns true if the current process has administrator / root privileges.
99pub fn is_admin() -> bool {
100    #[cfg(windows)]
101    {
102        use windows_sys::Win32::Security::{
103            GetTokenInformation, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation,
104        };
105        use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
106        use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
107
108        unsafe {
109            let mut token: HANDLE = 0;
110            if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
111                return false;
112            }
113            let mut elevation = TOKEN_ELEVATION { TokenIsElevated: 0 };
114            let mut ret_len: u32 = 0;
115            let ok = GetTokenInformation(
116                token,
117                TokenElevation,
118                &mut elevation as *mut _ as *mut _,
119                std::mem::size_of::<TOKEN_ELEVATION>() as u32,
120                &mut ret_len,
121            );
122            CloseHandle(token);
123            ok != 0 && elevation.TokenIsElevated != 0
124        }
125    }
126
127    #[cfg(not(windows))]
128    {
129        // POSIX: check for UID 0
130        libc_uid() == 0
131    }
132}
133
134#[cfg(not(windows))]
135fn libc_uid() -> u32 {
136    // Use std::os::unix equivalent
137    #[cfg(unix)]
138    {
139        // SAFETY: getuid() is always safe to call
140        extern "C" {
141            fn getuid() -> u32;
142        }
143        unsafe { getuid() }
144    }
145    #[cfg(not(unix))]
146    {
147        1 // assume non-root on unknown platforms
148    }
149}
150
151// ── SMT / logical CPU count ───────────────────────────────────────────────────
152
153/// Return the number of logical processors (hardware threads) visible to the OS.
154pub fn get_logical_cpu_count() -> u32 {
155    #[cfg(windows)]
156    {
157        use windows_sys::Win32::System::SystemInformation::GetSystemInfo;
158        use windows_sys::Win32::System::SystemInformation::SYSTEM_INFO;
159        unsafe {
160            let mut si = std::mem::zeroed::<SYSTEM_INFO>();
161            GetSystemInfo(&mut si);
162            si.dwNumberOfProcessors
163        }
164    }
165
166    #[cfg(not(windows))]
167    {
168        // Use std::thread::available_parallelism as a portable fallback
169        std::thread::available_parallelism()
170            .map(|n| n.get() as u32)
171            .unwrap_or(1)
172    }
173}
174
175// ── HyperX detection ─────────────────────────────────────────────────────────
176
177/// Classify the current execution environment as real VM, artifact VM,
178/// enlightened hypervisor host, or unknown.
179pub fn hyper_x() -> HyperXState {
180    // Return cached value if available
181    if let Some(s) = memo::get_hyperx_state() {
182        return s;
183    }
184
185    let state = hyper_x_inner();
186    memo::set_hyperx_state(state);
187    state
188}
189
190fn hyper_x_inner() -> HyperXState {
191    #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
192    return HyperXState::Unknown;
193
194    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
195    {
196        use crate::cpu::{cpuid, is_leaf_supported};
197
198        // 1. Check hypervisor bit (CPUID 1 ECX bit 31)
199        let ecx1 = cpuid(1, 0).ecx;
200        let hypervisor_bit = (ecx1 >> 31) & 1 != 0;
201
202        if !hypervisor_bit {
203            return HyperXState::Unknown;
204        }
205
206        // 2. Check if root partition (Hyper-V enlightened host)
207        //    CPUID 0x40000003, EBX bit 0 = "CreatePartitions" privilege.
208        if is_leaf_supported(0x4000_0003) {
209            let ebx = cpuid(0x4000_0003, 0).ebx;
210            if ebx & 1 != 0 {
211                return HyperXState::Enlightenment;
212            }
213        }
214
215        // 3. Distinguish real VM vs artifact by max hypervisor leaf
216        let max_leaf = cpuid(0x4000_0000, 0).eax;
217
218        if max_leaf >= 0x4000_0005 {
219            // Rich leaf set → more likely a full VM
220            HyperXState::RealVM
221        } else {
222            HyperXState::ArtifactVM
223        }
224    }
225}
226
227// ── BIOS / manufacturer / model helpers ───────────────────────────────────────
228
229/// Return (manufacturer, model) from the hardware description registry (Windows)
230/// or DMI/SMBIOS on Linux.
231pub fn get_manufacturer_model() -> (String, String) {
232    // Return cached value
233    if let Some(info) = memo::get_bios_info() {
234        return (info.manufacturer, info.model);
235    }
236
237    let result = get_manufacturer_model_inner();
238    memo::set_bios_info(memo::BiosInfo {
239        manufacturer: result.0.clone(),
240        model: result.1.clone(),
241    });
242    result
243}
244
245#[cfg(windows)]
246fn get_manufacturer_model_inner() -> (String, String) {
247    use winreg::enums::*;
248    use winreg::RegKey;
249
250    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
251    if let Ok(key) = hklm.open_subkey("HARDWARE\\DESCRIPTION\\System\\BIOS") {
252        let mfr: String = key.get_value("SystemManufacturer").unwrap_or_default();
253        let mdl: String = key.get_value("SystemProductName").unwrap_or_default();
254        return (mfr, mdl);
255    }
256    (String::new(), String::new())
257}
258
259#[cfg(not(windows))]
260fn get_manufacturer_model_inner() -> (String, String) {
261    let mfr = read_file("/sys/class/dmi/id/sys_vendor")
262        .unwrap_or_default()
263        .trim()
264        .to_string();
265    let mdl = read_file("/sys/class/dmi/id/product_name")
266        .unwrap_or_default()
267        .trim()
268        .to_string();
269    (mfr, mdl)
270}
271
272// ── String helpers ────────────────────────────────────────────────────────────
273
274/// Case-insensitive substring search.
275pub fn str_contains_ci(haystack: &str, needle: &str) -> bool {
276    haystack.to_lowercase().contains(&needle.to_lowercase())
277}
278
279/// CRC32C (software) of a byte slice.
280pub fn crc32c(data: &[u8]) -> u32 {
281    const POLY: u32 = 0x82F6_3B78;
282    let mut crc: u32 = 0xFFFF_FFFF;
283    for &byte in data {
284        crc ^= byte as u32;
285        for _ in 0..8 {
286            if crc & 1 != 0 {
287                crc = (crc >> 1) ^ POLY;
288            } else {
289                crc >>= 1;
290            }
291        }
292    }
293    !crc
294}
295
296// ── SMT detection ─────────────────────────────────────────────────────────────
297
298/// Returns true when SMT (Hyper-Threading / simultaneous multithreading) is
299/// enabled on the current x86 processor.
300#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
301pub fn is_smt_enabled() -> bool {
302    use cpu::{cpuid, is_leaf_supported};
303    if !is_leaf_supported(0x0B) {
304        return false;
305    }
306    // Leaf 0x0B, subleaf 0 reports SMT level thread count
307    let r = cpuid(0x0B, 0);
308    let level_type = (r.ecx >> 8) & 0xFF;
309    let threads_at_level = r.ebx & 0xFFFF;
310    // level_type == 1 is SMT level
311    level_type == 1 && threads_at_level > 1
312}
313
314#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
315pub fn is_smt_enabled() -> bool {
316    false
317}