SBAT support in EFI stub?

kernel.org@andraxin.se posted 1 patch 1 year ago
SBAT support in EFI stub?
Posted by kernel.org@andraxin.se 1 year ago
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)