diff options
Diffstat (limited to 'src/security/intel/stm/SmmStm.c')
-rw-r--r-- | src/security/intel/stm/SmmStm.c | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/src/security/intel/stm/SmmStm.c b/src/security/intel/stm/SmmStm.c new file mode 100644 index 0000000000..f23be70217 --- /dev/null +++ b/src/security/intel/stm/SmmStm.c @@ -0,0 +1,691 @@ +/* @file + * SMM STM support + * + * Copyright (c) 2015 - 2016, Intel Corporation. All rights reserved. + * + * This program and the accompanying materials are licensed and made available + * under the terms and conditions of the BSD License which accompanies this + * distribution. The full text of the license may be found at + * http://opensource.org/licenses/bsd-license.php. + * + * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR + * IMPLIED. + * + */ + +#include <console/console.h> +#include <cpu/x86/cr.h> +#include <cpu/x86/mp.h> +#include <cpu/x86/msr.h> +#include <cpu/x86/cache.h> +#include <security/intel/stm/SmmStm.h> +#include <string.h> + +#define TXT_EVTYPE_BASE 0x400 +#define TXT_EVTYPE_STM_HASH (TXT_EVTYPE_BASE + 14) + +#define RDWR_ACCS 3 +#define FULL_ACCS 7 + +#define SIZE_4KB 0x00001000 +#define SIZE_4MB 0x00400000 + +#define PTP_SIZE SIZE_4KB + +#define IA32_PG_P (1 << 0) +#define IA32_PG_RW (1 << 1) +#define IA32_PG_PS (1 << 7) + +#define STM_PAGE_SHIFT 12 +#define STM_PAGE_MASK 0xFFF +#define STM_SIZE_TO_PAGES(a) \ + (((a) >> STM_PAGE_SHIFT) + (((a)&STM_PAGE_MASK) ? 1 : 0)) +#define STM_PAGES_TO_SIZE(a) ((a) << STM_PAGE_SHIFT) + +#define STM_ACCESS_DENIED 15 +#define STM_UNSUPPORTED 3 + +#define STM_BUFFER_TOO_SMALL 1 + +#define STM_SM_MONITOR_STATE_ENABLED 1 + +typedef struct { + + uint64_t vmcs_revision_id : 31; + uint64_t always_zero : 1; + uint64_t vmcs_size : 13; + uint64_t reserved1 : 3; + uint64_t vmxon_add_width : 1; + uint64_t stm_supported : 1; + uint64_t vmcs_memory_type : 4; + uint64_t in_out_reporting : 1; + uint64_t may_clear_defaults : 1; + uint64_t reserved2 : 8; +} VMX_BASIC_MSR_BITS; + +typedef union { + VMX_BASIC_MSR_BITS bits; + uint64_t uint64; + msr_t msr; +} VMX_BASIC_MSR; + +typedef struct { + uint64_t valid : 1; + uint64_t reserved1 : 1; + uint64_t vmx_off_blockSmi : 1; + uint64_t reserved2 : 9; + uint64_t mseg_address : 20; + uint64_t reserved3 : 32; +} SMM_MONITOR_CTL_MSR_BITS; + +extern struct mp_state { + struct mp_ops ops; + int cpu_count; + uintptr_t perm_smbase; + size_t perm_smsize; + size_t smm_save_state_size; + int do_smm; +} mp_state; + +typedef union { + SMM_MONITOR_CTL_MSR_BITS bits; + uint64_t uint64; + msr_t msr; +} SMM_MONITOR_CTL_MSR; + +// Template of STM_RSC_END structure for copying. + +STM_RSC_END m_rsc_end_node = { + {END_OF_RESOURCES, sizeof(STM_RSC_END)}, +}; + +uint8_t *m_stm_resources_ptr = NULL; +uint32_t m_stm_resource_total_size = 0x0; +uint32_t m_stm_resource_size_used = 0x0; +uint32_t m_stm_resource_size_available = 0x0; + +uint8_t *stm_resource_heap = NULL; + +uint32_t m_stm_state = 0; + +/* + * Handle single Resource to see if it can be merged into Record. + * + * @param resource A pointer to resource node to be added + * @param record A pointer to record node to be merged + * + * @retval true resource handled + * @retval false resource is not handled + */ + +static bool handle_single_resource(STM_RSC *resource, STM_RSC *record) +{ + uint64_t resource_lo = 0; + uint64_t resource_hi = 0; + uint64_t record_lo = 0; + uint64_t record_hi = 0; + + // Calling code is responsible for making sure that + // Resource->Header.RscType == (*Record)->Header.RscType + // thus we use just one of them as switch variable. + + switch (resource->header.rsc_type) { + case MEM_RANGE: + case MMIO_RANGE: + resource_lo = resource->mem.base; + resource_hi = resource->mem.base + resource->mem.length; + record_lo = record->mem.base; + record_hi = record->mem.base + record->mem.length; + if (resource->mem.rwx_attributes + != record->mem.rwx_attributes) { + if ((resource_lo == record_lo) + && (resource_hi == record_hi)) { + record->mem.rwx_attributes = + resource->mem.rwx_attributes + | record->mem.rwx_attributes; + return true; + } else { + return false; + } + } + break; + case IO_RANGE: + case TRAPPED_IO_RANGE: + resource_lo = (uint64_t)resource->io.base; + resource_hi = (uint64_t)resource->io.base + + (uint64_t)resource->io.length; + record_lo = (uint64_t)record->io.base; + record_hi = + (uint64_t)record->io.base + (uint64_t)record->io.length; + break; + case PCI_CFG_RANGE: + if ((resource->pci_cfg.originating_bus_number + != record->pci_cfg.originating_bus_number) + || (resource->pci_cfg.last_node_index + != record->pci_cfg.last_node_index)) + return false; + + if (memcmp(resource->pci_cfg.pci_device_path, + record->pci_cfg.pci_device_path, + sizeof(STM_PCI_DEVICE_PATH_NODE) + * (resource->pci_cfg.last_node_index + 1)) + != 0) { + return false; + } + resource_lo = (uint64_t)resource->pci_cfg.base; + resource_hi = (uint64_t)resource->pci_cfg.base + + (uint64_t)resource->pci_cfg.length; + record_lo = (uint64_t)record->pci_cfg.base; + record_hi = (uint64_t)record->pci_cfg.base + + (uint64_t)record->pci_cfg.length; + if (resource->pci_cfg.rw_attributes + != record->pci_cfg.rw_attributes) { + if ((resource_lo == record_lo) + && (resource_hi == record_hi)) { + record->pci_cfg.rw_attributes = + resource->pci_cfg.rw_attributes + | record->pci_cfg.rw_attributes; + return true; + } else { + return false; + } + } + break; + case MACHINE_SPECIFIC_REG: + + // Special case - merge MSR masks in place. + if (resource->msr.msr_index != record->msr.msr_index) + return false; + record->msr.read_mask |= resource->msr.read_mask; + record->msr.write_mask |= resource->msr.write_mask; + return true; + default: + return false; + } + + // If resources are disjoint + if ((resource_hi < record_lo) || (resource_lo > record_hi)) + return false; + + // If resource is consumed by record. + if ((resource_lo >= record_lo) && (resource_hi <= record_hi)) + return true; + + // Resources are overlapping. + // Resource and record are merged. + resource_lo = (resource_lo < record_lo) ? resource_lo : record_lo; + resource_hi = (resource_hi > record_hi) ? resource_hi : record_hi; + + switch (resource->header.rsc_type) { + case MEM_RANGE: + case MMIO_RANGE: + record->mem.base = resource_lo; + record->mem.length = resource_hi - resource_lo; + break; + case IO_RANGE: + case TRAPPED_IO_RANGE: + record->io.base = (uint64_t)resource_lo; + record->io.length = (uint64_t)(resource_hi - resource_lo); + break; + case PCI_CFG_RANGE: + record->pci_cfg.base = (uint64_t)resource_lo; + record->pci_cfg.length = (uint64_t)(resource_hi - resource_lo); + break; + default: + return false; + } + + return true; +} + +/* + * Add resource node. + * + * @param Resource A pointer to resource node to be added + */ +static void add_single_resource(STM_RSC *resource) +{ + STM_RSC *record; + + record = (STM_RSC *)m_stm_resources_ptr; + + while (true) { + if (record->header.rsc_type == END_OF_RESOURCES) + break; + + // Go to next record if resource and record types don't match. + if (resource->header.rsc_type != record->header.rsc_type) { + record = (STM_RSC *)((void *)record + + record->header.length); + continue; + } + + // Record is handled inside of procedure - don't adjust. + if (handle_single_resource(resource, record)) + return; + record = (STM_RSC *)((void *)record + record->header.length); + } + + // Add resource to the end of area. + memcpy(m_stm_resources_ptr + m_stm_resource_size_used + - sizeof(m_rsc_end_node), + resource, resource->header.length); + memcpy(m_stm_resources_ptr + m_stm_resource_size_used + - sizeof(m_rsc_end_node) + resource->header.length, + &m_rsc_end_node, sizeof(m_rsc_end_node)); + m_stm_resource_size_used += resource->header.length; + m_stm_resource_size_available = + m_stm_resource_total_size - m_stm_resource_size_used; +} + +/* + * Add resource list. + * + * @param resource_list A pointer to resource list to be added + * @param num_entries Optional number of entries. + * If 0, list must be terminated by END_OF_RESOURCES. + */ +static void add_resource(STM_RSC *resource_list, uint32_t num_entries) +{ + uint32_t count; + uint32_t index; + STM_RSC *resource; + + if (num_entries == 0) + count = 0xFFFFFFFF; + else + count = num_entries; + + resource = resource_list; + + for (index = 0; index < count; index++) { + if (resource->header.rsc_type == END_OF_RESOURCES) + return; + add_single_resource(resource); + resource = + (STM_RSC *)((void *)resource + resource->header.length); + } +} + +/* + * Validate resource list. + * + * @param resource_list A pointer to resource list to be added + * @param num_entries Optional number of entries. + * If 0, list must be terminated by END_OF_RESOURCES. + * + * @retval true resource valid + * @retval false resource invalid + */ +static bool validate_resource(STM_RSC *resource_list, uint32_t num_entries) +{ + uint32_t count; + uint32_t index; + STM_RSC *resource; + uint32_t sub_index; + + // If NumEntries == 0 make it very big. Scan will be terminated by + // END_OF_RESOURCES. + if (num_entries == 0) + count = 0xFFFFFFFF; + else + count = num_entries; + + // Start from beginning of resource list. + resource = resource_list; + + for (index = 0; index < count; index++) { + printk(BIOS_DEBUG, "STM: %s (%u) - RscType(%x) length(0x%x)\n", + __func__, + index, + resource->header.rsc_type, + resource->header.length); + // Validate resource. + switch (resource->header.rsc_type) { + case END_OF_RESOURCES: + if (resource->header.length != sizeof(STM_RSC_END)) + return false; + + // If we are passed actual number of resources to add, + // END_OF_RESOURCES structure between them is considered + // an error. If NumEntries == 0 END_OF_RESOURCES is a + // termination. + if (num_entries != 0) + return false; + + // If NumEntries == 0 and list reached end - return + // success. + return true; + + case MEM_RANGE: + case MMIO_RANGE: + printk(BIOS_DEBUG, + "STM: %s - MEM (0x%0llx, 0x%0llx)\n", + __func__, + resource->mem.base, + resource->mem.length); + + if (resource->header.length != sizeof(STM_RSC_MEM_DESC)) + return false; + + if (resource->mem.rwx_attributes > FULL_ACCS) + return false; + break; + + case IO_RANGE: + case TRAPPED_IO_RANGE: + if (resource->header.length != sizeof(STM_RSC_IO_DESC)) + return false; + + if ((resource->io.base + resource->io.length) > 0xFFFF) + return false; + break; + + case PCI_CFG_RANGE: + printk(BIOS_DEBUG, + "STM: %s - PCI (0x%02x, 0x%08x, 0x%02x, 0x%02x)\n", + __func__, + resource->pci_cfg.originating_bus_number, + resource->pci_cfg.last_node_index, + resource->pci_cfg.pci_device_path[0].pci_device, + resource->pci_cfg.pci_device_path[0] + .pci_function); + if (resource->header.length + != sizeof(STM_RSC_PCI_CFG_DESC) + + (sizeof(STM_PCI_DEVICE_PATH_NODE) + * resource->pci_cfg.last_node_index)) + return false; + for (sub_index = 0; + sub_index <= resource->pci_cfg.last_node_index; + sub_index++) { + if ((resource->pci_cfg + .pci_device_path[sub_index] + .pci_device + > 0x1F) + || (resource->pci_cfg + .pci_device_path[sub_index] + .pci_function + > 7)) + return false; + } + if ((resource->pci_cfg.base + resource->pci_cfg.length) + > 0x1000) + return false; + break; + + case MACHINE_SPECIFIC_REG: + if (resource->header.length != sizeof(STM_RSC_MSR_DESC)) + return false; + break; + + default: + printk(BIOS_DEBUG, "STM: %s - Unknown RscType(%x)\n", + __func__, resource->header.rsc_type); + return false; + } + resource = + (STM_RSC *)((void *)resource + resource->header.length); + } + return true; +} + +/* + * Get resource list. + * EndResource is excluded. + * + * @param resou rce_list A pointer to resource list to be added + * @param num_entries Optional number of entries. + * If 0, list must be terminated by END_OF_RESOURCES. + * + * @retval true resource valid + * @retval false resource invalid + */ +static uint32_t get_resource_size(STM_RSC *resource_list, uint32_t num_entries) +{ + uint32_t count; + uint32_t index; + STM_RSC *resource; + + resource = resource_list; + + // If NumEntries == 0 make it very big. Scan will be terminated by + // END_OF_RESOURCES. + if (num_entries == 0) + count = 0xFFFFFFFF; + else + count = num_entries; + + // Start from beginning of resource list. + resource = resource_list; + + for (index = 0; index < count; index++) { + if (resource->header.rsc_type == END_OF_RESOURCES) + break; + resource = + (STM_RSC *)((void *)resource + resource->header.length); + } + return (uint32_t)((uint32_t)resource - (uint32_t)resource_list); +} + +/* + * Add resources in list to database. Allocate new memory areas as needed. + * + * @param resource_list A pointer to resource list to be added + * @param num_entries Optional number of entries. + * If 0, list must be terminated by END_OF_RESOURCES. + * + * @retval SUCCESS If resources are added + * @retval INVALID_PARAMETER If nested procedure detected resource failure + * @retval OUT_OF_RESOURCES If nested procedure returned it and we cannot + * allocate more areas. + */ +int add_pi_resource(STM_RSC *resource_list, uint32_t num_entries) +{ + size_t resource_size; + + printk(BIOS_DEBUG, "STM: %s - Enter\n", __func__); + + if (!validate_resource(resource_list, num_entries)) + return -1; // INVALID_PARAMETER; + + resource_size = get_resource_size(resource_list, num_entries); + printk(BIOS_DEBUG, "STM: ResourceSize - 0x%08lx\n", resource_size); + if (resource_size == 0) + return -1; // INVALID_PARAMETER; + + if (m_stm_resources_ptr == NULL) { + + // Copy EndResource for initialization + m_stm_resources_ptr = stm_resource_heap; + m_stm_resource_total_size = CONFIG_BIOS_RESOURCE_LIST_SIZE; + memset(m_stm_resources_ptr, 0, CONFIG_BIOS_RESOURCE_LIST_SIZE); + + memcpy(m_stm_resources_ptr, &m_rsc_end_node, + sizeof(m_rsc_end_node)); + m_stm_resource_size_used = sizeof(m_rsc_end_node); + m_stm_resource_size_available = + m_stm_resource_total_size - sizeof(m_rsc_end_node); + wbinvd(); // force to memory + + } else { + if (m_stm_resource_size_available < resource_size) { + printk(BIOS_DEBUG, + "STM: ERROR - not enough space for SMM resource list\n"); + return -1; // OUT_OF_RESOURCES + } + } + + // Check duplication + add_resource(resource_list, num_entries); + + return 0; // SUCCESS; +} + +/* + * Delete resources in list to database. + * + * @param resource_list A pointer to resource list to be deleted + * NULL means delete all resources. + * @param num_entries Optional number of entries. + * If 0, list must be terminated by END_OF_RESOURCES. + * + * @retval SUCCESS If resources are deleted + * @retval INVALID_PARAMETER If nested procedure detected resource failure + */ +int32_t delete_pi_resource(STM_RSC *resource_list, uint32_t num_entries) +{ + if (resource_list != NULL) { + // ASSERT (false); + return -1; // UNSUPPORTED; + } + + // Delete all + memcpy(m_stm_resources_ptr, &m_rsc_end_node, sizeof(m_rsc_end_node)); + m_stm_resource_size_used = sizeof(m_rsc_end_node); + m_stm_resource_size_available = + m_stm_resource_total_size - sizeof(m_rsc_end_node); + return 0; // SUCCESS; +} + +/* + * Get BIOS resources. + * + * @param resource_list A pointer to resource list to be filled + * @param resource_size On input it means size of resource list input. + * On output it means size of resource list filled, + * or the size of resource list to be filled if size is + * too small. + * + * @retval SUCCESS If resources are returned. + * @retval BUFFER_TOO_SMALL If resource list buffer is too small to hold + * the whole resource list. + */ +int32_t get_pi_resource(STM_RSC *resource_list, uint32_t *resource_size) +{ + if (*resource_size < m_stm_resource_size_used) { + *resource_size = (uint32_t)m_stm_resource_size_used; + return -1; // BUFFER_TOO_SMALL; + } + + memcpy(resource_list, m_stm_resources_ptr, m_stm_resource_size_used); + *resource_size = (uint32_t)m_stm_resource_size_used; + return 0; // SUCCESS; +} + +/* + * Get 4K page aligned VMCS size. + * @return 4K page aligned VMCS size + */ +static uint32_t get_vmcs_size(void) +{ + uint32_t this_vmcs_size; + VMX_BASIC_MSR msr_data64; + int stm_support; + + msr_data64.msr = rdmsr(IA32_VMX_BASIC_MSR); + + this_vmcs_size = msr_data64.bits.vmcs_size; + stm_support = msr_data64.bits.stm_supported; + printk(BIOS_DEBUG, "STM: %s: Size %d StmSupport %d\n", __func__, + this_vmcs_size, stm_support); + + // VMCS require 0x1000 alignment + this_vmcs_size = STM_PAGES_TO_SIZE(STM_SIZE_TO_PAGES(this_vmcs_size)); + + return this_vmcs_size; +} + +/* + * Create 4G page table for STM. + * 2M PTEs for x86_64 or 2M PTEs for x86_32. + * + * @param pageable_base The page table base in MSEG + */ +void stm_gen_4g_pagetable_x64(uint32_t pagetable_base) +{ + uint32_t index; + uint32_t sub_index; + uint64_t *pde; + uint64_t *pte; + uint64_t *pml4; + + pml4 = (uint64_t *)(uint32_t)pagetable_base; + pagetable_base += PTP_SIZE; + *pml4 = pagetable_base | IA32_PG_RW | IA32_PG_P; + + pde = (uint64_t *)(uint32_t)pagetable_base; + pagetable_base += PTP_SIZE; + pte = (uint64_t *)(uint32_t)pagetable_base; + + for (index = 0; index < 4; index++) { + *pde = pagetable_base | IA32_PG_RW | IA32_PG_P; + pde++; + pagetable_base += PTP_SIZE; + + for (sub_index = 0; sub_index < SIZE_4KB / sizeof(*pte); + sub_index++) { + *pte = (((index << 9) + sub_index) << 21) | IA32_PG_PS + | IA32_PG_RW | IA32_PG_P; + pte++; + } + } +} + +/* + * Check STM image size. + * + * @param stm_image STM image + * @param stm_imageSize STM image size + * + * @retval true check pass + * @retval false check fail + */ + +bool stm_check_stm_image(void *stm_image, uint32_t stm_imagesize) +{ + uint32_t min_mseg_size; + STM_HEADER *stm_header; + + stm_header = (STM_HEADER *)stm_image; + + // Get Minimal required Mseg size + min_mseg_size = (STM_PAGES_TO_SIZE(STM_SIZE_TO_PAGES( + stm_header->sw_stm_hdr.static_image_size)) + + stm_header->sw_stm_hdr.additional_dynamic_memory_size + + (stm_header->sw_stm_hdr.per_proc_dynamic_memory_size + + get_vmcs_size() * 2) + * mp_state.cpu_count); + if (min_mseg_size < stm_imagesize) + min_mseg_size = stm_imagesize; + + if (stm_header->hw_stm_hdr.cr3_offset + >= stm_header->sw_stm_hdr.static_image_size) { + + // We will create page table, just in case that SINIT does not + // create it. + if (min_mseg_size < stm_header->hw_stm_hdr.cr3_offset + + STM_PAGES_TO_SIZE(6)) { + min_mseg_size = stm_header->hw_stm_hdr.cr3_offset + + STM_PAGES_TO_SIZE(6); + } + } + + // Check if it exceeds MSEG size + if (min_mseg_size > CONFIG_MSEG_SIZE) + return false; + + return true; +} + +/* + * This function return BIOS STM resource. + * Produced by SmmStm. + * Comsumed by SmmMpService when Init. + * + * @return BIOS STM resource + */ +void *get_stm_resource(void) +{ + return m_stm_resources_ptr; +} |