summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Burmashev <alexander.burmashev@oracle.com>2021-02-16 07:08:35 -0500
committerPeter Jones <pjones@redhat.com>2021-02-19 14:28:10 -0500
commit31e1aa7aca6a0d3bd4f26008b455e27be0952bea (patch)
tree1585c879fa5eb2604d7dab597a8c7bf2a08b5f51
parentf1feb3ac04e2c96aa751fda8d36bb50c04ffa58d (diff)
downloadefi-boot-shim-31e1aa7aca6a0d3bd4f26008b455e27be0952bea.tar.gz
efi-boot-shim-31e1aa7aca6a0d3bd4f26008b455e27be0952bea.zip
pe.c: parse SBAT variable and perform basic verification
Per Peter Jones suggestion, we will be flexible in what data we expect while parsing the variable. Three fields are mandatory: component_generation, component_name_size, component_name However we also support adding comments and additional information to be added after component name, with ',' as a separator. Those information will be ignored and not used for verification purposes. So: grub,1 and grub,1,wow,this,is,my,comment will provide exactly same set of data for verification. [0]: https://github.com/rhboot/shim/blob/main/SBAT.md Signed-off-by: Alex Burmashev <alexander.burmashev@oracle.com> Signed-off-by: Peter Jones <pjones@redhat.com>
-rw-r--r--include/sbat.h11
-rw-r--r--pe.c28
-rw-r--r--sbat.c192
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
diff --git a/pe.c b/pe.c
index 0f4e1afd..bd974843 100644
--- a/pe.c
+++ b/pe.c
@@ -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);
diff --git a/sbat.c b/sbat.c
index c29340cf..8dc65d12 100644
--- a/sbat.c
+++ b/sbat.c
@@ -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