SBAT support in EFI stub?
From: kernel . org
Date: Sun Jan 12 2025 - 10:52:11 EST
Folks,
The problem: *secure boot* directly via EFI stub on a system,
where the necessary key management options are not present
(disabled by the OEM) in the BIOS.
Unless one is prepared to hack that BIOS, following, say
https://lukegb.com/posts/2016-11-11-secure-boot-shenanigans/
the standard approach would be to use SHIM (and MOK):
https://github.com/rhboot/shim
A few years back, CVE-2020-10713 ("BootHole") prompted
what one might call a lightweight revocation solution:
Secure Boot Advanced Targeting (SBAT).
https://github.com/rhboot/shim/blob/main/SBAT.md
As a result, SHIM flatly refuses to load an EFI binary without
an embedded SBAT section.
Adding SBAT to a "plain" kernel, as described, for instance here:
https://www.rodsbooks.com/efi-bootloaders/secureboot.html#sbat
works fine, but doing the same for a kernel with EFI stub
messes up the binary and results in a boot loop. That's,
perhaps, not surprising considering that the intervention
also changes the file's signature:
```
$ file bzImage-6.12.9-gentoo
bzImage-6.12.9-gentoo: Linux kernel x86 boot executable bzImage, version 6.12.9-gentoo ...
$ objcopy --set-section-alignment '.sbat=512' --add-section .sbat=sbat.csv --adjust-section-vma .sbat+10000000 bzImage-6.12.9-gentoo
$ file bzImage-6.12.9-gentoo
bzImage-6.12.9-gentoo: PE32+ executable (EFI application) x86-64 (stripped to external PDB), for MS Windows, 4 sections
$
```
NB: I have NOT tried the `pe-add-sections.py` script linked from
https://github.com/rhboot/shim/issues/376#issuecomment-1628004034
but I'd expect similar results to the `objcopy` case above, since
the EFI stub is a bit "special" in its construction.
BTW, workarounds for secure boot on such a system via OTHER MEANS
than EFI stub do exist. One could simply employ a bootloader,
or build a Unified Kernel Image using `ukify` that will add both
its own "EFIStub" and an SBAT section to a "plain" kernel.
https://manpages.debian.org/bookworm-backports/systemd/ukify.1.en.html
https://wiki.gentoo.org/wiki/Unified_kernel_image#EFI_stub
I realize that wanting to stick with the kernel's built-in EFI stub
may be a bit of a niche issue, and I do have a WORKSFORME-level hack
to address this for myself (attached, since it's not a proper patch
for review, only a conversation starter); so, this isn't high priority.
I'm only checking if adding SBAT support may be of general interest.
Best,
--andrás
PS
BTW, I wasn't kidding. My current approach_is_ a hack.
The included `sbat.csv` is for Gentoo (although simple to adapt).
The code blindly uses a fixed (overly large) size for SBAT.
The final binary MUST be renamed to `grubx64.efi` (or similar,
for 32-bit systems); AND, it MUST be enrolled by hash, since
I haven't added any code that might satisfy the "participating
bootloader" requirements.
https://github.com/rhboot/shim/commit/39df41ceb5a793f7db9233a2741d30c55b6a8861
Interestingly, MOK Manager cannot (or doesn't want to?) extract
the hash from a signed binary; so, either the hash must be
enrolled via `mokutil`, or the kernel can be left unsigned...
diff '--color=auto' -ruN linux-6.12.1-gentoo/arch/x86/boot/Makefile linux-6.12.1-gentoo.sbat/arch/x86/boot/Makefile
--- linux-6.12.1-gentoo/arch/x86/boot/Makefile 2024-11-22 22:47:28.288743052 +0100
+++ linux-6.12.1-gentoo.sbat/arch/x86/boot/Makefile 2024-11-23 13:39:34.930313632 +0100
@@ -63,9 +63,9 @@
quiet_cmd_image = BUILD $@
silent_redirect_image = >/dev/null
cmd_image = $(obj)/tools/build $(obj)/setup.bin $(obj)/vmlinux.bin \
- $(obj)/zoffset.h $@ $($(quiet)redirect_image)
+ $(obj)/zoffset.h $(obj)/sbat.csv $@ $($(quiet)redirect_image)
-$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE
+$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/sbat.csv $(obj)/tools/build FORCE
$(call if_changed,image)
@$(kecho) 'Kernel: $@ is ready' ' (#'$(or $(KBUILD_BUILD_VERSION),`cat .version`)')'
@@ -75,6 +75,16 @@
SETUP_OBJS = $(addprefix $(obj)/,$(setup-y))
+
+quiet_cmd_sbat = SBAT $@
+ cmd_sbat = sed s@%KERNELVERSION%@$(KERNELVERSION)@ $(srctree)/$@ > $@
+
+
+targets += sbat.csv
+$(obj)/sbat.csv: $(srctree)/$@ FORCE
+ $(call if_changed,sbat)
+
+
sed-zoffset := -e 's/^\([0-9a-fA-F]*\) [a-zA-Z] \(startup_32\|efi.._stub_entry\|efi\(32\)\?_pe_entry\|input_data\|kernel_info\|_end\|_ehead\|_text\|_e\?data\|z_.*\)$$/\#define ZO_\2 0x\1/p'
quiet_cmd_zoffset = ZOFFSET $@
diff '--color=auto' -ruN linux-6.12.1-gentoo/arch/x86/boot/header.S linux-6.12.1-gentoo.sbat/arch/x86/boot/header.S
--- linux-6.12.1-gentoo/arch/x86/boot/header.S 2024-11-22 22:47:28.291743064 +0100
+++ linux-6.12.1-gentoo.sbat/arch/x86/boot/header.S 2024-11-30 15:33:33.427367030 +0100
@@ -83,9 +83,9 @@
.long ZO__end - ZO__data # SizeOfInitializedData
.long 0 # SizeOfUninitializedData
- .long setup_size + ZO_efi_pe_entry # AddressOfEntryPoint
+ .long setup_size + salign + ZO_efi_pe_entry # AddressOfEntryPoint
- .long setup_size # BaseOfCode
+ .long setup_size + salign # BaseOfCode
#ifdef CONFIG_X86_32
.long 0 # data
#endif
@@ -106,7 +106,7 @@
.word 0 # MinorSubsystemVersion
.long 0 # Win32VersionValue
- .long setup_size + ZO__end # SizeOfImage
+ .long setup_size + salign + ZO__end # SizeOfImage
.long salign # SizeOfHeaders
.long 0 # CheckSum
@@ -179,15 +179,25 @@
#else
.set pecompat_fstart, setup_size
#endif
+
+ .ascii ".sbat\0\0\0"
+ .long salign # SizeOfRawData
+ .long setup_size # VirtualAddress
+ .long salign # SizeOfRawData
+ .long setup_size # PointerToRawData
+
+ .long 0, 0, 0
+ .long IMAGE_SCN_ALIGN_512BYTES
+
.ascii ".text"
.byte 0
.byte 0
.byte 0
.long ZO__data
- .long setup_size
+ .long setup_size + salign
.long ZO__data # Size of initialized data
# on disk
- .long setup_size
+ .long setup_size + salign
.long 0 # PointerToRelocations
.long 0 # PointerToLineNumbers
.word 0 # NumberOfRelocations
@@ -198,9 +208,9 @@
.ascii ".data\0\0\0"
.long ZO__end - ZO__data # VirtualSize
- .long setup_size + ZO__data # VirtualAddress
+ .long setup_size + salign + ZO__data # VirtualAddress
.long ZO__edata - ZO__data # SizeOfRawData
- .long setup_size + ZO__data # PointerToRawData
+ .long setup_size + salign + ZO__data # PointerToRawData
.long 0, 0, 0
.long IMAGE_SCN_CNT_INITIALIZED_DATA | \
diff '--color=auto' -ruN linux-6.12.1-gentoo/arch/x86/boot/sbat.csv linux-6.12.1-gentoo.sbat/arch/x86/boot/sbat.csv
--- linux-6.12.1-gentoo/arch/x86/boot/sbat.csv 1970-01-01 01:00:00.000000000 +0100
+++ linux-6.12.1-gentoo.sbat/arch/x86/boot/sbat.csv 2024-11-23 13:39:34.930313632 +0100
@@ -0,0 +1,2 @@
+sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
+kernel,1,Gentoo Linux,gentoo-sources,%KERNELVERSION%,https://www.gentoo.org
diff '--color=auto' -ruN linux-6.12.1-gentoo/arch/x86/boot/tools/build.c linux-6.12.1-gentoo.sbat/arch/x86/boot/tools/build.c
--- linux-6.12.1-gentoo/arch/x86/boot/tools/build.c 2024-11-22 22:47:28.292743068 +0100
+++ linux-6.12.1-gentoo.sbat/arch/x86/boot/tools/build.c 2024-11-30 15:27:50.721048086 +0100
@@ -6,11 +6,15 @@
*/
/*
- * This file builds a disk-image from three different files:
+ * This file builds a disk-image from 4 different files:
*
* - setup: 8086 machine code, sets up system parm
* - system: 80386 code for actual system
* - zoffset.h: header with ZO_* defines
+ * - sbat.csv: policy specification for Secure Boot Advanced Targeting
+ * only needed when CONFIG_EFI_STUB is selected,
+ * and secure boot is enabled, but does no harm
+ * to include SBAT in either case
*
* It does some checking that all files are of the correct type, and writes
* the result to the specified destination, removing headers and padding to
@@ -43,6 +47,7 @@
/* Minimal number of setup sectors */
#define SETUP_SECT_MIN 5
#define SETUP_SECT_MAX 64
+#define SBAT_SIZE 4096
/* This must be large enough to hold the entire setup */
u8 buf[SETUP_SECT_MAX*512];
@@ -130,7 +135,7 @@
static void usage(void)
{
- die("Usage: build setup system zoffset.h image");
+ die("Usage: build setup system zoffset.h sbat.csv image");
}
/*
@@ -179,13 +184,13 @@
void *kernel;
u32 crc = 0xffffffffUL;
- if (argc != 5)
+ if (argc != 6)
usage();
parse_zoffset(argv[3]);
- dest = fopen(argv[4], "w");
+ dest = fopen(argv[5], "w");
if (!dest)
- die("Unable to write `%s': %m", argv[4]);
+ die("Unable to write `%s': %m", argv[5]);
/* Copy the setup code */
file = fopen(argv[1], "r");
@@ -215,8 +220,7 @@
if (fstat(fd, &sb))
die("Unable to stat `%s': %m", argv[2]);
if (_edata != sb.st_size)
- die("Unexpected file size `%s': %u != %u", argv[2], _edata,
- sb.st_size);
+ die("Unexpected file size `%s': %u != %u", argv[2], _edata, sb.st_size);
sz = _edata - 4;
kernel = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0);
if (kernel == MAP_FAILED)
@@ -226,6 +230,22 @@
if (fwrite(buf, 1, i, dest) != i)
die("Writing setup failed");
+ /* Copy CSV for Secure Boot Advanced Targeting (SBAT) */
+ file = fopen(argv[4], "r");
+ if (!file)
+ die("Unable to open `%s': %m", argv[4]);
+ c = fread(buf, 1, sizeof(buf), file);
+ if (ferror(file))
+ die("read-error on SBAT");
+ fclose(file);
+ if (c > SBAT_SIZE)
+ die("SBAT must be at most %u bytes", SBAT_SIZE);
+ if (c < SBAT_SIZE)
+ memset(buf+c, 0, SBAT_SIZE-c);
+ crc = partial_crc32(buf, SBAT_SIZE, crc);
+ if (fwrite(buf, 1, SBAT_SIZE, dest) != SBAT_SIZE)
+ die("Writing SBAT failed");
+
/* Copy the kernel code */
crc = partial_crc32(kernel, sz, crc);
if (fwrite(kernel, 1, sz, dest) != sz)