From 14af6bbafd771b48c507bfce3b7dc582d0b65c6f Mon Sep 17 00:00:00 2001
From: Alexey Makhalov <amakhalov@vmware.com>
Date: Mon, 9 Oct 2023 09:00:46 -0700
Subject: [PATCH 3/3] Verify SBAT on kexec

To extend Secure Boot trusted chain to next executed kernel.
Introduce a logic to verify .sbat section of new bzImage against
SBAT entries from EFI variable used by shim.

Signed-off-by: Alexey Makhalov <amakhalov@vmware.com>
---
 crypto/asymmetric_keys/verify_pefile.c |  24 +++
 drivers/firmware/efi/libstub/efistub.h |   1 +
 include/linux/verification.h           |  11 ++
 kernel/kexec_file.c                    |   4 +
 security/Kconfig                       |  13 ++
 security/Makefile                      |   1 +
 security/sbat.c                        | 229 +++++++++++++++++++++++++
 7 files changed, 283 insertions(+)
 create mode 100644 security/sbat.c

diff --git a/crypto/asymmetric_keys/verify_pefile.c b/crypto/asymmetric_keys/verify_pefile.c
index 22beaf221..7813de5cf 100644
--- a/crypto/asymmetric_keys/verify_pefile.c
+++ b/crypto/asymmetric_keys/verify_pefile.c
@@ -454,3 +454,27 @@ int verify_pefile_signature(const void *pebuf, unsigned pelen,
 	kfree_sensitive(ctx.digest);
 	return ret;
 }
