From 5f73c8d655465470498c91f238788d2ffa48de81 Mon Sep 17 00:00:00 2001
From: Gary Lin <glin@suse.com>
Date: Tue, 25 Feb 2025 16:13:28 +0800
Subject: [PATCH 1/2] Locate .sbatlevel section in shim.efi

The .sbatlevel section in shim.efi will be used to set SbatLevelRT and
we need the section to predict SbatLevelRT.

Per PE format SPEC(*), the section name is

"An 8-byte, null-padded UTF-8 encoded string. If the string is exactly 8
 8 characters long, there is no terminating null. For longer names, this
 field contains a slash (/) that is followed by an ASCII representation
 of a decimal number that is an offset into the string table."

Before looking for the .sbatlevel section, we have to get the string
table, To get the offset to the string table:

  string_table_offset = symbol_table_offset +
                        (number_of_symbols * symbol_size)

With the offset, we can go further to look for the longer section names
and get the .sbatlevel section.

(*) https://learn.microsoft.com/en-us/windows/win32/debug/pe-format

Signed-off-by: Gary Lin <glin@suse.com>
---
 src/authenticode.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++
 src/authenticode.h |  2 ++
 2 files changed, 77 insertions(+)

Index: pcr-oracle-0.4.6/src/authenticode.c
===================================================================
--- pcr-oracle-0.4.6.orig/src/authenticode.c
+++ pcr-oracle-0.4.6/src/authenticode.c
@@ -91,10 +91,14 @@ struct pecoff_image_info {
 		uint16_t	machine_id;
 		uint16_t	num_sections;
 		uint32_t	symtab_offset;
+		uint32_t	num_symbols;
 		uint16_t	optional_hdr_size;
 		uint32_t	optional_hdr_offset;
 
 		uint32_t	section_table_offset;
+
+		uint32_t	strtab_offset;
+		uint32_t	strtab_size;
 	} pe_hdr;
 
 	struct {
@@ -111,6 +115,9 @@ struct pecoff_image_info {
 	pecoff_section_t *	section;
 
 	authenticode_image_info_t auth_info;
+
+	/* The contents of .sbatlevel */
+	buffer_t *		sbatlevel;
 };
 
 #define MSDOS_STUB_PE_OFFSET	0x3c
@@ -147,6 +154,7 @@ void
 pecoff_image_info_free(pecoff_image_info_t *img)
 {
 	buffer_free(img->data);
+	buffer_free(img->sbatlevel);
 	free(img->display_name);
 	free(img->data_dirs);
 	free(img->section);
@@ -351,6 +359,8 @@ __pecoff_process_header(buffer_t *in, pe
 
 	if (!__pecoff_get_u32(in, img, PECOFF_HEADER_SYMTAB_POS_OFFSET, &img->pe_hdr.symtab_offset))
 		return false;
+	if (!__pecoff_get_u32(in, img, PECOFF_HEADER_SYMTAB_CNT_OFFSET, &img->pe_hdr.num_symbols))
+		return false;
 
 	img->pe_hdr.optional_hdr_offset = img->pe_hdr.offset + PECOFF_HEADER_LENGTH;
 	if (!__pecoff_get_u16(in, img, PECOFF_HEADER_OPTIONAL_HDR_SIZE_OFFSET, &img->pe_hdr.optional_hdr_size))
@@ -358,6 +368,19 @@ __pecoff_process_header(buffer_t *in, pe
 
 	img->pe_hdr.section_table_offset = img->pe_hdr.optional_hdr_offset + img->pe_hdr.optional_hdr_size;
 
+	/* String table follows symbol table immediately.
+	 * One symbol is 18 bytes, so the offset to string table is
+	 *   symtab_offset + num_symbols * 18
+	 *
+	 * ref: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#coff-string-table
+	 *      https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#coff-symbol-table
+	 */
+	if (img->pe_hdr.symtab_offset != 0) {
+		img->pe_hdr.strtab_offset = img->pe_hdr.symtab_offset + (img->pe_hdr.num_symbols * 18);
+		if (!__pecoff_get_u32(in, img, img->pe_hdr.strtab_offset, &img->pe_hdr.strtab_size))
+			return false;
+	}
+
 	return true;
 }
 
@@ -451,6 +474,27 @@ __pecoff_process_optional_header(buffer_
 }
 
 static bool
+__pecoff_name_to_offset(char *sec_name, uint32_t *offset)
+{
+	uint32_t result = 0;
+	int i;
+
+	if (sec_name[0] != '/')
+		return false;
+
+	for (i = 1; i < 8 && sec_name[i] != '\0'; i++) {
+		if (sec_name[i] < '0' || sec_name[i] > '9')
+			return false;
+
+		result = result * 10 + sec_name[i] - '0';
+	}
+
+	*offset = result;
+
+	return true;
+}
+
+static bool
 __pecoff_process_sections(buffer_t *in, pecoff_image_info_t *info)
 {
 	unsigned int tbl_offset = info->pe_hdr.section_table_offset;
@@ -458,6 +502,9 @@ __pecoff_process_sections(buffer_t *in,
 	buffer_t hdr;
 	unsigned int i;
 	pecoff_section_t *sec;
+	uint32_t str_offset;
+	char *long_name;
+	buffer_t *sec_buf;
 
 	pe_debug("  Processing %u sections (table at offset %u)\n", num_sections, tbl_offset);
 
@@ -483,6 +530,27 @@ __pecoff_process_sections(buffer_t *in,
 
 		pe_debug("  Section %-8s raw %7u at 0x%08x-0x%08x\n",
 				sec->name, sec->raw.size, sec->raw.addr, sec->raw.addr + sec->raw.size);
+		/* Process the section names longer than 8 bytes */
+		long_name = NULL;
+		if (__pecoff_name_to_offset(sec->name, &str_offset) &&
+		    str_offset < info->pe_hdr.strtab_size) {
+			long_name = (char *)(in->data + info->pe_hdr.strtab_offset + str_offset);
+			pe_debug("  Long Name: %s\n", long_name);
+		}
+
+		/* Get sbatlevel from .sbatlevel section */
+		if (long_name != NULL && strcmp(long_name, ".sbatlevel") == 0) {
+			if (!(sec_buf = buffer_alloc_write(sec->raw.size)))
+				return false;
+
+			if (!buffer_seek_read(in, sec->raw.addr))
+				return false;
+
+			if (!buffer_copy(in, sec->raw.size, sec_buf))
+				return false;
+
+			info->sbatlevel = sec_buf;
+		}
 	}
 
 	/* We are supposed to sort the sections in ascending order, but we're not doing it here, we
@@ -506,6 +574,7 @@ __pecoff_show_header(pecoff_image_info_t
 	pe_debug("  Architecture: %s\n", __pecoff_get_machine(img));
 	pe_debug("  Number of sections: %d\n", img->pe_hdr.num_sections);
 	pe_debug("  Symbol table position: 0x%08x\n", img->pe_hdr.symtab_offset);
+	pe_debug("  String table position: 0x%08x\n", img->pe_hdr.strtab_offset);
 	pe_debug("  Optional header size: %d\n", img->pe_hdr.optional_hdr_size);
 }
 
@@ -751,3 +820,9 @@ authenticode_get_signer(const pecoff_ima
 	cert_table_free(cert_tbl);
 	return signer;
 }
+
+buffer_t *
+pecoff_image_get_sbatlevel(pecoff_image_info_t *img)
+{
+	return img->sbatlevel;
+}
Index: pcr-oracle-0.4.6/src/authenticode.h
===================================================================
--- pcr-oracle-0.4.6.orig/src/authenticode.h
+++ pcr-oracle-0.4.6/src/authenticode.h
@@ -29,5 +29,7 @@ extern tpm_evdigest_t *	authenticode_get
 extern cert_table_t *	authenticode_get_certificate_table(const pecoff_image_info_t *img);
 extern parsed_cert_t *	authenticode_get_signer(const pecoff_image_info_t *);
 
+extern buffer_t *	pecoff_image_get_sbatlevel(pecoff_image_info_t *);
+
 #endif /* AUTHENTICODE_H */
 
Index: pcr-oracle-0.4.6/src/efi-application.c
===================================================================
--- pcr-oracle-0.4.6.orig/src/efi-application.c
+++ pcr-oracle-0.4.6/src/efi-application.c
@@ -278,6 +278,7 @@ static const tpm_evdigest_t *
 __tpm_event_efi_bsa_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *parsed, tpm_event_log_rehash_ctx_t *ctx)
 {
 	const struct efi_bsa_event *evspec = &parsed->efi_bsa_event;
+	buffer_t *sbatlevel;
 
 	/* Some BSA events do not refer to files, but to some data blobs residing somewhere on a device.
 	 * We're not yet prepared to handle these, so we hope the user doesn't mess with them, and
@@ -288,6 +289,14 @@ __tpm_event_efi_bsa_rehash(const tpm_eve
 		return tpm_event_get_digest(ev, ctx->algo->openssl_name);
 	}
 
+	/* Set the sbatlevel section from shim.efi */
+	if (ctx->sbatlevel == NULL
+	 && (sbatlevel = pecoff_image_get_sbatlevel(evspec->img_info)) != NULL) {
+		if ((ctx->sbatlevel = buffer_alloc_write(sbatlevel->size)) == NULL
+		 || !buffer_copy(sbatlevel, sbatlevel->size, ctx->sbatlevel))
+			return NULL;
+	}
+
 	if (ctx->use_pesign)
 		return __efi_application_rehash_pesign(ctx, evspec->efi_partition, evspec->efi_application);
 
Index: pcr-oracle-0.4.6/src/efi-variable.c
===================================================================
--- pcr-oracle-0.4.6.orig/src/efi-variable.c
+++ pcr-oracle-0.4.6/src/efi-variable.c
@@ -93,6 +93,159 @@ __tpm_event_efi_variable_build_event(con
 	return bp;
 }
 
+#define SBATLEVELRT_VARNAME "SbatLevelRT-605dab50-e046-4300-abb6-3dd810dd8b23"
+#define SBATPOLICY_VARNAME "SbatPolicy-605dab50-e046-4300-abb6-3dd810dd8b23"
+#define SECUREBOOT_VARNAME "SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c"
+
+#define POLICY_LATEST		1
+#define POLICY_AUTOMATIC	2
+#define POLICY_RESET		3
+
+#define SBAT_ORIGINAL "sbat,1,2021030218\n"
+
+static bool
+parse_sbatlevel_section(buffer_t *sec, char **sbat_automatic, char **sbat_latest)
+{
+	uint32_t fmt_ver;
+	uint32_t offset_auto;
+	uint32_t offset_latest;
+
+	if (!buffer_get_u32le(sec, &fmt_ver)
+	 || !buffer_get_u32le(sec, &offset_auto)
+	 || !buffer_get_u32le(sec, &offset_latest))
+		return false;
+
+	if (offset_auto >= offset_latest)
+		return false;
+
+	if (!buffer_seek_read(sec, offset_auto + 4))
+		return false;
+	*sbat_automatic = (char *)buffer_read_pointer(sec);
+
+	if (!buffer_seek_read(sec, offset_latest + 4))
+		return false;
+	*sbat_latest = (char *)(buffer_read_pointer(sec));
+
+	return true;
+}
+
+static bool
+fetch_sbat_datestamp(const char *sbat, size_t size, uint32_t *datestamp)
+{
+	uint32_t date = 0;
+	size_t i;
+
+	/* Expected string: "sbat,X,YYYYYYYYYY\n" */
+	if (size < 17)
+		return false;
+
+	if (strncmp(sbat, "sbat,", 5) != 0)
+		return false;
+
+	for (i = 5; i < size && sbat[i] != ','; i++);
+	i++;
+	if (i >= size)
+		return false;
+
+	for (; i < size && sbat[i] != '\n'; i++) {
+		if (sbat[i] < '0' || sbat[i] > '9')
+			return false;
+		date = date * 10 + sbat[i] - '0';
+	}
+
+	*datestamp = date;
+	return true;
+}
+
+static buffer_t *
+efi_sbatlevel_get_record(buffer_t *sbatlevel)
+{
+	char *sbat_automatic;
+	char *sbat_latest;
+	const char *sbat_candidate;
+	const char *sbat_current;
+	buffer_t *buffer = NULL;
+	buffer_t *sbatlvlrt = NULL;
+	buffer_t *result = NULL;
+	uint8_t secureboot;
+	uint8_t sbatpolicy;
+	uint32_t current_date;
+	uint32_t candidate_date;
+	bool sbat_reset = false;
+
+	if (!parse_sbatlevel_section(sbatlevel, &sbat_automatic, &sbat_latest)) {
+		error("Unable to process SbatLevel\n");
+		return NULL;
+	}
+
+	buffer = runtime_read_efi_variable(SECUREBOOT_VARNAME);
+	if (buffer == NULL || !buffer_get_u8(buffer, &secureboot))
+		secureboot = 0;
+	buffer_free(buffer);
+
+	buffer = runtime_read_efi_variable(SBATPOLICY_VARNAME);
+	if (buffer == NULL || !buffer_get_u8(buffer, &sbatpolicy))
+		sbatpolicy = POLICY_AUTOMATIC;
+	buffer_free(buffer);
+
+	switch (sbatpolicy) {
+	case POLICY_LATEST:
+		sbat_candidate = sbat_latest;
+		break;
+	case POLICY_AUTOMATIC:
+		sbat_candidate = sbat_automatic;
+		break;
+	case POLICY_RESET:
+		if (secureboot == 1) {
+			infomsg("SBAT cannot be reset when Secure Boot is enabled.\n");
+			sbat_candidate = sbat_automatic;
+		} else {
+			sbat_candidate = SBAT_ORIGINAL;
+		}
+		break;
+	default:
+		error("Invalid SBAT policy\n");
+		return NULL;
+	}
+
+	if ((sbatlvlrt = runtime_read_efi_variable(SBATLEVELRT_VARNAME)) == NULL) {
+		error("Unable to read SbatLevelRT\n");
+		return NULL;
+	}
+
+	sbat_current = (const char *)buffer_read_pointer(sbatlvlrt);
+
+	if (!fetch_sbat_datestamp(sbat_current, sbatlvlrt->size, &current_date)
+	 || !fetch_sbat_datestamp(sbat_candidate, strlen(sbat_candidate), &candidate_date)) {
+		error("Unable to get SBAT timestamp\n");
+		goto fail;
+	}
+
+	debug("Current SBAT datestampe: %u\n", current_date);
+	debug("Candidate SBAT datestampe: %u\n", candidate_date);
+
+	if (current_date >= candidate_date && sbat_reset == false) {
+		debug("Use current SbatLevel\n");
+		result = sbatlvlrt;
+	} else {
+		debug("Use candidate SbatLevel\n");
+		buffer_free(sbatlvlrt);
+
+		/* Copy the candidate SbatLevel string without the terminating null */
+		if ((result = buffer_alloc_write(strlen(sbat_candidate))) == NULL
+		 || !buffer_put(result, sbat_candidate, strlen(sbat_candidate)))
+			goto fail;
+	}
+
+	return result;
+
+fail:
+	buffer_free(sbatlvlrt);
+	buffer_free(result);
+
+	return NULL;
+}
+
 enum {
 	HASH_STRATEGY_EVENT,
 	HASH_STRATEGY_DATA,
@@ -114,6 +267,11 @@ efi_variable_authority_get_record(const
 	} else
 	if (!strcmp(var_short_name, "MokListRT")) {
 		db_name = "MokList";
+	} else
+	if (!strcmp(var_short_name, "SbatLevel")) {
+		if (ctx->sbatlevel != NULL)
+			return efi_sbatlevel_get_record(ctx->sbatlevel);
+		return runtime_read_efi_variable(var_name);
 	} else {
 		/* Read as-is (this could be SbatLevel, or some other variable that's not
 		 * a signature db). */
Index: pcr-oracle-0.4.6/src/eventlog.c
===================================================================
--- pcr-oracle-0.4.6.orig/src/eventlog.c
+++ pcr-oracle-0.4.6/src/eventlog.c
@@ -921,6 +921,7 @@ tpm_event_log_rehash_ctx_init(tpm_event_
 void
 tpm_event_log_rehash_ctx_destroy(tpm_event_log_rehash_ctx_t *ctx)
 {
+	buffer_free(ctx->sbatlevel);
 }
 
 void
Index: pcr-oracle-0.4.6/src/eventlog.h
===================================================================
--- pcr-oracle-0.4.6.orig/src/eventlog.h
+++ pcr-oracle-0.4.6/src/eventlog.h
@@ -195,6 +195,8 @@ typedef struct tpm_event_log_rehash_ctx
 	bool			use_pesign;		/* compute authenticode FP using external pesign application */
 
 	const pecoff_image_info_t *next_stage_img;
+
+	buffer_t *		sbatlevel;
 } tpm_event_log_rehash_ctx_t;
 
 #define GRUB_COMMAND_ARGV_MAX	32
