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