+
+int pefile_find_section(const char *section_name, const void *pebuf,
+			unsigned pelen, char **section, unsigned *len)
+{
+	struct pefile_context ctx;
+	int ret, i;
+
+	memset(&ctx, 0, sizeof(ctx));
+	ret = pefile_parse_binary(pebuf, pelen, &ctx);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < ctx.n_sections; i++) {
+		struct section_header s = ctx.secs[i];
+
+		if (!strcmp(section_name, s.name)) {
+			*section = (char *)pebuf + s.data_addr;
+			*len = s.raw_data_size;
+			return 0;
+		}
+	}
+
+	return -ENODATA;
+}
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index 970e86e3a..747a5f7aa 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -967,6 +967,7 @@ asmlinkage void __noreturn efi_enter_kernel(unsigned long entrypoint,
 void efi_handle_post_ebs_state(void);
 
 enum efi_secureboot_mode efi_get_secureboot(void);
+efi_status_t efi_get_sbatlevel(char **data, unsigned long *size);
 
 #ifdef CONFIG_RESET_ATTACK_MITIGATION
 void efi_enable_reset_attack_mitigation(void);
diff --git a/include/linux/verification.h b/include/linux/verification.h
index f34e50ebc..9a4fcef83 100644
--- a/include/linux/verification.h
+++ b/include/linux/verification.h
@@ -66,6 +66,17 @@ extern int verify_pkcs7_message_sig(const void *data, size_t len,
 extern int verify_pefile_signature(const void *pebuf, unsigned pelen,
 				   struct key *trusted_keys,
 				   enum key_being_used_for usage);
+extern int pefile_find_section(const char *section_name, const void *pebuf,
+			       unsigned pelen, char **section, unsigned *len);
+#endif
+
+#ifdef CONFIG_SECURITY_SBAT_VERIFY
+extern int verify_pefile_sbat(const char *pebuf, unsigned long pelen);
+#else
+static inline int verify_pefile_sbat(const char *pebuf, unsigned long pelen)
+{
+	return 0;
+}
 #endif
 
 #endif /* CONFIG_SYSTEM_DATA_VERIFICATION */
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index 6d787ae9d..ce67705cf 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -131,6 +131,10 @@ int kexec_kernel_verify_pe_sig(const char *kernel, unsigned long kernel_len)
 {
 	int ret;
 
+	ret = verify_pefile_sbat(kernel, kernel_len);
+	if (ret)
+		return ret;
+
 	ret = verify_pefile_signature(kernel, kernel_len,
 				      VERIFY_USE_SECONDARY_KEYRING,
 				      VERIFYING_KEXEC_PE_SIGNATURE);
diff --git a/security/Kconfig b/security/Kconfig
index 536689678..7acc4986b 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -218,6 +218,18 @@ config SECURITY_SBAT
 	  See <https://github.com/rhboot/shim/blob/main/SBAT.md> for more information
 	  about SBAT.
 
+config SECURITY_SBAT_VERIFY
+	bool "Verify .sbat section of kexec bzImage"
+	depends on SECURITY
+	depends on SIGNED_PE_FILE_VERIFICATION
+	depends on EFI
+	help
+	  If set, kexec will perform .sbat generations verification in addition to
+	  image signature verification.
+
+	  See <https://github.com/rhboot/shim/blob/main/SBAT.md> for more information
+	  about SBAT.
+
 source "security/landlock/Kconfig"
 
 source "security/integrity/Kconfig"
diff --git a/security/Makefile b/security/Makefile
index 18121f8f8..38da41d50 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_MMU)			+= min_addr.o
 # Object file lists
 obj-$(CONFIG_SECURITY)			+= security.o
 obj-$(CONFIG_SECURITYFS)		+= inode.o
+obj-$(CONFIG_SECURITY_SBAT_VERIFY)	+= sbat.o
 obj-$(CONFIG_SECURITY_SELINUX)		+= selinux/
 obj-$(CONFIG_SECURITY_SMACK)		+= smack/
 obj-$(CONFIG_SECURITY)			+= lsm_audit.o
diff --git a/security/sbat.c b/security/sbat.c
new file mode 100644
index 000000000..80168215f
--- /dev/null
+++ b/security/sbat.c
@@ -0,0 +1,229 @@
+#define pr_fmt(fmt) "SBAT: "fmt
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/verification.h>
+#include <linux/efi.h>
+
+/* SHIM variables */
+static efi_guid_t shim_guid = EFI_SHIM_LOCK_GUID;
+static efi_char16_t shim_SbatLevel_name[] = L"SbatLevelRT";
+
+#define FIELDS 6
+
+struct sbat_entry {
+	union {
+		const char *field[FIELDS];
+		struct {
+			const char *name;
+			const char *genstr;
+			const char *vendor_name;
+			const char *vendor_package_name;
+			const char *vendor_version;
+			const char *vendor_url;
+		};
+	};
+	unsigned long gen;
+};
+
+struct sbat {
+	struct sbat_entry *entries;
+	int n_entries;
+	char data[];
+};
+
+static void free_sbat(struct sbat *s)
+{
+	kfree(s->entries);
+	kfree(s);
+};
+
+static struct sbat* parse_sbat(struct sbat *s, const unsigned size, bool full_parse)
+{
+	int i, e, f;
+
+	s->n_entries = 1;
+	for (i = 0; i < size - 1; i++)
+		if (s->data[i] == '\xA')
+			s->n_entries++;
+
+	s->entries = kcalloc(s->n_entries, sizeof(struct sbat_entry*), GFP_KERNEL);
+	if (!s->entries)
+		return ERR_PTR(-ENOMEM);
+
+	e = 0; f = 0;
+	s->entries[e].field[f] = &s->data[0];
+	for (i = 0; i < size; i++) {
+		if (s->data[i] == ',') {
+			if (!full_parse && f == 2)
+				continue;
+			f++;
+			/* Too many fields per line */
+			if (f == FIELDS)
+				goto error;
+			s->data[i++] = '\0';
+			/* Corrupted SBAT ? */
+			if (i >= size)
+				goto error;
+			s->entries[e].field[f] = &s->data[i];
+
+		}
+		else if (s->data[i] == '\xA') {
+			if (full_parse && f != FIELDS - 1)
+				goto error;
+			/* The line must have at least 2 entries */
+			if (!f)
+				goto error;
+
+			s->data[i++] = '\0';
+			/* Convert generation field to integer */
+			if (kstrtoul(s->entries[e].field[1], 0, &s->entries[e].gen) < 0)
+				goto error;
+
+			e++;
+			f = 0;
+			if (e <= s->n_entries)
+				s->entries[e].field[f] = &s->data[i];
+		}
+	}
+	/* Handle non 0xA terminated SBAT */
+	if ((e == s->n_entries - 1) && ((f == FIELDS - 1) || !full_parse)) {
+		/* The line must have at least 2 entries */
+		if (!f)
+			goto error;
+		s->data[size] = '\0';
+		/* Convert generation field to integer */
+		if (kstrtoul(s->entries[e].field[1], 0, &s->entries[e].gen) < 0)
+			goto error;
+		e++;
+		f = 0;
+	}
+	/* Corrupted SBAT ? */
+	if (e != s->n_entries || f)
+		goto error;
+
+	return s;
+
+error:
+	kfree(s->entries);
+	return ERR_PTR(-EILSEQ);
+}
+
+struct sbat* get_sbat_from_pefile(const char *pebuf, unsigned long pelen)
+{
+	struct sbat *s, *out;
+	char *sbat_data;
+	unsigned size;
+	int ret;
+
+	ret = pefile_find_section(".sbat", pebuf, pelen, &sbat_data, &size);
+	if (ret < 0) {
+		pr_warn("PE binary has no .sbat section\n");
+		return ERR_PTR(ret);
+	}
+
+	if (!size) {
+		pr_warn("PE binary has empty .sbat section\n");
+		return ERR_PTR(-ENODATA);
+	}
+
+	s = kmalloc(sizeof(struct sbat) + size + 1, GFP_KERNEL);
+	if (!s)
+		return ERR_PTR(-ENOMEM);
+	memcpy(&s->data, sbat_data, size);
+	s->data[size] = '\xA';
+
+	out = parse_sbat(s, size, true);
+	if (IS_ERR(out))
+		kfree(s);
+
+	return out;
+}
+
+struct sbat* get_sbat_from_efi(void)
+{
+	efi_status_t status;
+	struct sbat *s, *out;
+	unsigned long size = 0;
+	int ret;
+
+	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) {
+		pr_info("Unable to get SBAT level from EFI\n");
+		ret = efi_enabled(EFI_BOOT)? -ENOPROTOOPT : 0;
+		return ERR_PTR(ret);
+	}
+
+	status = efi.get_variable(shim_SbatLevel_name, &shim_guid,
+				  NULL, &size, NULL);
+
+	if (status != EFI_BUFFER_TOO_SMALL || !size)
+		return ERR_PTR(-ENODATA);
+
+	s = kmalloc(sizeof(struct sbat) + size + 1, GFP_KERNEL);
+	if (!s)
+		return ERR_PTR(-ENOMEM);
+
+	status = efi.get_variable(shim_SbatLevel_name, &shim_guid,
+				  NULL, &size, s->data);
+	if (status != EFI_SUCCESS) {
+		kfree(s);
+		return ERR_PTR(-ENODATA);
+	}
+
+	out = parse_sbat(s, size, false);
+	if (IS_ERR(out))
+		kfree(s);
+
+	return out;
+}
+
+int verify_pefile_sbat(const char *pebuf, unsigned long pelen)
+{
+	struct sbat *s, *sl;
+	int ret = 0;
+	int i, j;
+
+	s = get_sbat_from_pefile(pebuf, pelen);
+	if (IS_ERR(s))
+		return PTR_ERR(s);
+
+	sl = get_sbat_from_efi();
+	/*
+	 *..OR_NULL for the case when EFI is not available (Legacy BIOS?),
+	 * then return 0 (verified OK)
+	 */
+	if (IS_ERR_OR_NULL(sl)) {
+		kfree(s);
+		return PTR_ERR(sl);
+	}
+
+	pr_debug("PE file:\n");
+	for (i = 0; i < s->n_entries; i++)
+		pr_debug("%d: %s,%ld\n", i, s->entries[i].name,
+			s->entries[i].gen);
+
+	pr_debug("EFI:\n");
+	for (i = 0; i < sl->n_entries; i++)
+		pr_debug("%d: %s,%ld\n", i, sl->entries[i].name,
+			 sl->entries[i].gen);
+
+	for (i = 0; i < s->n_entries; i++) {
+		struct sbat_entry *e = &s->entries[i];
+		for (j = 0; j < sl->n_entries; j++) {
+			struct sbat_entry *el = &sl->entries[j];
+			if (strcmp(e->name, el->name))
+				continue;
+			if (e->gen < el->gen) {
+				pr_warn("Component %s was revoked %ld < %ld\n",
+					e->name, e->gen, el->gen);
+				ret = -EKEYREVOKED;
+				goto quit;
+			}
+		}
+	}
+
+quit:
+	free_sbat(s);
+	free_sbat(sl);
+	return ret;
+}
-- 
2.39.0