From ff5aafb5f423272d2040182f79d847446ad0d748 Mon Sep 17 00:00:00 2001 From: Xorg Date: Thu, 15 Sep 2022 21:24:43 +0200 Subject: [PATCH] Detect Virtual Machine Fix #90 --- cpuid_tool/cpuid_tool.c | 37 +++++++++++++++++++++++++ libcpuid/cpuid_main.c | 61 +++++++++++++++++++++++++++++++++++++++++ libcpuid/exports.def | 1 + libcpuid/libcpuid.h | 37 +++++++++++++++++++++++++ libcpuid/libcpuid.sym | 1 + 5 files changed, 137 insertions(+) diff --git a/cpuid_tool/cpuid_tool.c b/cpuid_tool/cpuid_tool.c index cb80a38..0bf7640 100644 --- a/cpuid_tool/cpuid_tool.c +++ b/cpuid_tool/cpuid_tool.c @@ -108,6 +108,7 @@ int need_input = 0, need_version = 0, need_cpulist = 0, need_sgx = 0, + need_hypervisor = 0, need_identify = 0; #define MAX_REQUESTS 32 @@ -178,6 +179,7 @@ static void usage(void) printf(" --clock-rdtsc - same as --clock, but use RDTSC for clock detection\n"); printf(" --cpulist - list all known CPUs\n"); printf(" --sgx - list SGX leaf data, if SGX is supported.\n"); + printf(" --hypervisor - print hypervisor vendor if detected.\n"); printf(" --quiet - disable warnings\n"); printf(" --outfile= - redirect all output to this file, instead of stdout\n"); printf(" --verbose, -v - be extra verbose (more keys increase verbosiness level)\n"); @@ -297,6 +299,11 @@ static int parse_cmdline(int argc, char** argv) need_identify = 1; recog = 1; } + if (!strcmp(arg, "--hypervisor")) { + need_hypervisor = 1; + need_identify = 1; + recog = 1; + } if (arg[0] == '-' && arg[1] == 'v') { num_vs = 1; while (arg[num_vs] == 'v') @@ -556,6 +563,33 @@ static void print_sgx_data(const struct cpu_raw_data_t* raw, const struct cpu_id } } +static void print_hypervisor(struct cpu_raw_data_t* raw, struct cpu_id_t* data) +{ + int i; + const char *hypervisor_str = NULL; + const hypervisor_vendor_t hypervisor = cpuid_get_hypervisor(raw, data); + const struct { const char *name; hypervisor_vendor_t hypervisor; } hypervisors_vendors[NUM_HYPERVISOR_VENDORS] = { + { "none", HYPERVISOR_NONE }, + { "FreeBSD bhyve", HYPERVISOR_BHYVE }, + { "Microsoft Hyper-V ", HYPERVISOR_HYPERV }, + { "KVM", HYPERVISOR_KVM }, + { "Parallels", HYPERVISOR_PARALLELS }, + { "QEMU", HYPERVISOR_QEMU }, + { "VirtualBox", HYPERVISOR_VIRTUALBOX }, + { "VMware", HYPERVISOR_VMWARE }, + { "Xen", HYPERVISOR_XEN }, + }; + for (i = 0; i < NUM_HYPERVISOR_VENDORS; i++) + if (hypervisors_vendors[i].hypervisor == hypervisor) { + hypervisor_str = hypervisors_vendors[i].name; + break; + } + fprintf(fout, "Hypervisor vendor: %s\n", (hypervisor_str == NULL) ? "unknown" : hypervisor_str); + if (hypervisor == HYPERVISOR_NONE) + fprintf(fout, "Caution: no hypervisor detected from CPUID bits, but a hypervisor may be hidden.\n" + "Refer to https://github.com/anrieff/libcpuid/issues/90#issuecomment-296568713\n"); +} + int main(int argc, char** argv) { int parseres = parse_cmdline(argc, argv); @@ -762,6 +796,9 @@ int main(int argc, char** argv) if (need_sgx) { print_sgx_data(&raw_array.raw[0], &data.cpu_types[0]); } + if (need_hypervisor) { + print_hypervisor(&raw_array.raw[0], &data.cpu_types[0]); + } cpuid_free_raw_data_array(&raw_array); cpuid_free_system_id(&data); diff --git a/libcpuid/cpuid_main.c b/libcpuid/cpuid_main.c index 40b8963..dd60a71 100644 --- a/libcpuid/cpuid_main.c +++ b/libcpuid/cpuid_main.c @@ -568,6 +568,7 @@ static void load_features_common(struct cpu_raw_data_t* raw, struct cpu_id_t* da { 28, CPU_FEATURE_AVX }, { 29, CPU_FEATURE_F16C }, { 30, CPU_FEATURE_RDRAND }, + { 31, CPU_FEATURE_HYPERVISOR }, }; const struct feature_map_t matchtable_ebx7[] = { { 3, CPU_FEATURE_BMI1 }, @@ -1180,6 +1181,7 @@ const char* cpu_feature_str(cpu_feature_t feature) { CPU_FEATURE_AVX512VNNI, "avx512vnni" }, { CPU_FEATURE_AVX512VBMI, "avx512vbmi" }, { CPU_FEATURE_AVX512VBMI2, "avx512vbmi2" }, + { CPU_FEATURE_HYPERVISOR, "hypervisor" }, }; unsigned i, n = COUNT_OF(matchtable); if (n != NUM_CPU_FEATURES) { @@ -1299,6 +1301,65 @@ void cpuid_get_cpu_list(cpu_vendor_t vendor, struct cpu_list_t* list) } } +hypervisor_vendor_t cpuid_get_hypervisor(struct cpu_raw_data_t* raw, struct cpu_id_t* data) +{ + int i, r; + uint32_t hypervisor_fn40000000h[NUM_REGS]; + char hypervisor_str[VENDOR_STR_MAX]; + struct cpu_id_t mydata; + const struct { hypervisor_vendor_t hypervisor; char match[16]; } + matchtable[NUM_HYPERVISOR_VENDORS] = { + /* source: https://github.com/a0rtega/pafish/blob/master/pafish/cpu.c */ + { HYPERVISOR_BHYVE , "bhyve bhyve\0" }, + { HYPERVISOR_HYPERV , "Microsoft Hv" }, + { HYPERVISOR_KVM , "KVMKVMKVM\0\0\0" }, + { HYPERVISOR_PARALLELS , "prl hyperv\0\0" }, + { HYPERVISOR_QEMU , "TCGTCGTCGTCG" }, + { HYPERVISOR_VIRTUALBOX , "VBoxVBoxVBox" }, + { HYPERVISOR_VMWARE , "VMwareVMware" }, + { HYPERVISOR_XEN , "XenVMMXenVMM" }, + }; + + if (!data) { + if ((r = cpu_identify(raw, data)) < 0) + return HYPERVISOR_UNKNOWN; + data = &mydata; + } + + /* Intel and AMD CPUs have reserved bit 31 of ECX of CPUID leaf 0x1 as the hypervisor present bit + Source: https://kb.vmware.com/s/article/1009458 */ + switch (data->vendor) { + case VENDOR_AMD: + case VENDOR_INTEL: + break; + default: + return HYPERVISOR_UNKNOWN; + } + if (!data->flags[CPU_FEATURE_HYPERVISOR]) + return HYPERVISOR_NONE; + + /* Intel and AMD have also reserved CPUID leaves 0x40000000 - 0x400000FF for software use. + Hypervisors can use these leaves to provide an interface to pass information + from the hypervisor to the guest operating system running inside a virtual machine. + The hypervisor bit indicates the presence of a hypervisor + and that it is safe to test these additional software leaves. */ + memset(hypervisor_fn40000000h, 0, sizeof(hypervisor_fn40000000h)); + hypervisor_fn40000000h[EAX] = 0x40000000; + cpu_exec_cpuid_ext(hypervisor_fn40000000h); + + /* Copy the hypervisor CPUID information leaf */ + memcpy(hypervisor_str + 0, &hypervisor_fn40000000h[1], 4); + memcpy(hypervisor_str + 4, &hypervisor_fn40000000h[2], 4); + memcpy(hypervisor_str + 8, &hypervisor_fn40000000h[3], 4); + hypervisor_str[12] = '\0'; + + /* Determine hypervisor */ + for (i = 0; i < NUM_HYPERVISOR_VENDORS; i++) + if (!strcmp(hypervisor_str, matchtable[i].match)) + return matchtable[i].hypervisor; + return HYPERVISOR_UNKNOWN; +} + void cpuid_free_cpu_list(struct cpu_list_t* list) { int i; diff --git a/libcpuid/exports.def b/libcpuid/exports.def index 05fd0e7..8472934 100644 --- a/libcpuid/exports.def +++ b/libcpuid/exports.def @@ -44,3 +44,4 @@ cpuid_free_raw_data_array @40 affinity_mask_str @41 cpuid_free_system_id @42 affinity_mask_str_r @43 +cpuid_get_hypervisor @44 diff --git a/libcpuid/libcpuid.h b/libcpuid/libcpuid.h index e8c5295..9de308d 100644 --- a/libcpuid/libcpuid.h +++ b/libcpuid/libcpuid.h @@ -148,6 +148,24 @@ typedef enum { } cpu_purpose_t; #define NUM_CPU_PURPOSES NUM_CPU_PURPOSES +/** + * @brief Hypervisor vendor, as guessed from the CPU_FEATURE_HYPERVISOR flag. + */ +typedef enum { + HYPERVISOR_NONE = 0, /*!< no hypervisor */ + HYPERVISOR_BHYVE, /*!< FreeBSD bhyve hypervisor */ + HYPERVISOR_HYPERV, /*!< Microsoft Hyper-V or Windows Virtual PC hypervisor */ + HYPERVISOR_KVM, /*!< KVM hypervisor */ + HYPERVISOR_PARALLELS, /*!< Parallels hypervisor */ + HYPERVISOR_QEMU, /*!< QEMU hypervisor */ + HYPERVISOR_VIRTUALBOX, /*!< VirtualBox hypervisor */ + HYPERVISOR_VMWARE, /*!< VMware hypervisor */ + HYPERVISOR_XEN, /*!< Xen hypervisor */ + NUM_HYPERVISOR_VENDORS, /*!< Valid hypervisor vendor ids: 0..NUM_HYPERVISOR_VENDORS - 1 */ + HYPERVISOR_UNKNOWN = -1, +} hypervisor_vendor_t; +#define NUM_HYPERVISOR_VENDORS NUM_HYPERVISOR_VENDORS + /** * @brief Contains just the raw CPUID data. * @@ -591,6 +609,7 @@ typedef enum { CPU_FEATURE_AVX512VNNI, /*!< AVX-512 Vector Neural Network Instructions */ CPU_FEATURE_AVX512VBMI, /*!< AVX-512 Vector Bit ManipulationInstructions (version 1) */ CPU_FEATURE_AVX512VBMI2, /*!< AVX-512 Vector Bit ManipulationInstructions (version 2) */ + CPU_FEATURE_HYPERVISOR, /*!< Hypervisor present (always zero on physical CPUs) */ /* termination: */ NUM_CPU_FEATURES, } cpu_feature_t; @@ -1167,6 +1186,24 @@ void cpuid_set_verbosiness_level(int level); */ cpu_vendor_t cpuid_get_vendor(void); +/** + * @brief Obtains the hypervisor vendor from CPUID from the current CPU + * @param raw - Optional input - a pointer to the raw CPUID data, which is obtained + * either by cpuid_get_raw_data or cpuid_deserialize_raw_data. + * Can also be NULL, in which case the functions calls + * cpuid_get_raw_data itself. + * @param data - Optional input - the decoded CPU features/info is written here. + * Can also be NULL, in which case the functions calls + * cpu_identify itself. + * @note If no hypervisor is detected, the hypervisor can be hidden in some cases. + * Refer to https://github.com/anrieff/libcpuid/issues/90#issuecomment-296568713. + * @returns HYPERVISOR_UNKNOWN if failed, + * HYPERVISOR_NONE if no hypervisor detected (or hidden), + * otherwise the hypervisor vendor type. + * @see hypervisor_vendor_t + */ +hypervisor_vendor_t cpuid_get_hypervisor(struct cpu_raw_data_t* raw, struct cpu_id_t* data); + /** * @brief a structure that holds a list of processor names */ diff --git a/libcpuid/libcpuid.sym b/libcpuid/libcpuid.sym index c9a2e2d..68fbcf3 100644 --- a/libcpuid/libcpuid.sym +++ b/libcpuid/libcpuid.sym @@ -41,3 +41,4 @@ cpuid_free_raw_data_array affinity_mask_str cpuid_free_system_id affinity_mask_str_r +cpuid_get_hypervisor