From 031e5cce385d3f96b1caa1d53495332a7eb03749 Mon Sep 17 00:00:00 2001 From: Steve McIntyre Date: Tue, 23 Mar 2021 23:49:46 +0000 Subject: New upstream version 15.3 --- sbat.c | 379 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) create mode 100644 sbat.c (limited to 'sbat.c') diff --git a/sbat.c b/sbat.c new file mode 100644 index 00000000..89c08417 --- /dev/null +++ b/sbat.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * sbat.c - parse SBAT data from the .sbat section data + */ + +#include "shim.h" + +EFI_STATUS +parse_sbat_section(char *section_base, size_t section_size, + size_t *n_entries, + struct sbat_section_entry ***entriesp) +{ + struct sbat_section_entry *entry = NULL, **entries; + EFI_STATUS efi_status = EFI_SUCCESS; + list_t csv, *pos = NULL; + char * end = section_base + section_size - 1; + size_t allocsz = 0; + size_t n; + char *strtab; + + if (!section_base || !section_size || !n_entries || !entriesp) + return EFI_INVALID_PARAMETER; + + INIT_LIST_HEAD(&csv); + + efi_status = + parse_csv_data(section_base, end, SBAT_SECTION_COLUMNS, &csv); + if (EFI_ERROR(efi_status)) { + return efi_status; + } + + n = 0; + list_for_each(pos, &csv) { + struct csv_row * row; + size_t i; + + row = list_entry(pos, struct csv_row, list); + + if (row->n_columns < SBAT_SECTION_COLUMNS) { + efi_status = EFI_INVALID_PARAMETER; + goto err; + } + + allocsz += sizeof(struct sbat_section_entry *); + allocsz += sizeof(struct sbat_section_entry); + for (i = 0; i < row->n_columns; i++) { + if (row->columns[i][0] == '\000') { + efi_status = EFI_INVALID_PARAMETER; + goto err; + } + allocsz += strlen(row->columns[i]) + 1; + } + n++; + } + + strtab = AllocateZeroPool(allocsz); + if (!strtab) { + efi_status = EFI_OUT_OF_RESOURCES; + goto err; + } + + entries = (struct sbat_section_entry **)strtab; + strtab += sizeof(struct sbat_section_entry *) * n; + entry = (struct sbat_section_entry *)strtab; + strtab += sizeof(struct sbat_section_entry) * n; + n = 0; + + list_for_each(pos, &csv) { + struct csv_row * row; + size_t i; + const char **ptrs[] = { + &entry->component_name, + &entry->component_generation, + &entry->vendor_name, + &entry->vendor_package_name, + &entry->vendor_version, + &entry->vendor_url, + }; + + + row = list_entry(pos, struct csv_row, list); + for (i = 0; i < row->n_columns; i++) { + *(ptrs[i]) = strtab; + strtab = stpcpy(strtab, row->columns[i]) + 1; + } + entries[n] = entry; + entry++; + n++; + } + *entriesp = entries; + *n_entries = n; +err: + free_csv_list(&csv); + return efi_status; +} + +void +cleanup_sbat_section_entries(size_t n, struct sbat_section_entry **entries) +{ + if (!n || !entries) + return; + + FreePool(entries); +} + +EFI_STATUS +verify_single_entry(struct sbat_section_entry *entry, struct sbat_var_entry *sbat_var_entry) +{ + UINT16 sbat_gen, sbat_var_gen; + + if (strcmp((const char *)entry->component_name, (const char *)sbat_var_entry->component_name) == 0) { + dprint(L"component %a has a matching SBAT variable entry, verifying\n", + entry->component_name); + + /* + * atoi returns zero for failed conversion, so essentially + * badly parsed component_generation will be treated as zero + */ + sbat_gen = atoi((const char *)entry->component_generation); + sbat_var_gen = atoi((const char *)sbat_var_entry->component_generation); + + if (sbat_gen < sbat_var_gen) { + dprint(L"component %a, generation %d, was revoked by SBAT variable", + entry->component_name, sbat_gen); + LogError(L"image did not pass SBAT verification\n"); + return EFI_SECURITY_VIOLATION; + } + } + return EFI_SUCCESS; +} + +void +cleanup_sbat_var(list_t *entries) +{ + list_t *pos = NULL, *tmp = NULL; + struct sbat_var_entry *entry; + void *first = NULL; + + list_for_each_safe(pos, tmp, entries) { + entry = list_entry(pos, struct sbat_var_entry, list); + + if ((uintptr_t)entry < (uintptr_t)first && entry != NULL) + first = entry; + + list_del(&entry->list); + } + if (first) + FreePool(first); +} + +EFI_STATUS +verify_sbat_helper(list_t *local_sbat_var, size_t n, struct sbat_section_entry **entries) +{ + unsigned int i; + list_t *pos = NULL; + EFI_STATUS efi_status = EFI_SUCCESS; + struct sbat_var_entry *sbat_var_entry; + + if (list_empty(local_sbat_var)) { + dprint(L"SBAT variable not present\n"); + return EFI_SUCCESS; + } + + for (i = 0; i < n; i++) { + list_for_each(pos, local_sbat_var) { + sbat_var_entry = list_entry(pos, struct sbat_var_entry, list); + efi_status = verify_single_entry(entries[i], sbat_var_entry); + if (EFI_ERROR(efi_status)) + goto out; + } + } + +out: + dprint(L"finished verifying SBAT data: %r\n", efi_status); + return efi_status; +} + +EFI_STATUS +verify_sbat(size_t n, struct sbat_section_entry **entries) +{ + EFI_STATUS efi_status; + + efi_status = verify_sbat_helper(&sbat_var, n, entries); + return efi_status; +} + +EFI_STATUS +parse_sbat_var_data(list_t *entry_list, UINT8 *data, UINTN datasize) +{ + struct sbat_var_entry *entry = NULL, **entries; + EFI_STATUS efi_status = EFI_SUCCESS; + list_t csv, *pos = NULL; + char * start = (char *)data; + char * end = (char *)data + datasize - 1; + size_t allocsz = 0; + size_t n; + char *strtab; + + if (!entry_list|| !data || datasize == 0) + return EFI_INVALID_PARAMETER; + + INIT_LIST_HEAD(&csv); + + efi_status = parse_csv_data(start, end, SBAT_VAR_COLUMNS, &csv); + if (EFI_ERROR(efi_status)) { + return efi_status; + } + + n = 0; + list_for_each(pos, &csv) { + struct csv_row * row; + size_t i; + + row = list_entry(pos, struct csv_row, list); + + if (row->n_columns < SBAT_VAR_REQUIRED_COLUMNS) { + efi_status = EFI_INVALID_PARAMETER; + goto err; + } + + + allocsz += sizeof(struct sbat_var_entry *); + allocsz += sizeof(struct sbat_var_entry); + for (i = 0; i < row->n_columns; i++) { + if (!row->columns[i][0]) { + efi_status = EFI_INVALID_PARAMETER; + goto err; + } + allocsz += strlen(row->columns[i]) + 1; + } + n++; + } + + strtab = AllocateZeroPool(allocsz); + if (!strtab) { + efi_status = EFI_OUT_OF_RESOURCES; + goto err; + } + + INIT_LIST_HEAD(entry_list); + + entries = (struct sbat_var_entry **)strtab; + strtab += sizeof(struct sbat_var_entry *) * n; + entry = (struct sbat_var_entry *)strtab; + strtab += sizeof(struct sbat_var_entry) * n; + n = 0; + + list_for_each(pos, &csv) { + struct csv_row * row; + size_t i; + const char **ptrs[] = { + &entry->component_name, + &entry->component_generation, + &entry->sbat_datestamp, + }; + + row = list_entry(pos, struct csv_row, list); + for (i = 0; i < row->n_columns; i++) { + *(ptrs[i]) = strtab; + strtab = stpcpy(strtab, row->columns[i]) + 1; + } + INIT_LIST_HEAD(&entry->list); + list_add_tail(&entry->list, entry_list); + entries[n] = entry; + entry++; + n++; + } +err: + free_csv_list(&csv); + return efi_status; +} + +EFI_STATUS +parse_sbat_var(list_t *entries) +{ + UINT8 *data = 0; + UINTN datasize; + EFI_STATUS efi_status; + + if (!entries) + return EFI_INVALID_PARAMETER; + + efi_status = get_variable(SBAT_VAR_NAME, &data, &datasize, SHIM_LOCK_GUID); + if (EFI_ERROR(efi_status)) { + LogError(L"Failed to read SBAT variable\n", efi_status); + return efi_status; + } + + /* + * We've intentionally made sure there's a NUL byte on all variable + * allocations, so use that here. + */ + return parse_sbat_var_data(entries, data, datasize+1); +} + +static bool +check_sbat_var_attributes(UINT32 attributes) +{ +#ifdef ENABLE_SHIM_DEVEL + return attributes == UEFI_VAR_NV_BS_RT; +#else + return attributes == UEFI_VAR_NV_BS || + attributes == UEFI_VAR_NV_BS_TIMEAUTH; +#endif +} + +EFI_STATUS +set_sbat_uefi_variable(void) +{ + EFI_STATUS efi_status = EFI_SUCCESS; + UINT32 attributes = 0; + + UINT8 *sbat = NULL; + UINTN sbatsize = 0; + + efi_status = get_variable_attr(SBAT_VAR_NAME, &sbat, &sbatsize, + SHIM_LOCK_GUID, &attributes); + /* + * Always set the SBAT UEFI variable if it fails to read. + * + * Don't try to set the SBAT UEFI variable if attributes match and + * the signature matches. + */ + if (EFI_ERROR(efi_status)) { + dprint(L"SBAT read failed %r\n", efi_status); + } else if (check_sbat_var_attributes(attributes) && + sbatsize >= strlen(SBAT_VAR_SIG "1") && + strncmp((const char *)sbat, SBAT_VAR_SIG, + strlen(SBAT_VAR_SIG))) { + dprint("SBAT variable is %d bytes, attributes are 0x%08x\n", + sbatsize, attributes); + FreePool(sbat); + return EFI_SUCCESS; + } else { + FreePool(sbat); + + /* delete previous variable */ + dprint("%s variable is %d bytes, attributes are 0x%08x\n", + SBAT_VAR_NAME, sbatsize, attributes); + dprint("Deleting %s variable.\n", SBAT_VAR_NAME); + efi_status = set_variable(SBAT_VAR_NAME, SHIM_LOCK_GUID, + attributes, 0, ""); + if (EFI_ERROR(efi_status)) { + dprint(L"SBAT variable delete failed %r\n", efi_status); + return efi_status; + } + } + + /* set variable */ + efi_status = set_variable(SBAT_VAR_NAME, SHIM_LOCK_GUID, SBAT_VAR_ATTRS, + sizeof(SBAT_VAR)-1, SBAT_VAR); + if (EFI_ERROR(efi_status)) { + dprint(L"SBAT variable writing failed %r\n", efi_status); + return efi_status; + } + + /* verify that the expected data is there */ + efi_status = get_variable(SBAT_VAR_NAME, &sbat, &sbatsize, + SHIM_LOCK_GUID); + if (EFI_ERROR(efi_status)) { + dprint(L"SBAT read failed %r\n", efi_status); + return efi_status; + } + + if (sbatsize != strlen(SBAT_VAR) || + strncmp((const char *)sbat, SBAT_VAR, strlen(SBAT_VAR)) != 0) { + dprint("new sbatsize is %d, expected %d\n", sbatsize, + strlen(SBAT_VAR)); + efi_status = EFI_INVALID_PARAMETER; + } else { + dprint(L"SBAT variable initialization succeeded\n"); + } + + FreePool(sbat); + + return efi_status; +} + +// vim:fenc=utf-8:tw=75:noet -- cgit v1.2.3