From 44cbd79562ed55a8b0f2e5b5dc708265568ed9f8 Mon Sep 17 00:00:00 2001 From: Noah Meyerhans Date: Fri, 30 Apr 2021 09:30:52 -0700 Subject: [PATCH] Use BIOS characteristics to distinguish EC2 bare-metal from VMs DMI vendor information fields do not provide enough information for us to distinguish between Amazon EC2 virtual machines and bare-metal instances. SMBIOS provides a BIOS Information table (https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.4.0.pdf Ch. 7) that provides a field to indicate that the current machine is a virtual machine. On EC2 virtual machine instances, this field is set, while bare-metal instances leave this unset, so we inspect the field via the kernel's /sys/firemware/dmi/entries interface. Fixes #18929 (cherry picked from commit ce35037928f4c4c931088256853f07804ec7d235) Related: #2117948 --- src/basic/virt.c | 65 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/src/basic/virt.c b/src/basic/virt.c index 6e4c702051..00d1c894e6 100644 --- a/src/basic/virt.c +++ b/src/basic/virt.c @@ -22,6 +22,12 @@ #include "string-util.h" #include "virt.h" +enum { + SMBIOS_VM_BIT_SET, + SMBIOS_VM_BIT_UNSET, + SMBIOS_VM_BIT_UNKNOWN, +}; + static const char *const vm_table[_VIRTUALIZATION_MAX] = { [VIRTUALIZATION_XEN] = "XenVMMXenVMM", [VIRTUALIZATION_KVM] = "KVMKVMKVM", @@ -131,9 +137,8 @@ static int detect_vm_device_tree(void) { #endif } -static int detect_vm_dmi(void) { #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) - +static int detect_vm_dmi_vendor(void) { static const char *const dmi_vendors[] = { "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */ "/sys/class/dmi/id/sys_vendor", @@ -179,11 +184,63 @@ static int detect_vm_dmi(void) { return dmi_vendor_table[j].id; } } -#endif + return VIRTUALIZATION_NONE; +} + +static int detect_vm_smbios(void) { + /* The SMBIOS BIOS Charateristics Extension Byte 2 (Section 2.1.2.2 of + * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.4.0.pdf), specifies that + * the 4th bit being set indicates a VM. The BIOS Characteristics table is exposed via the kernel in + * /sys/firmware/dmi/entries/0-0. Note that in the general case, this bit being unset should not + * imply that the system is running on bare-metal. For example, QEMU 3.1.0 (with or without KVM) + * with SeaBIOS does not set this bit. */ + _cleanup_free_ char *s = NULL; + size_t readsize; + int r; + + r = read_full_virtual_file("/sys/firmware/dmi/entries/0-0/raw", &s, &readsize); + if (r < 0) { + log_debug_errno(r, "Unable to read /sys/firmware/dmi/entries/0-0/raw, ignoring: %m"); + return SMBIOS_VM_BIT_UNKNOWN; + } + if (readsize < 20 || s[1] < 20) { + /* The spec indicates that byte 1 contains the size of the table, 0x12 + the number of + * extension bytes. The data we're interested in is in extension byte 2, which would be at + * 0x13. If we didn't read that much data, or if the BIOS indicates that we don't have that + * much data, we don't infer anything from the SMBIOS. */ + log_debug("Only read %zu bytes from /sys/firmware/dmi/entries/0-0/raw (expected 20)", readsize); + return SMBIOS_VM_BIT_UNKNOWN; + } - log_debug("No virtualization found in DMI"); + uint8_t byte = (uint8_t) s[19]; + if (byte & (1U<<4)) { + log_debug("DMI BIOS Extension table indicates virtualization"); + return SMBIOS_VM_BIT_SET; + } + log_debug("DMI BIOS Extension table does not indicate virtualization"); + return SMBIOS_VM_BIT_UNSET; +} +#endif /* defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) */ + +static int detect_vm_dmi(void) { +#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + + int r; + r = detect_vm_dmi_vendor(); + /* The DMI vendor tables in /sys/class/dmi/id don't help us distinguish between Amazon EC2 + * virtual machines and bare-metal instances, so we need to look at SMBIOS. */ + if (r == VIRTUALIZATION_AMAZON && detect_vm_smbios() == SMBIOS_VM_BIT_UNSET) + return VIRTUALIZATION_NONE; + + /* If we haven't identified a VM, but the firmware indicates that there is one, indicate as much. We + * have no further information about what it is. */ + if (r == VIRTUALIZATION_NONE && detect_vm_smbios() == SMBIOS_VM_BIT_SET) + return VIRTUALIZATION_VM_OTHER; + return r; +#else return VIRTUALIZATION_NONE; +#endif } static int detect_vm_xen(void) {