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