summaryrefslogtreecommitdiff
path: root/sbat.c
diff options
context:
space:
mode:
Diffstat (limited to 'sbat.c')
-rw-r--r--sbat.c379
1 files changed, 379 insertions, 0 deletions
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