From: Ross Philipson <ross.philip...@oracle.com> Signed-off-by: Ross Philipson <ross.philip...@oracle.com> Signed-off-by: Daniel Kiper <daniel.ki...@oracle.com> Signed-off-by: Michał Żygowski <michal.zygow...@3mdeb.com> Signed-off-by: Krystian Hebel <krystian.he...@3mdeb.com> Signed-off-by: Sergii Dmytruk <sergii.dmyt...@3mdeb.com> --- grub-core/loader/i386/txt/txt.c | 1052 +++++++++++++++++++++++++++++++ include/grub/i386/slaunch.h | 14 +- 2 files changed, 1065 insertions(+), 1 deletion(-) create mode 100644 grub-core/loader/i386/txt/txt.c
diff --git a/grub-core/loader/i386/txt/txt.c b/grub-core/loader/i386/txt/txt.c new file mode 100644 index 000000000..ece482ec7 --- /dev/null +++ b/grub-core/loader/i386/txt/txt.c @@ -0,0 +1,1052 @@ +/* + * txt.c: Intel(r) TXT support functions, including initiating measured + * launch, post-launch, AP wakeup, etc. + * + * Copyright (c) 2003-2011, Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of the Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2020 Oracle and/or its affiliates. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/loader.h> +#include <grub/memory.h> +#include <grub/normal.h> +#include <grub/err.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/dl.h> +#include <grub/acpi.h> +#include <grub/safemath.h> +#include <grub/slr_table.h> +#include <grub/cpu/relocator.h> +#include <grub/i386/cpuid.h> +#include <grub/i386/msr.h> +#include <grub/i386/crfr.h> +#include <grub/i386/txt.h> +#include <grub/i386/linux.h> +#include <grub/i386/memory.h> +#include <grub/i386/slaunch.h> +#include <grub/i386/tpm.h> + +#define OS_SINIT_DATA_TPM_12_VER 6 +#define OS_SINIT_DATA_TPM_20_VER 7 + +#define OS_SINIT_DATA_MIN_VER OS_SINIT_DATA_TPM_12_VER + +static struct grub_slr_entry_intel_info slr_intel_info_staging = {0}; + +static grub_err_t +enable_smx_mode (void) +{ + grub_uint32_t caps; + grub_uint64_t feat_ctrl = grub_rdmsr (GRUB_MSR_X86_FEATURE_CONTROL); + + if (!(feat_ctrl & GRUB_MSR_X86_FEATURE_CTRL_LOCK)) + { + grub_dprintf ("slaunch", "Firmware didn't lock FEATURE_CONTROL MSR," + "locking it now\n"); + /* Not setting SENTER_FUNCTIONS and SENTER_ENABLE because they were tested + * in grub_txt_verify_platform() */ + feat_ctrl |= GRUB_MSR_X86_ENABLE_VMX_OUT_SMX | + GRUB_MSR_X86_ENABLE_VMX_IN_SMX | + GRUB_MSR_X86_FEATURE_CTRL_LOCK; + grub_wrmsr (GRUB_MSR_X86_FEATURE_CONTROL, feat_ctrl); + } + + /* Enable SMX mode. */ + grub_write_cr4 (grub_read_cr4 () | GRUB_CR4_X86_SMXE); + + caps = grub_txt_getsec_capabilities (0); + + if (!(caps & GRUB_SMX_CAPABILITY_CHIPSET_PRESENT)) + { + grub_error (GRUB_ERR_BAD_DEVICE, N_("TXT-capable chipset is not present")); + goto fail; + } + + if (!(caps & GRUB_SMX_CAPABILITY_SENTER)) + { + grub_error (GRUB_ERR_BAD_DEVICE, N_("GETSEC[SENTER] is not available")); + goto fail; + } + + if (!(caps & GRUB_SMX_CAPABILITY_PARAMETERS)) + { + grub_error (GRUB_ERR_BAD_DEVICE, N_("GETSEC[PARAMETERS] is not available")); + goto fail; + } + + return GRUB_ERR_NONE; + + fail: + /* Disable SMX mode on failure. */ + grub_write_cr4 (grub_read_cr4 () & ~GRUB_CR4_X86_SMXE); + + return grub_errno; +} + +static grub_err_t +grub_txt_smx_parameters (struct grub_smx_parameters *params) +{ + grub_uint32_t index = 0, eax, ebx, ecx, param_type; + + grub_memset (params, 0, sizeof(*params)); + + params->max_acm_size = GRUB_SMX_DEFAULT_MAX_ACM_SIZE; + params->acm_memory_types = GRUB_SMX_DEFAULT_ACM_MEMORY_TYPE; + params->senter_controls = GRUB_SMX_DEFAULT_SENTER_CONTROLS; + + do + { + grub_txt_getsec_parameters (index, &eax, &ebx, &ecx); + param_type = eax & GRUB_SMX_PARAMETER_TYPE_MASK; + + switch (param_type) + { + case GRUB_SMX_PARAMETER_NULL: + break; /* This means done. */ + + case GRUB_SMX_PARAMETER_ACM_VERSIONS: + if (params->version_count >= GRUB_SMX_PARAMETER_MAX_VERSIONS) + return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("Too many ACM versions")); + params->versions[params->version_count].mask = ebx; + params->versions[params->version_count++].version = ecx; + break; + + case GRUB_SMX_PARAMETER_MAX_ACM_SIZE: + params->max_acm_size = GRUB_SMX_GET_MAX_ACM_SIZE (eax); + break; + + case GRUB_SMX_PARAMETER_ACM_MEMORY_TYPES: + params->acm_memory_types = GRUB_SMX_GET_ACM_MEMORY_TYPES (eax); + break; + + case GRUB_SMX_PARAMETER_SENTER_CONTROLS: + params->senter_controls = GRUB_SMX_GET_SENTER_CONTROLS (eax); + break; + + case GRUB_SMX_PARAMETER_TXT_EXTENSIONS: + params->txt_feature_ext_flags = GRUB_SMX_GET_TXT_EXT_FEATURES (eax); + break; + + default: + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("Unknown SMX parameter")); + } + + ++index; + + } while (param_type != GRUB_SMX_PARAMETER_NULL); + + /* If no ACM versions were found, set the default one. */ + if (!params->version_count) + { + params->versions[0].mask = GRUB_SMX_DEFAULT_VERSION_MASK; + params->versions[0].version = GRUB_SMX_DEFAULT_VERSION; + params->version_count++; + } + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_txt_prepare_cpu (void) +{ + struct grub_smx_parameters params; + grub_uint32_t i; + grub_uint64_t mcg_cap, mcg_stat; + unsigned long cr0; + grub_err_t err; + + cr0 = grub_read_cr0 (); + + /* Cache must be enabled (CR0.CD = CR0.NW = 0). */ + cr0 &= ~(GRUB_CR0_X86_CD | GRUB_CR0_X86_NW); + + /* Native FPU error reporting must be enabled for proper interaction behavior. */ + cr0 |= GRUB_CR0_X86_NE; + + grub_write_cr0 (cr0); + + /* Disable virtual-8086 mode (EFLAGS.VM = 0). */ + grub_write_flags_register (grub_read_flags_register () & ~GRUB_EFLAGS_X86_VM); + + /* + * Verify all machine check status registers are clear (unless + * support preserving them). + */ + + /* Is machine check in progress? */ + if ( grub_rdmsr (GRUB_MSR_X86_MCG_STATUS) & GRUB_MSR_MCG_STATUS_MCIP ) + return grub_error (GRUB_ERR_BAD_DEVICE, + N_("machine check in progress during secure launch")); + + err = grub_txt_smx_parameters (¶ms); + if (err != GRUB_ERR_NONE) + return err; + + if (params.txt_feature_ext_flags & GRUB_SMX_PROCESSOR_BASE_SCRTM) + grub_dprintf ("slaunch", "CPU supports processor-based S-CRTM\n"); + + if (params.txt_feature_ext_flags & GRUB_SMX_MACHINE_CHECK_HANDLING) + grub_dprintf ("slaunch", "CPU supports preserving machine check errors\n"); + else + { + grub_dprintf ("slaunch", "CPU does not support preserving machine check errors\n"); + + /* Check if all machine check registers are clear. */ + mcg_cap = grub_rdmsr (GRUB_MSR_X86_MCG_CAP); + for (i = 0; i < (mcg_cap & GRUB_MSR_MCG_BANKCNT_MASK); ++i) + { + mcg_stat = grub_rdmsr (GRUB_MSR_X86_MC0_STATUS + i * 4); + if (mcg_stat & (1ULL << 63)) + return grub_error (GRUB_ERR_BAD_DEVICE, + N_("secure launch MCG[%u] = %" PRIxGRUB_UINT64_T " ERROR"), i, + mcg_stat); + } + } + + return GRUB_ERR_NONE; +} + +static void +save_mtrrs (struct grub_slr_txt_mtrr_state *saved_bsp_mtrrs) +{ + grub_uint64_t i; + + saved_bsp_mtrrs->default_mem_type = + grub_rdmsr (GRUB_MSR_X86_MTRR_DEF_TYPE); + + saved_bsp_mtrrs->mtrr_vcnt = + grub_rdmsr (GRUB_MSR_X86_MTRRCAP) & GRUB_MSR_X86_VCNT_MASK; + + if (saved_bsp_mtrrs->mtrr_vcnt > GRUB_TXT_VARIABLE_MTRRS_LENGTH) + { + /* Print warning but continue saving what we can... */ + grub_printf ("WARNING: Actual number of variable MTRRs (%" PRIuGRUB_UINT64_T + ") > GRUB_SL_MAX_VARIABLE_MTRRS (%d)\n", + saved_bsp_mtrrs->mtrr_vcnt, + GRUB_TXT_VARIABLE_MTRRS_LENGTH); + saved_bsp_mtrrs->mtrr_vcnt = GRUB_TXT_VARIABLE_MTRRS_LENGTH; + } + + for (i = 0; i < saved_bsp_mtrrs->mtrr_vcnt; ++i) + { + saved_bsp_mtrrs->mtrr_pair[i].mtrr_physmask = + grub_rdmsr (GRUB_MSR_X86_MTRR_PHYSMASK (i)); + saved_bsp_mtrrs->mtrr_pair[i].mtrr_physbase = + grub_rdmsr (GRUB_MSR_X86_MTRR_PHYSBASE (i)); + } + /* Zero unused array items. */ + for ( ; i < GRUB_TXT_VARIABLE_MTRRS_LENGTH; ++i) + { + saved_bsp_mtrrs->mtrr_pair[i].mtrr_physmask = 0; + saved_bsp_mtrrs->mtrr_pair[i].mtrr_physbase = 0; + } +} + +static void +set_all_mtrrs (int enable) +{ + grub_uint64_t mtrr_def_type; + + mtrr_def_type = grub_rdmsr (GRUB_MSR_X86_MTRR_DEF_TYPE); + + if ( enable ) + mtrr_def_type |= GRUB_MSR_X86_MTRR_ENABLE; + else + mtrr_def_type &= ~GRUB_MSR_X86_MTRR_ENABLE; + + grub_wrmsr (GRUB_MSR_X86_MTRR_DEF_TYPE, mtrr_def_type); +} + +#define SINIT_MTRR_MASK 0xFFFFFF /* SINIT requires 36b mask */ + +/* + * Note: bitfields in following structures are assumed to work on x86 and + * nothing else. All compilers supported by GRUB agree when it comes to layout + * of bits that is consistent with hardware implementation. It was decided to + * use bitfields for better readability instead of manual shifting and masking. + */ +union mtrr_physbase_t +{ + grub_uint64_t raw; + struct + { + grub_uint64_t type : 8; + grub_uint64_t reserved1 : 4; + grub_uint64_t base : 52; /* Define as max width and mask w/ */ + /* MAXPHYADDR when using */ + }; +} GRUB_PACKED; + +union mtrr_physmask_t +{ + grub_uint64_t raw; + struct + { + grub_uint64_t reserved1 : 11; + grub_uint64_t v : 1; /* valid */ + grub_uint64_t mask : 52; /* define as max width and mask w/ */ + /* MAXPHYADDR when using */ + }; +} GRUB_PACKED; + +static inline grub_uint32_t +bsrl (grub_uint32_t mask) +{ + grub_uint32_t result; + + asm ("bsrl %1,%0" : "=r" (result) : "rm" (mask) : "cc"); + + return result; +} + +static inline int +fls (int mask) +{ + return (mask == 0 ? mask : (int)bsrl ((grub_uint32_t)mask) + 1); +} + +/* + * Set the memory type for specified range (base to base+size) + * to mem_type and everything else to UC + */ +static grub_err_t +set_mtrr_mem_type (const grub_uint8_t *base, grub_uint32_t size, + grub_uint32_t mem_type) +{ + grub_uint64_t mtrr_def_type; + grub_uint64_t mtrr_cap; + union mtrr_physbase_t mtrr_physbase; + union mtrr_physmask_t mtrr_physmask; + grub_uint32_t vcnt, pages_in_range; + unsigned long ndx, base_v; + int i = 0, j, num_pages, mtrr_s; + + /* Disable all fixed MTRRs, set default type to UC */ + mtrr_def_type = grub_rdmsr (GRUB_MSR_X86_MTRR_DEF_TYPE); + mtrr_def_type &= ~(GRUB_MSR_X86_MTRR_ENABLE_FIXED | GRUB_MSR_X86_DEF_TYPE_MASK); + mtrr_def_type |= GRUB_MTRR_MEMORY_TYPE_UC; + grub_wrmsr (GRUB_MSR_X86_MTRR_DEF_TYPE, mtrr_def_type); + + /* Initially disable all variable MTRRs (we'll enable the ones we use) */ + mtrr_cap = grub_rdmsr (GRUB_MSR_X86_MTRRCAP); + vcnt = (mtrr_cap & GRUB_MSR_X86_VCNT_MASK); + + for ( ndx = 0; ndx < vcnt; ndx++ ) + { + mtrr_physmask.raw = grub_rdmsr (GRUB_MSR_X86_MTRR_PHYSMASK (ndx)); + mtrr_physmask.v = 0; + grub_wrmsr (GRUB_MSR_X86_MTRR_PHYSMASK (ndx), mtrr_physmask.raw); + } + + /* Map all AC module pages as mem_type */ + num_pages = GRUB_PAGE_UP(size) >> GRUB_PAGE_SHIFT; + + grub_dprintf ("slaunch", "setting MTRRs for acmod: base=%p, size=%x, num_pages=%d\n", + base, size, num_pages); + + /* + * Each VAR MTRR base must be a multiple of that MTRR's Size. + * grub_txt_sinit_select() made sure that base is at least 4K-aligned. + */ + base_v = (unsigned long)base; + /* MTRR size in pages */ + mtrr_s = 1; + + while ( (base_v & 0x01) == 0 ) + { + i++; + base_v = base_v >> 1; + } + + for (j = i - 12; j > 0; j--) + mtrr_s = mtrr_s*2; /* mtrr_s = mtrr_s << 1 */ + + grub_dprintf ("slaunch", "The maximum allowed MTRR range size=%d Pages \n", mtrr_s); + + ndx = 0; + + while ( num_pages >= mtrr_s ) + { + mtrr_physbase.raw = grub_rdmsr (GRUB_MSR_X86_MTRR_PHYSBASE (ndx)); + mtrr_physbase.base = ((unsigned long)base >> GRUB_PAGE_SHIFT) & + SINIT_MTRR_MASK; + mtrr_physbase.type = mem_type; + grub_wrmsr (GRUB_MSR_X86_MTRR_PHYSBASE (ndx), mtrr_physbase.raw); + + mtrr_physmask.raw = grub_rdmsr (GRUB_MSR_X86_MTRR_PHYSMASK (ndx)); + mtrr_physmask.mask = ~(mtrr_s - 1) & SINIT_MTRR_MASK; + mtrr_physmask.v = 1; + grub_wrmsr (GRUB_MSR_X86_MTRR_PHYSMASK (ndx), mtrr_physmask.raw); + + base += (mtrr_s * GRUB_PAGE_SIZE); + num_pages -= mtrr_s; + ndx++; + if ( ndx == vcnt ) + return grub_error (GRUB_ERR_BAD_DEVICE, + N_("exceeded number of var MTRRs when mapping range")); + } + + while ( num_pages > 0 ) + { + /* Set the base of the current MTRR */ + mtrr_physbase.raw = grub_rdmsr (GRUB_MSR_X86_MTRR_PHYSBASE (ndx)); + mtrr_physbase.base = ((unsigned long)base >> GRUB_PAGE_SHIFT) & + SINIT_MTRR_MASK; + mtrr_physbase.type = mem_type; + grub_wrmsr (GRUB_MSR_X86_MTRR_PHYSBASE (ndx), mtrr_physbase.raw); + + /* + * Calculate MTRR mask + * MTRRs can map pages in power of 2 + * may need to use multiple MTRRS to map all of region + */ + pages_in_range = 1 << (fls (num_pages) - 1); + + mtrr_physmask.raw = grub_rdmsr (GRUB_MSR_X86_MTRR_PHYSMASK (ndx)); + mtrr_physmask.mask = ~(pages_in_range - 1) & SINIT_MTRR_MASK; + mtrr_physmask.v = 1; + grub_wrmsr (GRUB_MSR_X86_MTRR_PHYSMASK (ndx), mtrr_physmask.raw); + + /* + * Prepare for the next loop depending on number of pages + * We figure out from the above how many pages could be used in this + * mtrr. Then we decrement the count, increment the base, + * increment the mtrr we are dealing with, and if num_pages is + * still not zero, we do it again. + */ + base += (pages_in_range * GRUB_PAGE_SIZE); + num_pages -= pages_in_range; + ndx++; + if ( ndx == vcnt ) + return grub_error (GRUB_ERR_BAD_DEVICE, + N_("exceeded number of var MTRRs when mapping range")); + } + + return GRUB_ERR_NONE; +} + +/* + * This must be done for each processor so that all have the same + * memory types + */ +static grub_err_t +set_mtrrs_for_acmod (struct grub_txt_acm_header *hdr) +{ + unsigned long eflags; + unsigned long cr0, cr4; + grub_err_t err; + + /* + * Need to do some things before we start changing MTRRs. + * + * Since this will modify some of the MTRRs, they should be saved first + * so that they can be restored once the AC mod is done. + */ + + /* Disable interrupts */ + eflags = grub_read_flags_register (); + grub_write_flags_register (eflags & ~GRUB_EFLAGS_X86_IF); + + /* Save CR0 then disable cache (CRO.CD=1, CR0.NW=0) */ + cr0 = grub_read_cr0 (); + grub_write_cr0 ( (cr0 & ~GRUB_CR0_X86_NW) | GRUB_CR0_X86_CD ); + + /* Flush caches */ + asm volatile ("wbinvd"); + + /* Save CR4 and disable global pages (CR4.PGE=0) */ + cr4 = grub_read_cr4 (); + grub_write_cr4 (cr4 & ~GRUB_CR4_X86_PGE); + + /* Disable MTRRs */ + set_all_mtrrs (0); + + /* Set MTRRs for AC mod and rest of memory */ + err = set_mtrr_mem_type ((grub_uint8_t*)hdr, hdr->size*4, + GRUB_MTRR_MEMORY_TYPE_WB); + + /* Undo some of earlier changes and enable our new settings */ + + /* Flush caches */ + asm volatile ("wbinvd"); + + /* Enable MTRRs */ + set_all_mtrrs (1); + + /* Restore CR0 (caching) */ + grub_write_cr0 (cr0); + + /* Restore CR4 (global pages) */ + grub_write_cr4 (cr4); + + /* Restore flags */ + grub_write_flags_register (eflags); + + return err; +} + +static void +setup_txt_slrt_entry (struct grub_slaunch_params *slparams, + struct grub_txt_os_mle_data *os_mle_data) +{ + struct grub_slr_table *slr_table = slparams->slr_table_mem; + struct grub_slr_entry_hdr *txt_info; + + grub_slr_add_entry (slr_table, &slr_intel_info_staging.hdr); + + txt_info = grub_slr_next_entry_by_tag (slr_table, NULL, GRUB_SLR_ENTRY_INTEL_INFO); + os_mle_data->txt_info = (grub_addr_t) slparams->slr_table_base + + ((grub_addr_t) txt_info - (grub_addr_t) slparams->slr_table_mem); +} + +/* + * Adds new element to the end. `size` does not include common header. + * Assume that heap was cleared and there is enough space to add the element. + */ +static inline struct grub_txt_heap_ext_data_element * +add_ext_data_elt (struct grub_txt_os_sinit_data *os_sinit_data, + grub_uint32_t type, grub_uint32_t size) +{ + struct grub_txt_heap_ext_data_element *elt = + (struct grub_txt_heap_ext_data_element *) os_sinit_data->ext_data_elts; + + while (elt->type != GRUB_TXT_HEAP_EXTDATA_TYPE_END) + elt = (struct grub_txt_heap_ext_data_element *)((grub_uint8_t *)elt + elt->size); + + elt->type = type; + elt->size = size + GRUB_TXT_HEAP_ELEMENT_HEADER_SIZE; + + return elt; +} + +static grub_err_t +init_txt_heap (struct grub_slaunch_params *slparams, struct grub_txt_acm_header *sinit) +{ + grub_uint8_t *txt_heap; + grub_uint32_t os_sinit_data_ver, sinit_caps; + grub_uint64_t *size; + grub_uint64_t size_total; + struct grub_txt_os_mle_data *os_mle_data; + struct grub_txt_os_sinit_data *os_sinit_data; + struct grub_txt_heap_ext_data_element *elt; +#ifdef GRUB_MACHINE_EFI + struct grub_acpi_rsdp_v20 *rsdp; +#endif + + /* BIOS data already verified in grub_txt_verify_platform(). */ + + txt_heap = grub_txt_get_heap (); + + grub_dprintf ("slaunch", "TXT heap %p\n", txt_heap); + + /* OS/loader to MLE data. */ + + os_mle_data = grub_txt_os_mle_data_start (txt_heap); + grub_dprintf ("slaunch", "OS MLE data: %p\n", os_mle_data); + size = (grub_uint64_t *) ((grub_addr_t) os_mle_data - sizeof (grub_uint64_t)); + *size = sizeof (*os_mle_data) + sizeof (grub_uint64_t); + + if (slparams->slr_table_base == GRUB_SLAUNCH_STORE_IN_OS2MLE) + { + /* SLRT needs to be at least 4-byte aligned per specification. */ + slparams->slr_table_base = + ALIGN_UP ((grub_addr_t) os_mle_data + sizeof (*os_mle_data), 4); + + /* Recompute size including SLRT table in it. */ + *size = (slparams->slr_table_base + slparams->slr_table_size) + - ((grub_addr_t) os_mle_data - sizeof (grub_uint64_t)); + + /* Size of heap sections should be a multiple of 8. */ + *size = ALIGN_UP (*size, 8); + } + + if (grub_add (grub_txt_bios_data_size (txt_heap), *size, &size_total) || + (size_total > grub_txt_get_heap_size ())) + { + *size = 0; + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + N_("not enough TXT HEAP space for OsMleData")); + } + + grub_memset (os_mle_data, 0, sizeof (*os_mle_data)); + + os_mle_data->version = GRUB_SL_OS_MLE_STRUCT_VERSION; + os_mle_data->boot_params_addr = slparams->boot_params_addr; + os_mle_data->slrt = slparams->slr_table_base; + + os_mle_data->ap_wake_block = slparams->ap_wake_block; + os_mle_data->ap_wake_block_size = slparams->ap_wake_block_size; + + /* Setup the TXT specific SLR information */ + slr_intel_info_staging.hdr.tag = GRUB_SLR_ENTRY_INTEL_INFO; + slr_intel_info_staging.hdr.size = sizeof(struct grub_slr_entry_intel_info); + slr_intel_info_staging.saved_misc_enable_msr = + grub_rdmsr (GRUB_MSR_X86_MISC_ENABLE); + + /* Save the BSPs MTRR state so post launch can restore it. */ + grub_dprintf ("slaunch", "Saving MTRRs to OS MLE data\n"); + save_mtrrs (&slr_intel_info_staging.saved_bsp_mtrrs); + + /* OS/loader to SINIT data. */ + grub_dprintf ("slaunch", "Get supported OS SINIT data version\n"); + os_sinit_data_ver = grub_txt_supported_os_sinit_data_ver (sinit); + + if (os_sinit_data_ver < OS_SINIT_DATA_MIN_VER) + return grub_error (GRUB_ERR_BAD_DEVICE, + N_("unsupported OS to SINIT data version in SINIT ACM: %d" + " expected >= %d"), os_sinit_data_ver, OS_SINIT_DATA_MIN_VER); + + os_sinit_data = grub_txt_os_sinit_data_start (txt_heap); + grub_dprintf ("slaunch", "OS SINIT data: %p\n", os_sinit_data); + size = (grub_uint64_t *) ((grub_addr_t) os_sinit_data - sizeof (grub_uint64_t)); + + *size = sizeof(grub_uint64_t) + sizeof (struct grub_txt_os_sinit_data) + + GRUB_TXT_HEAP_ELEMENT_HEADER_SIZE /* End element */; + + if (grub_get_tpm_ver () == GRUB_TPM_12) + *size += GRUB_TXT_HEAP_ELEMENT_HEADER_SIZE + + sizeof (struct grub_txt_heap_tpm_event_log_element); + else if (grub_get_tpm_ver () == GRUB_TPM_20) + *size += GRUB_TXT_HEAP_ELEMENT_HEADER_SIZE + + sizeof (struct grub_txt_heap_event_log_pointer2_1_element); + else + return grub_error (GRUB_ERR_BAD_DEVICE, N_("unsupported TPM version")); + + if (grub_add (size_total, *size, &size_total) || + (size_total > grub_txt_get_heap_size ())) + { + *size = 0; + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + N_("not enough TXT HEAP space for OsSinitData")); + } + + grub_memset (os_sinit_data, 0, *size); + +#ifdef GRUB_MACHINE_EFI + rsdp = grub_acpi_get_rsdpv2 (); + + if (rsdp == NULL) + return grub_error (GRUB_ERR_BAD_DEVICE, N_("ACPI RSDP 2.0 missing\n")); + + os_sinit_data->efi_rsdp_ptr = (grub_uint64_t)(grub_addr_t) rsdp; +#endif + + os_sinit_data->mle_ptab = slparams->mle_ptab_target; + os_sinit_data->mle_size = slparams->mle_size; + + os_sinit_data->mle_hdr_base = slparams->mle_header_offset; + + /* TODO: Check low PMR with RMRR. Look at relevant tboot code too. */ + /* TODO: Kernel should not allocate any memory outside of PMRs regions!!! */ + os_sinit_data->vtd_pmr_lo_base = 0; + os_sinit_data->vtd_pmr_lo_size = ALIGN_DOWN (grub_mmap_get_highest (0x100000000), + GRUB_TXT_PMR_ALIGN); + + os_sinit_data->vtd_pmr_hi_base = ALIGN_UP (grub_mmap_get_lowest (0x100000000), + GRUB_TXT_PMR_ALIGN); + os_sinit_data->vtd_pmr_hi_size = ALIGN_DOWN (grub_mmap_get_highest (0xffffffffffffffff), + GRUB_TXT_PMR_ALIGN); + os_sinit_data->vtd_pmr_hi_size -= os_sinit_data->vtd_pmr_hi_base; + + grub_dprintf ("slaunch", + "vtd_pmr_lo_base: 0x%" PRIxGRUB_UINT64_T " vtd_pmr_lo_size: 0x%" + PRIxGRUB_UINT64_T " vtd_pmr_hi_base: 0x%" PRIxGRUB_UINT64_T + " vtd_pmr_hi_size: 0x%" PRIxGRUB_UINT64_T "\n", + os_sinit_data->vtd_pmr_lo_base, os_sinit_data->vtd_pmr_lo_size, + os_sinit_data->vtd_pmr_hi_base, os_sinit_data->vtd_pmr_hi_size); + + sinit_caps = grub_txt_get_sinit_capabilities (sinit); + + /* + * In the latest TXT Software Development Guide as of now (April 2023, + * Revision 017.4) bits 4 and 5 (used to be "no legacy PCR usage" and + * "auth PCR usage" respectively) of capabilities field bit are reserved. + * Bit 4 is ignored, but it is returned as 0 by SINIT[CAPABILITIES], + * while bit 5 is forced to 1 for compatibility reasons. This is related + * to support for TPM 1.2 devices, which always had this bit set. + * + * There are TPM 2.0 platforms that will have both bits set. TXT + * specification doesn't specify when this has changed, so we can't + * reliably test for those. + */ + os_sinit_data->capabilities = GRUB_TXT_CAPS_TPM_12_AUTH_PCR_USAGE; + + /* + * APs (application processors) can't be brought up by usual INIT-SIPI-SIPI + * sequence after Measured Launch, otherwise the MLE integrity is lost. + * Choose monitor RLP (responding logical processor, fancy name for AP) wakeup + * mechanism first, if that isn't supported fall back to GETSEC[WAKEUP]. + */ + if (sinit_caps & GRUB_TXT_CAPS_MONITOR_SUPPORT) + os_sinit_data->capabilities |= GRUB_TXT_CAPS_MONITOR_SUPPORT; + else if (sinit_caps & GRUB_TXT_CAPS_GETSEC_WAKE_SUPPORT) + os_sinit_data->capabilities |= GRUB_TXT_CAPS_GETSEC_WAKE_SUPPORT; + else + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("lack of RLP wakeup mechanism")); + + if (sinit_caps & GRUB_TXT_CAPS_ECX_PT_SUPPORT) + os_sinit_data->capabilities |= GRUB_TXT_CAPS_ECX_PT_SUPPORT; + + if (grub_get_tpm_ver () == GRUB_TPM_12) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + N_("TPM 1.2 detected, but not implemented yet")); + else + { + if (!(sinit_caps & GRUB_TXT_CAPS_TPM_20_EVTLOG_SUPPORT)) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("original TXT TPM 2.0 event log format is not supported")); + + os_sinit_data->capabilities |= GRUB_TXT_CAPS_TPM_20_EVTLOG_SUPPORT; + + os_sinit_data->flags = GRUB_TXT_PCR_EXT_MAX_PERF_POLICY; + + os_sinit_data->version = OS_SINIT_DATA_TPM_20_VER; + + elt = add_ext_data_elt(os_sinit_data, + GRUB_TXT_HEAP_EXTDATA_TYPE_EVENT_LOG_POINTER2_1, + sizeof (struct grub_txt_heap_event_log_pointer2_1_element)); + elt->event_log_pointer2_1.phys_addr = slparams->tpm_evt_log_base; + elt->event_log_pointer2_1.allocated_event_container_size = slparams->tpm_evt_log_size; + } + + elt = add_ext_data_elt(os_sinit_data, GRUB_TXT_HEAP_EXTDATA_TYPE_END, 0); + + if ((grub_uint8_t *)elt + elt->size > + (grub_uint8_t *)grub_txt_sinit_mle_data_start (txt_heap) - sizeof(grub_uint64_t)) + return grub_error (GRUB_ERR_BUG, + N_("error in OsSinitData size requirements calculation")); + + /* SinitMleDataSize isn't known at this point, it is crafted by SINIT ACM. + * We can only test if size field fits and hope that ACM checks the rest. */ + if (grub_add (size_total, sizeof (grub_uint64_t), &size_total) || + (size_total > grub_txt_get_heap_size ())) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + N_("not enough TXT HEAP space for SinitMleDataSize")); + + grub_dprintf ("slaunch", "TXT HEAP init done\n"); + + return GRUB_ERR_NONE; +} + +/* + * TODO: Why 1 GiB limit? It does not seem that it is required by TXT spec. + * If there is a limit then it should be checked before allocation and image load. + * + * If enough room is available in front of the MLE, the maximum size of an + * MLE that can be covered is 1G. This is due to having 512 PDEs pointing + * to 512 page tables with 512 PTEs each. + */ +grub_uint32_t +grub_txt_get_mle_ptab_size (grub_uint32_t mle_size) +{ + /* + * #PT + 1 PT + #PD + 1 PD + 1 PDT + * + * Why do we need 2 extra PTEs and PDEs? Because MLE image may not + * start and end at PTE (page) and PDE (2 MiB) boundary... + */ + return ((((mle_size / GRUB_PAGE_SIZE) + 2) / 512) /* Number of PTs */ + + 1 /* PT */ + + (((mle_size / (512 * GRUB_PAGE_SIZE)) + 2) / 512) /* Number of PDs */ + + 1 /* PD */ + + 1) /* PDT */ + * GRUB_PAGE_SIZE; +} + +/* Page directory and table entries only need Present set */ +#define MAKE_PT_MLE_ENTRY(addr) (((grub_uint64_t)(grub_addr_t)(addr) & GRUB_PAGE_MASK) | 0x01) + +/* + * The MLE page tables have to be below the MLE and have no special regions in + * between them and the MLE (this is a bit of an unwritten rule). + * 20 pages are carved out of memory below the MLE. That leave 18 page table + * pages that can cover up to 36M . + * can only contain 4k pages + * + * TODO: TXT Spec p.32; List section name and number with PT MLE requirements here. + * + * TODO: This function is not able to cover MLEs larger than 1 GiB. Fix it!!! + * After fixing increase GRUB_TXT_MLE_MAX_SIZE too. + */ +void +grub_txt_setup_mle_ptab (struct grub_slaunch_params *slparams) +{ + grub_uint8_t *pg_dir, *pg_dir_ptr_tab = slparams->mle_ptab_mem, *pg_tab; + grub_uint32_t mle_off = 0, pd_off = 0; + grub_uint64_t *pde, *pte; + + grub_memset (pg_dir_ptr_tab, 0, slparams->mle_ptab_size); + + pg_dir = pg_dir_ptr_tab + GRUB_PAGE_SIZE; + pg_tab = pg_dir + GRUB_PAGE_SIZE; + + /* Only use first entry in page dir ptr table */ + *(grub_uint64_t *)pg_dir_ptr_tab = MAKE_PT_MLE_ENTRY(pg_dir); + + /* Start with first entry in page dir */ + *(grub_uint64_t *)pg_dir = MAKE_PT_MLE_ENTRY(pg_tab); + + pte = (grub_uint64_t *)pg_tab; + pde = (grub_uint64_t *)pg_dir; + + do + { + /* mle_start may be unaligned, handled by mask in MAKE_PT_MLE_ENTRY */ + *pte = MAKE_PT_MLE_ENTRY(slparams->mle_start + mle_off); + + pte++; + mle_off += GRUB_PAGE_SIZE; + + if (!(++pd_off % 512)) + { + /* Break if we don't need any additional page entries */ + if (mle_off >= slparams->mle_size) + break; + pde++; + *pde = MAKE_PT_MLE_ENTRY(pte); + } + /* Add one page in case mle_start isn't aligned */ + } while (mle_off - GRUB_PAGE_SIZE + 1 < slparams->mle_size); +} + +grub_err_t +grub_txt_init (void) +{ + grub_err_t err; + + err = grub_txt_verify_platform (); + + if (err != GRUB_ERR_NONE) + return err; + + err = enable_smx_mode (); + + if (err != GRUB_ERR_NONE) + return err; + + return GRUB_ERR_NONE; +} + +void +grub_txt_shutdown (void) +{ + /* Disable SMX mode. */ + grub_write_cr4 (grub_read_cr4 () & ~GRUB_CR4_X86_SMXE); +} + +void +grub_txt_state_show (void) +{ + union { + grub_uint64_t d64; + grub_uint32_t d32; + grub_uint8_t d8; + grub_uint8_t a8[8]; + } data; + int i; + union grub_txt_didvid didvid; + + data.d64 = grub_txt_reg_pub_read64 (GRUB_TXT_STS); + grub_printf (" TXT.STS: 0x%016" PRIxGRUB_UINT64_T "\n" + " SENTER.DONE.STS: %d\n" + " SEXIT.DONE.STS: %d\n" + " MEM-CONFIGLOCK.STS: %d\n" + " PRIVATEOPEN.STS: %d\n" + " TXT.LOCALITY1.OPEN.STS: %d\n" + " TXT.LOCALITY2.OPEN.STS: %d\n", + data.d64, !!(data.d64 & GRUB_TXT_STS_SENTER_DONE), + !!(data.d64 & GRUB_TXT_STS_SEXIT_DONE), + !!(data.d64 & GRUB_TXT_STS_MEM_CONFIG_LOCK), + !!(data.d64 & GRUB_TXT_STS_PRIVATE_OPEN), + !!(data.d64 & GRUB_TXT_STS_LOCALITY1_OPEN), + !!(data.d64 & GRUB_TXT_STS_LOCALITY2_OPEN)); + + /* Only least significant byte has a meaning. */ + data.d8 = grub_txt_reg_pub_read8 (GRUB_TXT_ESTS); + grub_printf (" TXT.ESTS: 0x%02x\n" + " TXT_RESET.STS: %d\n", data.d8, + !!(data.d8 & GRUB_TXT_ESTS_TXT_RESET)); + + data.d64 = grub_txt_reg_pub_read64 (GRUB_TXT_E2STS); + grub_printf (" TXT.E2STS: 0x%016" PRIxGRUB_UINT64_T "\n" + " SECRETS.STS: %d\n", data.d64, + !!(data.d64 & GRUB_TXT_E2STS_SECRETS)); + + /* Only least significant 4 bytes have a meaning. */ + data.d32 = grub_txt_reg_pub_read32 (GRUB_TXT_ERRORCODE); + grub_printf (" TXT.ERRORCODE: 0x%08" PRIxGRUB_UINT32_T "\n", data.d32); + + didvid.value = grub_txt_reg_pub_read64 (GRUB_TXT_DIDVID); + grub_printf (" TXT.DIDVID: 0x%016" PRIxGRUB_UINT64_T "\n" + " VID: 0x%04x\n" + " DID: 0x%04x\n" + " RID: 0x%04x\n" + " ID-EXT: 0x%04x\n", + didvid.value, didvid.vid, didvid.did, didvid.rid, didvid.id_ext); + + /* Only least significant 4 bytes have a meaning. */ + data.d32 = grub_txt_reg_pub_read32 (GRUB_TXT_VER_FSBIF); + grub_printf (" TXT.VER.FSBIF: 0x%08" PRIxGRUB_UINT32_T "\n", data.d32); + + if ((data.d32 != 0x00000000) && (data.d32 != 0xffffffff)) + grub_printf (" DEBUG.FUSE: %d\n", !!(data.d32 & GRUB_TXT_VER_FSBIF_DEBUG_FUSE)); + else + { + /* Only least significant 4 bytes have a meaning. */ + data.d32 = grub_txt_reg_pub_read32 (GRUB_TXT_VER_QPIIF); + grub_printf (" TXT.VER.QPIIF: 0x%08" PRIxGRUB_UINT32_T "\n" + " DEBUG.FUSE: %d\n", data.d32, + !!(data.d32 & GRUB_TXT_VER_QPIIF_DEBUG_FUSE)); + } + + /* Only least significant 4 bytes have a meaning. */ + data.d32 = grub_txt_reg_pub_read32 (GRUB_TXT_SINIT_BASE); + grub_printf (" TXT.SINIT.BASE: 0x%08" PRIxGRUB_UINT32_T "\n", data.d32); + + /* Only least significant 4 bytes have a meaning. */ + data.d32 = grub_txt_reg_pub_read32 (GRUB_TXT_SINIT_SIZE); + grub_printf (" TXT.SINIT.SIZE: %" PRIuGRUB_UINT32_T + " B (0x%" PRIxGRUB_UINT32_T ")\n", data.d32, data.d32); + + /* Only least significant 4 bytes have a meaning. */ + data.d32 = grub_txt_reg_pub_read32 (GRUB_TXT_HEAP_BASE); + grub_printf (" TXT.HEAP.BASE: 0x%08" PRIxGRUB_UINT32_T "\n", data.d32); + + /* Only least significant 4 bytes have a meaning. */ + data.d32 = grub_txt_reg_pub_read32 (GRUB_TXT_HEAP_SIZE); + grub_printf (" TXT.HEAP.SIZE: %" PRIuGRUB_UINT32_T + " B (0x%" PRIxGRUB_UINT32_T ")\n", data.d32, data.d32); + + /* Only least significant 4 bytes have a meaning. */ + data.d32 = grub_txt_reg_pub_read32 (GRUB_TXT_DPR); + grub_printf (" TXT.DPR: 0x%08" PRIxGRUB_UINT32_T "\n" + " LOCK: %d\n" + " TOP: 0x%08" PRIxGRUB_UINT32_T "\n" + " SIZE: %" PRIuGRUB_UINT32_T " MiB\n", + data.d32, !!(data.d32 & (1 << 0)), (data.d32 & 0xfff00000), + (data.d32 & 0x00000ff0) >> 4); + + grub_printf (" TXT.PUBLIC.KEY:\n"); + + for (i = 0; i < 4; ++i) + { + /* TODO: Check relevant MSRs on SGX platforms. */ + data.d64 = grub_txt_reg_pub_read64 (GRUB_TXT_PUBLIC_KEY + i * sizeof (grub_uint64_t)); + grub_printf (" %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x%s", data.a8[0], data.a8[1], + data.a8[2], data.a8[3], data.a8[4], data.a8[5], data.a8[6], data.a8[7], + (i < 3) ? ":\n" : "\n"); + } +} + +grub_err_t +grub_txt_boot_prepare (struct grub_slaunch_params *slparams) +{ + grub_err_t err; + grub_uint8_t *txt_heap; + struct grub_txt_os_mle_data *os_mle_data; + struct grub_txt_acm_header *sinit_base; + + sinit_base = grub_txt_sinit_select (grub_slaunch_module ()); + + if (sinit_base == NULL) + return grub_errno; + + grub_dprintf ("slaunch", "Init TXT heap\n"); + err = init_txt_heap (slparams, sinit_base); + + if (err != GRUB_ERR_NONE) + return err; + + grub_dprintf ("slaunch", "TXT heap successfully prepared\n"); + + slparams->dce_base = (grub_uint32_t)(grub_addr_t) sinit_base; + slparams->dce_size = sinit_base->size * 4; + + /* Setup of SLR table. */ + grub_slaunch_init_slrt_storage (GRUB_SLR_INTEL_TXT); + txt_heap = grub_txt_get_heap (); + os_mle_data = grub_txt_os_mle_data_start (txt_heap); + setup_txt_slrt_entry (slparams, os_mle_data); + + grub_tpm_relinquish_locality (0); + grub_dprintf ("slaunch", "Relinquished TPM locality 0\n"); + + err = set_mtrrs_for_acmod (sinit_base); + if (err) + return grub_error (err, N_("secure launch failed to set MTRRs for ACM")); + + grub_dprintf ("slaunch", "MTRRs set for ACMOD\n"); + + err = grub_txt_prepare_cpu (); + if ( err ) + return err; + + grub_dprintf ("slaunch", "CPU prepared for secure launch\n"); + + if (!(grub_rdmsr (GRUB_MSR_X86_APICBASE) & GRUB_MSR_X86_APICBASE_BSP)) + return grub_error (GRUB_ERR_BAD_DEVICE, N_("secure launch must run on BSP")); + + return GRUB_ERR_NONE; +} + +void +grub_txt_add_slrt_policy_entries (void) +{ + struct grub_txt_os_mle_data *os_mle_data; + grub_uint8_t *txt_heap; + + txt_heap = grub_txt_get_heap (); + os_mle_data = grub_txt_os_mle_data_start (txt_heap); + + grub_slaunch_add_slrt_policy_entry (GRUB_SLAUNCH_DATA_PCR, + GRUB_SLR_ET_TXT_OS2MLE, + /*flags=*/0, + (grub_addr_t) os_mle_data, + sizeof(*os_mle_data), + "Measured TXT OS-MLE data"); +} diff --git a/include/grub/i386/slaunch.h b/include/grub/i386/slaunch.h index 1574b3a1c..a694260cb 100644 --- a/include/grub/i386/slaunch.h +++ b/include/grub/i386/slaunch.h @@ -31,9 +31,21 @@ #define GRUB_SLAUNCH_TPM_EVT_LOG_SIZE (8 * GRUB_PAGE_SIZE) +/* + * Special value for slr_table_base of struct grub_slaunch_params that indicates + * that the table should be stored near OS2MLE data (right after it). + * + * In this case: + * 1. Platform-specific code (e.g., TXT-code) is responsible for setting + * slr_table_base to its final value + * 2. SLRT should be copied from slr_table_mem to slr_table_base after invoking + * grub_slaunch_finish_slr_table () by the code which used this special + * value. + */ +#define GRUB_SLAUNCH_STORE_IN_OS2MLE ((grub_uint64_t) 0xFFFFFFFFFFFFFFFF) + #ifndef ASM_FILE -#include <grub/i386/linux.h> #include <grub/types.h> struct grub_slaunch_params -- 2.46.0 _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel