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)
This commit is contained in:
Stéphane Lesimple
2026-04-22 00:08:11 +02:00
parent 7329c1fd2f
commit 43bbfabc34
3 changed files with 66 additions and 4 deletions
+8 -4
View File
@@ -110,7 +110,8 @@ _build_json_system() {
1) smt_val='false' ;; 1) smt_val='false' ;;
*) smt_val='null' ;; *) smt_val='null' ;;
esac 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_release")" \
"$(_json_str "$kernel_version")" \ "$(_json_str "$kernel_version")" \
"$(_json_str "$kernel_arch")" \ "$(_json_str "$kernel_arch")" \
@@ -121,7 +122,9 @@ _build_json_system() {
"$(_json_num "${g_max_core_id:+$((g_max_core_id + 1))}")" \ "$(_json_num "${g_max_core_id:+$((g_max_core_id + 1))}")" \
"$smt_val" \ "$smt_val" \
"$(_json_bool "${g_has_vmm:-}")" \ "$(_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 # Build the "cpu" section of the comprehensive JSON output
@@ -262,14 +265,15 @@ _build_json_cpu_microcode() {
blacklisted='false' blacklisted='false'
fi fi
latest_hex="${ret_is_latest_known_ucode_version:-}" 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 "$ucode_hex")" \
"$(_json_str "$latest_hex")" \ "$(_json_str "$latest_hex")" \
"$ucode_uptodate" \ "$ucode_uptodate" \
"$blacklisted" \ "$blacklisted" \
"$(_json_str "${ret_is_latest_known_ucode_latest:-}")" \ "$(_json_str "${ret_is_latest_known_ucode_latest:-}")" \
"$(_json_str "${g_mcedb_source:-}")" \ "$(_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 --- # --- Format-specific batch emitters ---
+18
View File
@@ -55,3 +55,21 @@ is_xen_domU() {
return 1 return 1
fi 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 ]
}
+40
View File
@@ -388,6 +388,30 @@ check_kernel_info() {
check_cpu() { check_cpu() {
local capabilities ret spec_ctrl_msr codename ucode_str 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 if ! uname -m | grep -qwE 'x86_64|i[3-6]86|amd64'; then
return return
fi fi
@@ -416,6 +440,15 @@ check_cpu() {
fi fi
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 "* Hardware support (CPU microcode) for mitigation techniques"
pr_info " * Indirect Branch Restricted Speculation (IBRS)" pr_info " * Indirect Branch Restricted Speculation (IBRS)"
pr_info_nol " * SPEC_CTRL MSR is available: " pr_info_nol " * SPEC_CTRL MSR is available: "
@@ -1365,6 +1398,13 @@ check_cpu() {
else else
pstatus blue UNKNOWN "$ret_is_latest_known_ucode_latest" pstatus blue UNKNOWN "$ret_is_latest_known_ucode_latest"
fi 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. # Display per-CVE CPU vulnerability status based on CPU model/family.