From 43bbfabc346329210bcb148823c8f0c833dad3dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Wed, 22 Apr 2026 00:08:11 +0200 Subject: [PATCH] hw: detect VM guest via hypervisor CPUID flag, warn on unreliable microcode Addresses issue #336: when running inside a VM (KVM, VMware, ESXi, Hyper-V, VirtualBox), the hypervisor can present a fake CPUID and microcode version to the guest, making the microcode up-to-date check meaningless or misleading. Changes: - Add is_running_as_guest() to 370_hw_vmm.sh: detects VM guest status by checking for the 'hypervisor' CPUID flag in /proc/cpuinfo, which is exposed by KVM, VMware, Hyper-V, VirtualBox and most other hypervisors. Result is cached in g_is_guest_vm / g_is_guest_vm_reason. - Add "Running as VM guest: YES/NO" line to the CPU details block in check_cpu() (400_hw_check.sh), shown for both x86 and ARM guests. - Add a pr_warn block after the microcode-is-latest check in check_cpu() advising the user to verify microcode information on the hypervisor host when a VM guest is detected. - Add minimal ARM CPU details block in check_cpu(): vendor, model name, implementer(s), part(s), architecture(s), and VM guest status. ARM CPUs previously got no output from check_cpu() due to the x86-only early return guard. - Expose guest VM status in JSON output (250_output_emitters.sh): - system section: guest_vm (bool) and guest_vm_reason (string) - cpu_microcode section: unreliable_in_vm (bool) --- src/libs/250_output_emitters.sh | 12 ++++++---- src/libs/370_hw_vmm.sh | 18 +++++++++++++++ src/libs/400_hw_check.sh | 40 +++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/libs/250_output_emitters.sh b/src/libs/250_output_emitters.sh index 1c8db79..6f0a171 100644 --- a/src/libs/250_output_emitters.sh +++ b/src/libs/250_output_emitters.sh @@ -110,7 +110,8 @@ _build_json_system() { 1) smt_val='false' ;; *) smt_val='null' ;; esac - g_json_system=$(printf '{"kernel_release":%s,"kernel_version":%s,"kernel_arch":%s,"kernel_image":%s,"kernel_config":%s,"kernel_version_string":%s,"kernel_cmdline":%s,"cpu_count":%s,"smt_enabled":%s,"hypervisor_host":%s,"hypervisor_host_reason":%s}' \ + is_running_as_guest || true + g_json_system=$(printf '{"kernel_release":%s,"kernel_version":%s,"kernel_arch":%s,"kernel_image":%s,"kernel_config":%s,"kernel_version_string":%s,"kernel_cmdline":%s,"cpu_count":%s,"smt_enabled":%s,"hypervisor_host":%s,"hypervisor_host_reason":%s,"guest_vm":%s,"guest_vm_reason":%s}' \ "$(_json_str "$kernel_release")" \ "$(_json_str "$kernel_version")" \ "$(_json_str "$kernel_arch")" \ @@ -121,7 +122,9 @@ _build_json_system() { "$(_json_num "${g_max_core_id:+$((g_max_core_id + 1))}")" \ "$smt_val" \ "$(_json_bool "${g_has_vmm:-}")" \ - "$(_json_str "${g_has_vmm_reason:-}")") + "$(_json_str "${g_has_vmm_reason:-}")" \ + "$(_json_bool "${g_is_guest_vm:-}")" \ + "$(_json_str "${g_is_guest_vm_reason:-}")") } # Build the "cpu" section of the comprehensive JSON output @@ -262,14 +265,15 @@ _build_json_cpu_microcode() { blacklisted='false' fi latest_hex="${ret_is_latest_known_ucode_version:-}" - g_json_cpu_microcode=$(printf '{"installed_version":%s,"latest_version":%s,"microcode_up_to_date":%s,"is_blacklisted":%s,"message":%s,"db_source":%s,"db_info":%s}' \ + g_json_cpu_microcode=$(printf '{"installed_version":%s,"latest_version":%s,"microcode_up_to_date":%s,"is_blacklisted":%s,"message":%s,"db_source":%s,"db_info":%s,"unreliable_in_vm":%s}' \ "$(_json_str "$ucode_hex")" \ "$(_json_str "$latest_hex")" \ "$ucode_uptodate" \ "$blacklisted" \ "$(_json_str "${ret_is_latest_known_ucode_latest:-}")" \ "$(_json_str "${g_mcedb_source:-}")" \ - "$(_json_str "${g_mcedb_info:-}")") + "$(_json_str "${g_mcedb_info:-}")" \ + "$(_json_bool "${g_is_guest_vm:-}")") } # --- Format-specific batch emitters --- diff --git a/src/libs/370_hw_vmm.sh b/src/libs/370_hw_vmm.sh index f5a8516..e4ebf63 100644 --- a/src/libs/370_hw_vmm.sh +++ b/src/libs/370_hw_vmm.sh @@ -55,3 +55,21 @@ is_xen_domU() { return 1 fi } + +# Check whether the system is running as a guest inside a virtual machine. +# Uses the 'hypervisor' CPUID feature flag exposed in /proc/cpuinfo by KVM, +# VMware, Hyper-V, VirtualBox, and most other type-1 and type-2 hypervisors. +# Returns: 0 if running as a VM guest, 1 otherwise +# Sets: g_is_guest_vm (1=guest, 0=not a guest), g_is_guest_vm_reason +is_running_as_guest() { + if [ "${g_is_guest_vm_cached:-0}" != 1 ]; then + g_is_guest_vm=0 + g_is_guest_vm_reason='' + if [ -e "$g_procfs/cpuinfo" ] && grep -qw 'hypervisor' "$g_procfs/cpuinfo" 2>/dev/null; then + g_is_guest_vm=1 + g_is_guest_vm_reason="'hypervisor' flag in $g_procfs/cpuinfo" + fi + g_is_guest_vm_cached=1 + fi + [ "$g_is_guest_vm" = 1 ] +} diff --git a/src/libs/400_hw_check.sh b/src/libs/400_hw_check.sh index c3fceb5..22baa7e 100644 --- a/src/libs/400_hw_check.sh +++ b/src/libs/400_hw_check.sh @@ -388,6 +388,30 @@ check_kernel_info() { check_cpu() { local capabilities ret spec_ctrl_msr codename ucode_str + if is_arm_cpu; then + pr_info "* CPU details" + pr_info " * Vendor: $cpu_vendor" + pr_info " * Model name: $cpu_friendly_name" + if [ -n "${cpu_impl_list:-}" ]; then + pr_info " * Implementer(s): $cpu_impl_list" + fi + if [ -n "${cpu_part_list:-}" ]; then + pr_info " * Part(s): $cpu_part_list" + fi + if [ -n "${cpu_arch_list:-}" ]; then + pr_info " * Architecture(s): $cpu_arch_list" + fi + if has_runtime; then + pr_info_nol " * Running as VM guest: " + if is_running_as_guest; then + pstatus yellow YES "$g_is_guest_vm_reason" + else + pstatus green NO + fi + fi + return + fi + if ! uname -m | grep -qwE 'x86_64|i[3-6]86|amd64'; then return fi @@ -416,6 +440,15 @@ check_cpu() { fi fi + if has_runtime; then + pr_info_nol " * Running as VM guest: " + if is_running_as_guest; then + pstatus yellow YES "$g_is_guest_vm_reason" + else + pstatus green NO + fi + fi + pr_info "* Hardware support (CPU microcode) for mitigation techniques" pr_info " * Indirect Branch Restricted Speculation (IBRS)" pr_info_nol " * SPEC_CTRL MSR is available: " @@ -1365,6 +1398,13 @@ check_cpu() { else pstatus blue UNKNOWN "$ret_is_latest_known_ucode_latest" fi + if is_running_as_guest; then + pr_warn + pr_warn "Note: this system is running inside a VM ($g_is_guest_vm_reason)." + pr_warn "The hypervisor may be faking the CPU model and microcode version;" + pr_warn "verify the above microcode information on the hypervisor host for accuracy." + pr_warn + fi } # Display per-CVE CPU vulnerability status based on CPU model/family.