From a7c2739965aed73cfeeb94e1d136057d2a7034b3 Mon Sep 17 00:00:00 2001 From: Yifan Liu Date: Thu, 19 Aug 2021 16:07:12 +0800 Subject: [PATCH] hv: quirks: SMBIOS passthrough for prelaunched-VM This feature is guarded under config CONFIG_SECURITY_VM_FIXUP, which by default should be disabled. This patch passthrough native SMBIOS information to prelaunched VM. SMBIOS table contains a small entry point structure and a table, of which the entry point structure will be put in 0xf0000-0xfffff region in guest address space, and the table will be put in the ACPI_NVS region in guest address space. v2 -> v3: uuid_is_equal moved to util.h as inline API result -> pVendortable, in function efi_search_guid recalc_checksum -> generate_checksum efi_search_smbios -> efi_search_smbios_eps scan_smbios_eps kept (checked with Shiqing) EFI GUID definition kept Tracked-On: #6320 Signed-off-by: Yifan Liu --- hypervisor/arch/x86/guest/vm.c | 4 + hypervisor/quirks/security_vm_fixup.c | 213 ++++++++++++++++++++++++++ hypervisor/quirks/security_vm_fixup.h | 1 + hypervisor/quirks/smbios.h | 82 ++++++++++ 4 files changed, 300 insertions(+) create mode 100644 hypervisor/quirks/smbios.h diff --git a/hypervisor/arch/x86/guest/vm.c b/hypervisor/arch/x86/guest/vm.c index 385272193..fd70046f3 100644 --- a/hypervisor/arch/x86/guest/vm.c +++ b/hypervisor/arch/x86/guest/vm.c @@ -595,6 +595,10 @@ int32_t create_vm(uint16_t vm_id, uint64_t pcpu_bitmap, struct acrn_vm_config *v deny_hv_owned_devices(vm); } +#ifdef CONFIG_SECURITY_VM_FIXUP + passthrough_smbios(vm, get_acrn_boot_info()); +#endif + init_vpci(vm); enable_iommu(); diff --git a/hypervisor/quirks/security_vm_fixup.c b/hypervisor/quirks/security_vm_fixup.c index b7da51f3f..7a84f0aec 100644 --- a/hypervisor/quirks/security_vm_fixup.c +++ b/hypervisor/quirks/security_vm_fixup.c @@ -8,6 +8,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include static void *get_acpi_mod_entry(const char *signature, void *acpi) { @@ -89,3 +96,209 @@ void security_vm_fixup(uint16_t vm_id) } } +/* Below are code for SMBIOS passthrough */ + +/* The region after the first 64kb is currently not used. On some platforms, + * there might be an TPM2 event log region starting from VIRT_ACPI_NVS_ADDR + 0xB0000. + * This table is usually a few KB in size so we have plenty of room here. + */ +#define VIRT_SMBIOS_TABLE_ADDR (VIRT_ACPI_NVS_ADDR + 64 * MEM_1K) +/* We hardcode the eps addr to 0xf1000. The ACPI RSDP addr is hardcoded to 0xf2400 + * so we're safe here. This header is at most 31 bytes. + */ +#define VIRT_SMBIOS_EPS_ADDR 0xf1000UL +#define SMBIOS_EPS_SEARCH_START 0xf0000UL +#define SMBIOS_EPS_SEARCH_END 0xfffffUL + +/* For subsequent code, "smbios2" will be specifically refering to 32bit SMBIOS (major version 2), + * and "smbios3" will refer to 64-bit SMBIOS (major version 3). "smbios" will be a generic + * reference to SMBIOS structure. + */ +struct smbios_info { + union { + struct smbios2_entry_point eps2; + struct smbios3_entry_point eps3; + } smbios_eps; + uint8_t major_ver; + size_t smbios_eps_size; + void *smbios_table; + size_t smbios_table_size; +}; + +static void *efi_search_guid(EFI_SYSTEM_TABLE *tab, EFI_GUID *guid) +{ + uint64_t i; + void *pVendortable = NULL; + + if (tab != NULL) { + for (i = 0; i < tab->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *conf_tab = &tab->ConfigurationTable[i]; + + if (uuid_is_equal((uint8_t *)&conf_tab->VendorGuid, (uint8_t *)guid)) { + pVendortable = hpa2hva((uint64_t)conf_tab->VendorTable); + break; + } + } + } + + return pVendortable; +} + +static inline void get_smbios3_info(struct smbios3_entry_point *eps3, struct smbios_info *si) +{ + si->smbios_eps_size = eps3->length; + memcpy_s(&si->smbios_eps, si->smbios_eps_size, eps3, si->smbios_eps_size); + si->smbios_table = hpa2hva(eps3->st_addr); + si->smbios_table_size = eps3->max_st_size; + si->major_ver = eps3->major_ver; +} + +static inline void get_smbios2_info(struct smbios2_entry_point *eps2, struct smbios_info *si) +{ + si->smbios_eps_size = eps2->length; + memcpy_s(&si->smbios_eps, si->smbios_eps_size, eps2, si->smbios_eps_size); + si->smbios_table = hpa2hva(eps2->st_addr); + si->smbios_table_size = eps2->st_length; + si->major_ver = eps2->major_ver; +} + +static void generate_checksum(uint8_t *byte_start, int nbytes, uint8_t *checksum_pos) +{ + *checksum_pos = 0; + *checksum_pos = -calculate_sum8(byte_start, nbytes); +} + +static int efi_search_smbios_eps(EFI_SYSTEM_TABLE *efi_system_table, struct smbios_info *si) +{ + void *p = NULL; + EFI_GUID smbios3_guid = SMBIOS3_TABLE_GUID; + EFI_GUID smbios2_guid = SMBIOS2_TABLE_GUID; + + /* If both are present, SMBIOS3 takes precedence over SMBIOS */ + stac(); + p = efi_search_guid(efi_system_table, &smbios3_guid); + if (p != NULL) { + get_smbios3_info((struct smbios3_entry_point *)p, si); + } else { + p = efi_search_guid(efi_system_table, &smbios2_guid); + if (p != NULL) { + get_smbios2_info((struct smbios2_entry_point *)p, si); + } + } + clac(); + + return (p != NULL); +} + +static int copy_smbios_to_guest(struct acrn_vm *vm, struct smbios_info *si) +{ + int ret = 0; + uint64_t gpa; + + gpa = VIRT_SMBIOS_TABLE_ADDR; + ret = copy_to_gpa(vm, si->smbios_table, gpa, si->smbios_table_size); + if (ret == 0) { + if (si->major_ver == 2) { + struct smbios2_entry_point *eps2 = &si->smbios_eps.eps2; + eps2->st_addr = (uint32_t)gpa; + /* If we wrote generate_checksum(eps->int_anchor, ...), the code scanning tool will + * emit warnings about array bound overflow. So use offsetof instead. + */ + generate_checksum((uint8_t *)eps2 + offsetof(struct smbios2_entry_point, int_anchor), + 0xf, &eps2->int_checksum); + generate_checksum((uint8_t *)eps2, eps2->length, &eps2->checksum); + } else if (si->major_ver == 3) { + struct smbios3_entry_point *eps3 = &si->smbios_eps.eps3; + eps3->st_addr = (uint32_t)gpa; + generate_checksum((uint8_t *)eps3, eps3->length, &eps3->checksum); + } + + gpa = VIRT_SMBIOS_EPS_ADDR; + ret = copy_to_gpa(vm, &si->smbios_eps, gpa, si->smbios_eps_size); + } + + return ret; +} + +static int is_smbios3_present(uint8_t *p) +{ + return (strncmp((const char *)p, "_SM3_", 5) == 0 && + (calculate_sum8(p, ((struct smbios3_entry_point *)p)->length)) == 0); +} + +static int is_smbios2_present(uint8_t *p) +{ + return (strncmp((const char *)p, "_SM_", 4) == 0 && + (calculate_sum8(p, ((struct smbios2_entry_point *)p)->length)) == 0); +} + +static int scan_smbios_eps(struct smbios_info *si) +{ + uint8_t *start = (uint8_t *)hpa2hva(SMBIOS_EPS_SEARCH_START); + uint8_t *end = (uint8_t *)hpa2hva(SMBIOS_EPS_SEARCH_END); + uint8_t *p; + + /* per SMBIOS spec 3.2.0, 32-bit (SMBIOS) and 64-bit (SMBIOS3) EPS can be found by searching + * for the anchor string on paragraph (16-byte) boundaries within the physical address + * 0xf0000-0xfffff. + */ + stac(); + for (p = start; p < end; p += 16) { + if (is_smbios3_present(p)) { + get_smbios3_info((struct smbios3_entry_point *)p, si); + break; + } else if (is_smbios2_present(p)) { + get_smbios2_info((struct smbios2_entry_point *)p, si); + break; + } + } + clac(); + + return (p < end); +} + +static int probe_smbios_table(struct acrn_boot_info *abi, struct smbios_info *si) +{ + int found = 0; + + if (boot_from_uefi(abi)) { + /* Get from EFI system table */ + uint64_t efi_system_tab_paddr = ((uint64_t)abi->uefi_info.systab_hi << 32) | ((uint64_t)abi->uefi_info.systab); + EFI_SYSTEM_TABLE *efi_system_tab = (EFI_SYSTEM_TABLE *)hpa2hva(efi_system_tab_paddr); + found = efi_search_smbios_eps(efi_system_tab, si); + } else { + /* Search eps in 0xf0000-0xfffff */ + found = scan_smbios_eps(si); + } + /* Note: Multiboot2 spec specifies an SMBIOS tag where the bootloader can pass an "SMBIOS table" to OS. + * As of today GRUB does not support this feature, and if they were to support it, they will do the same + * thing we did here, i.e.: either reading from EFI system table or scan 0xf0000~0xfffff. So we will skip + * trying to get SMBIOS table from Multiboot2 tag. + */ + +#ifdef VM0_TPM_EVENTLOG_BASE_ADDR + if (found && (si->smbios_table_size > (VM0_TPM_EVENTLOG_BASE_ADDR - VIRT_SMBIOS_TABLE_ADDR))) { + /* Unlikely but we check this anyway */ + pr_err("Error: SMBIOS table too large. Stop copying SMBIOS info to guest."); + found = 0; + } +#endif + + return found; +} + +void passthrough_smbios(struct acrn_vm *vm, struct acrn_boot_info *abi) +{ + struct smbios_info si; + struct acrn_vm_config *vm_config = get_vm_config(vm->vm_id); + + if (is_prelaunched_vm(vm) && ((vm_config->guest_flags & GUEST_FLAG_SECURITY_VM) != 0)) { + memset(&si, 0, sizeof(struct smbios_info)); + + if (probe_smbios_table(abi, &si)) { + if (copy_smbios_to_guest(vm, &si)) { + pr_err("Failed to copy SMBIOS info to vm%d", vm->vm_id); + } + } + } +} diff --git a/hypervisor/quirks/security_vm_fixup.h b/hypervisor/quirks/security_vm_fixup.h index 3af8a9b7d..5cb92ee30 100644 --- a/hypervisor/quirks/security_vm_fixup.h +++ b/hypervisor/quirks/security_vm_fixup.h @@ -7,6 +7,7 @@ #ifndef _SECURITY_VM_FIXUP_H_ #define _SECURITY_VM_FIXUP_H_ +void passthrough_smbios(struct acrn_vm *vm, struct acrn_boot_info *abi); void security_vm_fixup(uint16_t vm_id); #endif /* _SECURITY_VM_FIXUP_H_ */ diff --git a/hypervisor/quirks/smbios.h b/hypervisor/quirks/smbios.h new file mode 100644 index 000000000..842422942 --- /dev/null +++ b/hypervisor/quirks/smbios.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 Intel Corporation. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef QUIRKS_SMBIOS_H +#define QUIRKS_SMBIOS_H + +#include +#include + +typedef struct { + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; +} EFI_GUID; + +typedef struct _EFI_CONFIGURATION_TABLE { + EFI_GUID VendorGuid; + void *VendorTable; +} EFI_CONFIGURATION_TABLE; + +typedef struct _EFI_TABLE_HEADER { + uint64_t Signature; + uint32_t Revision; + uint32_t HeaderSize; + uint32_t CRC32; + uint32_t Reserved; +} EFI_TABLE_HEADER; + +typedef struct _EFI_SYSTEM_TABLE { + EFI_TABLE_HEADER Hdr; + uint16_t *FirmwareVendor; + uint32_t FirmwareRevision; + void *ConsoleInHandle; + void *ConIn; + void *ConsoleOutHandle; + void *ConOut; + void *StandardErrorHandle; + void *StdErr; + void *RuntimeServices; + void *BootServices; + uint64_t NumberOfTableEntries; + EFI_CONFIGURATION_TABLE *ConfigurationTable; +} EFI_SYSTEM_TABLE; + +struct smbios2_entry_point { + char anchor[4]; /* "_SM_" */ + uint8_t checksum; /* covers the entire struct */ + uint8_t length; /* length of this entry point structure, currently 1Fh */ + uint8_t major_ver; /* version major */ + uint8_t minor_ver; /* version minor */ + uint16_t max_struct_size; /* size of the largest SMBIOS structure */ + uint8_t ep_rev; /* entry point structure revision */ + uint8_t formatted[5]; + char int_anchor[5]; /* "_DMI_" */ + uint8_t int_checksum; /* intermediate checksum, covers from start of int_anchor to bcd_revision */ + uint16_t st_length; /* total length of SMBIOS structure table */ + uint32_t st_addr; /* structure table address */ + uint16_t nstructs; /* number of SMBIOS structures */ + uint8_t bcd_revision; /* BCD revision */ +} __attribute__((packed)); + +struct smbios3_entry_point { + char anchor[5]; /* "_SM3_" */ + uint8_t checksum; + uint8_t length; /* length of this entry point structure, currently 18h */ + uint8_t major_ver; + uint8_t minor_ver; + uint8_t docrev; + uint8_t ep_rev; + uint8_t reserved; + uint32_t max_st_size; /* max structure table size. The actual size is guaranteed to be <= this value. */ + uint64_t st_addr; /* structure table address */ +} __attribute__((packed)); + +#define SMBIOS2_TABLE_GUID {0xeb9d2d31, 0x2d88, 0x11d3, {0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}} +#define SMBIOS3_TABLE_GUID {0xf2fd1544, 0x9794, 0x4a2c, {0x99,0x2e,0xe5,0xbb,0xcf,0x20,0xe3,0x94}} + +#endif