diff options
| -rw-r--r-- | include/sbat.h | 11 | ||||
| -rw-r--r-- | pe.c | 28 | ||||
| -rw-r--r-- | sbat.c | 192 |
3 files changed, 229 insertions, 2 deletions
diff --git a/include/sbat.h b/include/sbat.h index c34ad319..7441fa00 100644 --- a/include/sbat.h +++ b/include/sbat.h @@ -6,6 +6,15 @@ #ifndef SBAT_H_ #define SBAT_H_ +struct sbat_var { + const CHAR8 *component_name; + const CHAR8 *component_generation; + list_t list; +}; + +EFI_STATUS parse_sbat_var(list_t *entries); +void cleanup_sbat_var(list_t *entries); + struct sbat_entry { const CHAR8 *component_name; const CHAR8 *component_generation; @@ -17,5 +26,7 @@ struct sbat_entry { EFI_STATUS parse_sbat(char *sbat_base, size_t sbat_size, size_t *sbats, struct sbat_entry ***sbat); +EFI_STATUS verify_sbat(size_t n, struct sbat_entry **entries, list_t *var_entries); + #endif /* !SBAT_H_ */ // vim:fenc=utf-8:tw=75:noet @@ -1044,6 +1044,8 @@ handle_image (void *data, unsigned int datasize, size_t n; struct sbat_entry **entries; struct sbat_entry *entry = NULL; + list_t sbat_var_entries; + INIT_LIST_HEAD(&sbat_var_entries); if (SBATBase && SBATSize) { char *sbat_data; @@ -1081,6 +1083,32 @@ handle_image (void *data, unsigned int datasize, return EFI_UNSUPPORTED; } + efi_status = parse_sbat_var(&sbat_var_entries); + /* + * Until a SBAT variable is installed into the systems, it is expected that + * attempting to parse the variable will fail with an EFI_NOT_FOUND error. + * + * Do not consider that error fatal for now. + */ + if (EFI_ERROR(efi_status) && efi_status != EFI_NOT_FOUND) { + perror(L"Parsing SBAT variable failed: %r\n", + efi_status); + return efi_status; + } + + if (efi_status == EFI_SUCCESS) + efi_status = verify_sbat(n, entries, &sbat_var_entries); + if (efi_status == EFI_NOT_FOUND) + efi_status = EFI_SUCCESS; + + if (EFI_ERROR(efi_status)) { + if (verbose) + console_print(L"Verification failed: %r\n", efi_status); + else + console_error(L"Verification failed", efi_status); + return efi_status; + } + efi_status = verify_buffer(data, datasize, &context, sha256hash, sha1hash); @@ -4,6 +4,7 @@ */ #include "shim.h" +#include "string.h" CHAR8 * get_sbat_field(CHAR8 *current, CHAR8 *end, const CHAR8 **field, char delim) @@ -70,8 +71,8 @@ error: EFI_STATUS parse_sbat(char *sbat_base, size_t sbat_size, size_t *sbats, struct sbat_entry ***sbat) { - CHAR8 *current = (CHAR8 *) sbat_base; - CHAR8 *end = (CHAR8 *) sbat_base + sbat_size - 1; + CHAR8 *current = (CHAR8 *)sbat_base; + CHAR8 *end = (CHAR8 *)sbat_base + sbat_size - 1; EFI_STATUS efi_status = EFI_SUCCESS; struct sbat_entry *entry; struct sbat_entry **entries = NULL; @@ -121,4 +122,191 @@ error: return efi_status; } +EFI_STATUS +verify_single_entry(struct sbat_entry *entry, struct sbat_var *sbat_var_entry) +{ + UINT16 sbat_gen, sbat_var_gen; + + if (strcmp(entry->component_name, 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(entry->component_generation); + sbat_var_gen = atoi(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; + + list_for_each_safe(pos, tmp, entries) { + entry = list_entry(pos, struct sbat_var, list); + list_del(&entry->list); + + if (entry->component_generation) + FreePool((CHAR8 *)entry->component_name); + if (entry->component_name) + FreePool((CHAR8 *)entry->component_generation); + FreePool(entry); + } +} + +EFI_STATUS +verify_sbat(size_t n, struct sbat_entry **entries, list_t *sbat_entries) +{ + unsigned int i; + list_t *pos = NULL; + EFI_STATUS efi_status = EFI_SUCCESS; + struct sbat_var *sbat_var_entry; + + if (!entries || list_empty(sbat_entries)) { + dprint(L"SBAT variable not present or malformed\n"); + return EFI_INVALID_PARAMETER; + } + + for (i = 0; i < n; i++) { + list_for_each(pos, sbat_entries) { + sbat_var_entry = list_entry(pos, struct sbat_var, list); + efi_status = verify_single_entry(entries[i], sbat_var_entry); + if (EFI_ERROR(efi_status)) + return efi_status; + } + } + + dprint(L"all entries from SBAT section verified\n"); + cleanup_sbat_var(sbat_entries); + return efi_status; +} + +static BOOLEAN +is_utf8_bom(CHAR8 *buf, size_t bufsize) +{ + unsigned char bom[] = { 0xEF, 0xBB, 0xBF }; + + return CompareMem(buf, bom, MIN(sizeof(bom), bufsize)) == 0; +} + +static struct sbat_var * +new_entry(const CHAR8 *comp_name, const CHAR8 *comp_gen) +{ + struct sbat_var *new_entry = AllocatePool(sizeof(*new_entry)); + + if (!new_entry) + return NULL; + + INIT_LIST_HEAD(&new_entry->list); + new_entry->component_name = comp_name; + new_entry->component_generation = comp_gen; + + return new_entry; +} + +EFI_STATUS +add_entry(list_t *list, const CHAR8 *comp_name, const CHAR8 *comp_gen) +{ + struct sbat_var *new; + + new = new_entry(comp_name, comp_gen); + if (!new) + return EFI_OUT_OF_RESOURCES; + + list_add_tail(&new->list, list); + return EFI_SUCCESS; +} + +EFI_STATUS +parse_sbat_var(list_t *entries) +{ + UINT8 *data = 0; + UINTN datasize, i; + EFI_STATUS efi_status; + char delim; + + if (!entries) + return EFI_INVALID_PARAMETER; + + INIT_LIST_HEAD(entries); + + efi_status = get_variable(L"SBAT", &data, &datasize, SHIM_LOCK_GUID); + if (EFI_ERROR(efi_status)) { + LogError(L"Failed to read SBAT variable\n", + efi_status); + return efi_status; + } + + CHAR8 *start = (CHAR8 *)data; + CHAR8 *end = (CHAR8 *)data + datasize; + if (is_utf8_bom(start, datasize)) + start += 3; + + dprint(L"SBAT variable data:\n"); + + while (start[0] != '\0') { + const CHAR8 *fields[2] = { + NULL, + }; + for (i = 0; i < 3; i++) { + const CHAR8 *tmp; + /* + * on third iteration we check if we had extra stuff on line while parsing + * component_name. If delimeter on 2nd iteration was ',', this means that + * we have comments after component_name. get_sbat_field in this if condition + * parses comments, if they are present and drops them. + */ + if (i == 2 && start) { + if (delim == ',') { + start = get_sbat_field(start, end, &tmp, + '\n'); + } + break; + } + delim = ','; + /* we do not want to jump to next line and grab stuff from that + */ + if ((strchrnula(start, '\n') - start + 1) <= + (strchrnula(start, ',') - start + 1)) { + delim = '\n'; + if (i == 0) + goto error; + } + if (!start) { + goto error; + } + start = get_sbat_field(start, end, &tmp, delim); + /* to be replaced when we have strdupa() + */ + fields[i] = strndupa(tmp, strlen(tmp)); + if (!fields[i]) { + goto error; + } + } + dprint(L"component %a with generation %a\n", fields[0], fields[1]); + efi_status = + add_entry(entries, fields[0], fields[1]); + if (EFI_ERROR(efi_status)) + goto error; + } + FreePool(data); + return EFI_SUCCESS; +error: + perror(L"failed to parse SBAT variable\n"); + cleanup_sbat_var(entries); + FreePool(data); + return EFI_INVALID_PARAMETER; +} // vim:fenc=utf-8:tw=75:noet |
