Skip to contentSkip to conversation

efi: implement generic compressed boot support

PATCH Superseded Ard Biesheuvel 4 participants 14 messages Aug 9, 2022 – Sep 12, 2022

Subsystems (4)

v2 of 5 Superseded Latest is v5 →

lore
Share & tools
lore

Ard Biesheuvel

author maintainer
#0 lore raw

v2 cover efi: implement generic compressed boot support

Relatively modern architectures such as arm64 or RISC-V don't implement a self-decompressing kernel, and leave it up to the bootloader to decompress the compressed image before executing it. For bare metal boot, this policy makes sense, as a self-decompressing image essentially duplicates a lot of fiddly preparation work to create a 1:1 mapping and set up the C runtime, and to discover or infer where DRAM lives from device trees or other firmware tables.

For EFI boot, the situation is a bit different: the EFI entrypoint is called with a 1:1 cached mapping covering all of DRAM already active, and with a stack, a heap, a memory map and boot services to load and start images. This means it is rather trivial to implement a self-decompressing wrapper for EFI boot in a generic manner, and reuse it across architectures that implement EFI boot.

The only slight downside is that when UEFI secure boot is enabled, the generic LoadImage/StartImage only allow signed images to be loaded and started, and we prefer to avoid the need to sign both the inner and outer PE/COFF images. This series adopts the EFI shim approach, i.e., to override an internal UEFI/PI protocol that is used by the image loader, to allow the inner image to be booted after decompression. This has been tested to work with Tianocore based EFI implementations on arm64, but u-boot will need some interoperability tweaks as well, ideally just a protocol that exposes a LoadImage/StartImage combo that the decompresor can use directly to circumvent the signature check. (Note that EFI apps have full control over the CPU, page tables, etc. so having code that circumvents authentication checks is not as crazy as it sounds, given that the app can do anything it pleases already.)

The code is wired up for arm64 and RISC-V. The latter was build tested only.

Cc "James E.J. Bottomley" <[email protected]>
Cc Matthew Garrett <[email protected]>
Cc Peter Jones <[email protected]>
Cc Ilias Apalodimas <[email protected]>
Cc Heinrich Schuchardt <[email protected]>
Cc AKASHI Takahiro <[email protected]>
Cc Palmer Dabbelt <[email protected]>
Cc Atish Patra <[email protected]>
Cc Arnd Bergmann <[email protected]>
Cc Huacai Chen <[email protected]>
Cc Lennart Poettering <[email protected]>

Ard Biesheuvel (6):
efi: stub: add some missing boot service prototypes
efi: stub: split off printk() routines
efi: stub: move efi_system_table global var into separate object
efi: stub: implement generic EFI zboot
arm64: efi: enable generic EFI compressed boot
riscv: efi: enable generic EFI compressed boot

 arch/arm64/Makefile                            |   5 +
 arch/arm64/boot/Makefile                       |  12 ++
 arch/riscv/Makefile                            |   5 +
 arch/riscv/boot/Makefile                       |  14 ++
 drivers/firmware/efi/Kconfig                   |   9 +
 drivers/firmware/efi/libstub/Makefile          |   7 +-
 drivers/firmware/efi/libstub/Makefile.zboot    |  30 +++
 drivers/firmware/efi/libstub/efi-stub-helper.c | 141 ---------------
 drivers/firmware/efi/libstub/efi-stub.c        |   2 -
 drivers/firmware/efi/libstub/efistub.h         |  12 +-
 drivers/firmware/efi/libstub/printk.c          | 158 ++++++++++++++++
 drivers/firmware/efi/libstub/systable.c        |   8 +
 drivers/firmware/efi/libstub/zboot-header.S    | 144 +++++++++++++++
 drivers/firmware/efi/libstub/zboot.c           | 191 ++++++++++++++++++++
 drivers/firmware/efi/libstub/zboot.lds         |  41 +++++
 include/linux/efi.h                            |   2 +
 16 files changed, 633 insertions(+), 148 deletions(-)
 create mode 100644 drivers/firmware/efi/libstub/Makefile.zboot
 create mode 100644 drivers/firmware/efi/libstub/printk.c
 create mode 100644 drivers/firmware/efi/libstub/systable.c
 create mode 100644 drivers/firmware/efi/libstub/zboot-header.S
 create mode 100644 drivers/firmware/efi/libstub/zboot.c
 create mode 100644 drivers/firmware/efi/libstub/zboot.lds
Signature · 1 line
2.35.1
author maintainer
#1 lore raw

PATCH v2 1/6 efi: stub: add some missing boot service prototypes

Define the correct prototypes for the load_image/start_image and install_multiple_protocol_interfaces boot service pointers so we can call them from the EFI zboot code.

---
 drivers/firmware/efi/libstub/efistub.h | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
drivers/firmware/efi/libstub/efistub.h +8 -4
index ab9e990447d3..33215d7bd276 100644
@@ -254,8 +254,12 @@ union efi_boot_services {
 							    efi_handle_t *);
 		efi_status_t (__efiapi *install_configuration_table)(efi_guid_t *,
 								     void *);
-		void *load_image;
-		void *start_image;
+		efi_status_t (__efiapi *load_image)(bool, efi_handle_t,
+						    efi_device_path_protocol_t *,
+						    void *, unsigned long,
+						    efi_handle_t *);
+		efi_status_t (__efiapi *start_image)(efi_handle_t, unsigned long *,
+						     efi_char16_t **);
 		efi_status_t __noreturn (__efiapi *exit)(efi_handle_t,
 							 efi_status_t,
 							 unsigned long,
@@ -277,8 +281,8 @@ union efi_boot_services {
 		void *locate_handle_buffer;
 		efi_status_t (__efiapi *locate_protocol)(efi_guid_t *, void *,
 							 void **);
-		void *install_multiple_protocol_interfaces;
-		void *uninstall_multiple_protocol_interfaces;
+		efi_status_t (__efiapi *install_multiple_protocol_interfaces)(efi_handle_t *, ...);
+		efi_status_t (__efiapi *uninstall_multiple_protocol_interfaces)(efi_handle_t, ...);
 		void *calculate_crc32;
 		void *copy_mem;
 		void *set_mem;
Signature · 1 line
2.35.1
Reviewed-by on v2 1/6
#2 lore raw

Re: [PATCH v2 1/6] efi: stub: add some missing boot service prototypes

Quoted 5 lines · Ard Biesheuvel

Define the correct prototypes for the load_image/start_image and install_multiple_protocol_interfaces boot service pointers so we can call them from the EFI zboot code.

Quoted 34 lines
---
  drivers/firmware/efi/libstub/efistub.h | 12 ++++++++----
  1 file changed, 8 insertions(+), 4 deletions(-)
drivers/firmware/efi/libstub/efistub.h +8 -4
index ab9e990447d3..33215d7bd276 100644
@@ -254,8 +254,12 @@ union efi_boot_services {
  							    efi_handle_t *);
  		efi_status_t (__efiapi *install_configuration_table)(efi_guid_t *,
  								     void *);
-		void *load_image;
-		void *start_image;
+		efi_status_t (__efiapi *load_image)(bool, efi_handle_t,
+						    efi_device_path_protocol_t *,
+						    void *, unsigned long,
+						    efi_handle_t *);
+		efi_status_t (__efiapi *start_image)(efi_handle_t, unsigned long *,
+						     efi_char16_t **);
  		efi_status_t __noreturn (__efiapi *exit)(efi_handle_t,
  							 efi_status_t,
  							 unsigned long,
@@ -277,8 +281,8 @@ union efi_boot_services {
  		void *locate_handle_buffer;
  		efi_status_t (__efiapi *locate_protocol)(efi_guid_t *, void *,
  							 void **);
-		void *install_multiple_protocol_interfaces;
-		void *uninstall_multiple_protocol_interfaces;
+		efi_status_t (__efiapi *install_multiple_protocol_interfaces)(efi_handle_t *, ...);
+		efi_status_t (__efiapi *uninstall_multiple_protocol_interfaces)(efi_handle_t, ...);
  		void *calculate_crc32;
  		void *copy_mem;
  		void *set_mem;
author maintainer
#3 lore raw

PATCH v2 2/6 efi: stub: split off printk() routines

In reply to Ard Biesheuvel

We will be using the libstub static library also to build the EFI zboot decompressor, which is a separate binary and does not carry all the dependencies of efi-stub-helper.c, which is where efi_printk() currently lives.

So split it off into printk.c so we can incorporate it separately.

---
 drivers/firmware/efi/libstub/Makefile          |   2 +-
 drivers/firmware/efi/libstub/efi-stub-helper.c | 141 -----------------
 drivers/firmware/efi/libstub/printk.c          | 158 ++++++++++++++++++++
 3 files changed, 159 insertions(+), 142 deletions(-)
drivers/firmware/efi/libstub/Makefile +1 -1
index d0537573501e..475224be08e1 100644
@@ -55,7 +55,7 @@ KCOV_INSTRUMENT			:= n
 lib-y				:= efi-stub-helper.o gop.o secureboot.o tpm.o \
 				   file.o mem.o random.o randomalloc.o pci.o \
 				   skip_spaces.o lib-cmdline.o lib-ctype.o \
-				   alignedmem.o relocate.o vsprintf.o
+				   alignedmem.o relocate.o vsprintf.o printk.o
 
 # include the stub's generic dependencies from lib/ when building for ARM/arm64
 efi-deps-y := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c fdt_empty_tree.c fdt_sw.c
drivers/firmware/efi/libstub/efi-stub-helper.c +0 -141

153 lines not on this page · raw message

drivers/firmware/efi/libstub/printk.c new file +158 -0

158 lines not on this page · raw message

Signature · 1 line
2.35.1
author maintainer
#4 lore raw

PATCH v2 3/6 efi: stub: move efi_system_table global var into separate object

In reply to Ard Biesheuvel

To avoid pulling in the wrong object when using the libstub static library to build the decompressor, define efi_system_table in a separate compilation unit.

---
 drivers/firmware/efi/libstub/Makefile   | 3 ++-
 drivers/firmware/efi/libstub/efi-stub.c | 2 --
 drivers/firmware/efi/libstub/systable.c | 8 ++++++++
 3 files changed, 10 insertions(+), 3 deletions(-)
drivers/firmware/efi/libstub/Makefile +2 -1
index 475224be08e1..a8e26d5a1d14 100644
@@ -55,7 +55,8 @@ KCOV_INSTRUMENT			:= n
 lib-y				:= efi-stub-helper.o gop.o secureboot.o tpm.o \
 				   file.o mem.o random.o randomalloc.o pci.o \
 				   skip_spaces.o lib-cmdline.o lib-ctype.o \
-				   alignedmem.o relocate.o vsprintf.o printk.o
+				   alignedmem.o relocate.o vsprintf.o printk.o \
+				   systable.o
 
 # include the stub's generic dependencies from lib/ when building for ARM/arm64
 efi-deps-y := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c fdt_empty_tree.c fdt_sw.c
drivers/firmware/efi/libstub/efi-stub.c +0 -2
index f515394cce6e..ad179632f99f 100644
@@ -49,8 +49,6 @@
 static u64 virtmap_base = EFI_RT_VIRTUAL_BASE;
 static bool flat_va_mapping;
 
-const efi_system_table_t *efi_system_table;
-
 static struct screen_info *setup_graphics(void)
 {
 	efi_guid_t gop_proto = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
drivers/firmware/efi/libstub/systable.c new file +8 -0
new file mode 100644
index 000000000000..91d016b02f8c
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/efi.h>
+#include <asm/efi.h>
+
+#include "efistub.h"
+
+const efi_system_table_t *efi_system_table;
Signature · 1 line
2.35.1
author maintainer
#5 lore raw

PATCH v2 4/6 efi: stub: implement generic EFI zboot

In reply to Ard Biesheuvel

Implement a minimal EFI app that decompresses the real kernel image and launches it using the firmware's LoadImage and StartImage boot services. This removes the need for any arch-specific hacks.

Note that on systems that have UEFI secure boot policies enabled, LoadImage/StartImage require images to be signed, or their hashes known a priori, in order to be permitted to boot. We adopt the same approach as the EFI shim used by the distros, which is to override the security arch protocol implementations used internally by the firmware, permitting the inner, compressed image to be loaded and started after decompression. The outer PE/COFF image will be signed as usual in such cases, preserving the chain of trust.

BZIP2 has been omitted from the set of supported compression algorithms, given that its performance is mediocre both in speed and size, and it uses a disproportionate amount of memory. For optimal compression, use LZMA. For the fastest boot speed, use LZO.

---
 drivers/firmware/efi/Kconfig                |   9 +
 drivers/firmware/efi/libstub/Makefile       |   4 +
 drivers/firmware/efi/libstub/Makefile.zboot |  30 +++
 drivers/firmware/efi/libstub/zboot-header.S | 144 +++++++++++++++
 drivers/firmware/efi/libstub/zboot.c        | 191 ++++++++++++++++++++
 drivers/firmware/efi/libstub/zboot.lds      |  41 +++++
 include/linux/efi.h                         |   2 +
 7 files changed, 421 insertions(+)
drivers/firmware/efi/Kconfig +9 -0
index 6cb7384ad2ac..9ee0944f80bf 100644
@@ -105,6 +105,15 @@ config EFI_RUNTIME_WRAPPERS
 config EFI_GENERIC_STUB
 	bool
 
+config EFI_ZBOOT
+	bool "Enable the generic EFI decompressor"
+	depends on EFI_GENERIC_STUB
+	depends on !ARM && !X86
+	select HAVE_KERNEL_GZIP
+	select HAVE_KERNEL_LZ4
+	select HAVE_KERNEL_LZMA
+	select HAVE_KERNEL_LZO
+
 config EFI_ARMSTUB_DTB_LOADER
 	bool "Enable the DTB loader"
 	depends on EFI_GENERIC_STUB && !RISCV
drivers/firmware/efi/libstub/Makefile +4 -0
index a8e26d5a1d14..721f948950b8 100644
@@ -112,8 +112,12 @@ STUBCOPY_RELOC-$(CONFIG_ARM)	:= R_ARM_ABS
 # a verification pass to see if any absolute relocations exist in any of the
 # object files.
 #
+lib-$(CONFIG_EFI_ZBOOT)		+= zboot.o
 extra-y				:= $(lib-y)
 lib-y				:= $(patsubst %.o,%.stub.o,$(lib-y))
+lib-$(CONFIG_EFI_ZBOOT)		+= zboot-header.o
+AFLAGS_zboot-header.o		+= -DZIMAGE_EFI_PATH="\"$(realpath \
+				   $(objtree)/arch/$(ARCH)/boot/zImage.efi.elf)\""
 
 STUBCOPY_FLAGS-$(CONFIG_ARM64)	+= --prefix-alloc-sections=.init \
 				   --prefix-symbols=__efistub_
drivers/firmware/efi/libstub/Makefile.zboot new file +30 -0
new file mode 100644
index 000000000000..37d95d90db47
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# to be include'd by arch/$(ARCH)/boot/Makefile after setting
+# ZBOOT_PAYLOAD, ZBOOT_BFD_TARGET, ZBOOT_LD_FLAGS and ZBOOT_EXTRA_DEPS
+
+zimage-method-$(CONFIG_KERNEL_GZIP)	:= gzip
+zimage-method-$(CONFIG_KERNEL_LZ4)	:= lz4_with_size
+zimage-method-$(CONFIG_KERNEL_LZMA)	:= lzma_with_size
+zimage-method-$(CONFIG_KERNEL_LZO)	:= lzo_with_size
+
+$(obj)/zImage: $(ZBOOT_PAYLOAD) FORCE
+	$(call if_changed,$(zimage-method-y))
+
+OBJCOPYFLAGS_zImage.o := -I binary -O $(ZBOOT_BFD_TARGET) \
+			 --rename-section .data=.gzdata,load,alloc,readonly,contents
+$(obj)/zImage.o: $(obj)/zImage FORCE
+	$(call if_changed,objcopy)
+
+ZBOOT_DEPS := $(ZBOOT_EXTRA_DEPS) $(objtree)/drivers/firmware/efi/libstub/lib.a
+
+LDFLAGS_zImage.efi.elf := -T $(srctree)/drivers/firmware/efi/libstub/zboot.lds \
+			  $(ZBOOT_LD_FLAGS)
+$(obj)/zImage.efi.elf: $(obj)/zImage.o $(ZBOOT_DEPS) FORCE
+	$(call if_changed,ld)
+
+OBJCOPYFLAGS_zImage.efi := -O binary
+$(obj)/zImage.efi: $(obj)/zImage.efi.elf FORCE
+	$(call if_changed,objcopy)
+
+targets += zImage zImage.o zImage.efi.elf zImage.efi
drivers/firmware/efi/libstub/zboot-header.S new file +144 -0

144 lines not on this page · raw message

drivers/firmware/efi/libstub/zboot.c new file +191 -0

191 lines not on this page · raw message

drivers/firmware/efi/libstub/zboot.lds new file +41 -0

41 lines not on this page · raw message

include/linux/efi.h +2 -0
index 75f16e9bc0c0..07f17e2c5f69 100644
@@ -387,6 +387,8 @@ void efi_native_runtime_setup(void);
 #define EFI_RT_PROPERTIES_TABLE_GUID		EFI_GUID(0xeb66918a, 0x7eef, 0x402a,  0x84, 0x2e, 0x93, 0x1d, 0x21, 0xc3, 0x8a, 0xe9)
 #define EFI_DXE_SERVICES_TABLE_GUID		EFI_GUID(0x05ad34ba, 0x6f02, 0x4214,  0x95, 0x2e, 0x4d, 0xa0, 0x39, 0x8e, 0x2b, 0xb9)
 
+#define EFI_SECURITY_ARCH_PROTOCOL_GUID		EFI_GUID(0xA46423E3, 0x4617, 0x49f1,  0xB9, 0xFF, 0xD1, 0xBF, 0xA9, 0x11, 0x58, 0x39)
+#define EFI_SECURITY_ARCH2_PROTOCOL_GUID	EFI_GUID(0x94ab2f58, 0x1438, 0x4ef1,  0x91, 0x52, 0x18, 0x94, 0x1a, 0x3a, 0x0e, 0x68)
 #define EFI_IMAGE_SECURITY_DATABASE_GUID	EFI_GUID(0xd719b2cb, 0x3d3a, 0x4596,  0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f)
 #define EFI_SHIM_LOCK_GUID			EFI_GUID(0x605dab50, 0xe046, 0x4300,  0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23)
 
Signature · 1 line
2.35.1
author maintainer
#6 lore raw

PATCH v2 5/6 arm64: efi: enable generic EFI compressed boot

In reply to Ard Biesheuvel

Wire up the generic EFI zboot support for arm64.

---
 arch/arm64/Makefile      |  5 +++++
 arch/arm64/boot/Makefile | 12 ++++++++++++
 2 files changed, 17 insertions(+)
arch/arm64/Makefile +5 -0
index 6d9d4a58b898..ad4f849b91e8 100644
@@ -162,6 +162,11 @@ Image: vmlinux
 Image.%: Image
 	$(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
 
+ifneq ($(CONFIG_EFI_ZBOOT),)
+zImage.efi: Image
+	$(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
+endif
+
 install: KBUILD_IMAGE := $(boot)/Image
 install zinstall:
 	$(call cmd,install)
arch/arm64/boot/Makefile +12 -0
index a0e3dedd2883..08fcd39ae808 100644
@@ -38,3 +38,15 @@ $(obj)/Image.lzo: $(obj)/Image FORCE
 
 $(obj)/Image.zst: $(obj)/Image FORCE
 	$(call if_changed,zstd)
+
+ZBOOT_PAYLOAD		:= $(obj)/Image
+ZBOOT_BFD_TARGET	:= elf64-littleaarch64
+ZBOOT_LD_FLAGS		:= --defsym=__efistub_strnlen=__pi_strnlen \
+			   --defsym=__efistub_memmove=__pi_memmove \
+			   --defsym=__efistub_memcpy=__pi_memcpy \
+			   --defsym=__efistub_memset=__pi_memset
+
+ZBOOT_EXTRA_OBJS	:= memcpy.o memset.o strnlen.o
+ZBOOT_EXTRA_DEPS	:= $(addprefix $(objtree)/arch/arm64/lib/,$(ZBOOT_EXTRA_OBJS))
+
+include $(srctree)/drivers/firmware/efi/libstub/Makefile.zboot
Signature · 1 line
2.35.1
author maintainer
#7 lore raw

PATCH v2 6/6 riscv: efi: enable generic EFI compressed boot

In reply to Ard Biesheuvel

Wire up the generic EFI zboot support for RISC-V.

---
 arch/riscv/Makefile      |  5 +++++
 arch/riscv/boot/Makefile | 14 ++++++++++++++
 2 files changed, 19 insertions(+)
arch/riscv/Makefile +5 -0
index 81029d40a672..c30ea65ec877 100644
@@ -142,6 +142,11 @@ $(BOOT_TARGETS): vmlinux
 Image.%: Image
 	$(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
 
+ifneq ($(CONFIG_EFI_ZBOOT),)
+zImage.efi: Image
+	$(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
+endif
+
 install: KBUILD_IMAGE := $(boot)/Image
 zinstall: KBUILD_IMAGE := $(boot)/Image.gz
 install zinstall:
arch/riscv/boot/Makefile +14 -0
index becd0621071c..60cd319685ea 100644
@@ -58,3 +58,17 @@ $(obj)/Image.lzo: $(obj)/Image FORCE
 
 $(obj)/loader.bin: $(obj)/loader FORCE
 	$(call if_changed,objcopy)
+
+ZBOOT_PAYLOAD		:= $(obj)/Image
+ZBOOT_BFD_TARGET	:= elf$(BITS)-littleriscv
+ZBOOT_LD_FLAGS		:= --defsym=__efistub_strnlen=strnlen \
+			   --defsym=__efistub_memmove=memmove \
+			   --defsym=__efistub_memcpy=memcpy \
+			   --defsym=__efistub_memset=memset
+
+ZBOOT_EXTRA_OBJS	:= memcpy.o memset.o memmove.o
+ZBOOT_EXTRA_DEPS	:= $(objtree)/lib/string.o \
+			   $(objtree)/lib/ctype.o \
+			   $(addprefix $(objtree)/arch/riscv/lib/,$(ZBOOT_EXTRA_OBJS))
+
+include $(srctree)/drivers/firmware/efi/libstub/Makefile.zboot
Signature · 1 line
2.35.1
maintainer Acked-by on v2 6/6
#8 lore raw

Re: [PATCH v2 6/6] riscv: efi: enable generic EFI compressed boot

Quoted 36 lines · On Tue, 09 Aug 2022 01:09:44 PDT (-0700), [email protected] wrote:

Wire up the generic EFI zboot support for RISC-V.

---
 arch/riscv/Makefile      |  5 +++++
 arch/riscv/boot/Makefile | 14 ++++++++++++++
 2 files changed, 19 insertions(+)
arch/riscv/Makefile +5 -0
index 81029d40a672..c30ea65ec877 100644
@@ -142,6 +142,11 @@ $(BOOT_TARGETS): vmlinux
 Image.%: Image
 	$(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
 
+ifneq ($(CONFIG_EFI_ZBOOT),)
+zImage.efi: Image
+	$(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
+endif
+
 install: KBUILD_IMAGE := $(boot)/Image
 zinstall: KBUILD_IMAGE := $(boot)/Image.gz
 install zinstall:
arch/riscv/boot/Makefile +4 -0
index becd0621071c..60cd319685ea 100644
@@ -58,3 +58,17 @@ $(obj)/Image.lzo: $(obj)/Image FORCE
 
 $(obj)/loader.bin: $(obj)/loader FORCE
 	$(call if_changed,objcopy)
+
+ZBOOT_PAYLOAD		:= $(obj)/Image
+ZBOOT_BFD_TARGET	:= elf$(BITS)-littleriscv
+ZBOOT_LD_FLAGS		:= --defsym=__efistub_strnlen=strnlen \

arch/riscv/boot/Makefile $(obj)/Image.lzo: $(obj)/Image FORCE · line 74

Quoted 10 lines
arch/riscv/boot/Makefile +10 -0
+			   --defsym=__efistub_memmove=memmove \
+			   --defsym=__efistub_memcpy=memcpy \
+			   --defsym=__efistub_memset=memset
+
+ZBOOT_EXTRA_OBJS	:= memcpy.o memset.o memmove.o
+ZBOOT_EXTRA_DEPS	:= $(objtree)/lib/string.o \
+			   $(objtree)/lib/ctype.o \
+			   $(addprefix $(objtree)/arch/riscv/lib/,$(ZBOOT_EXTRA_OBJS))
+
+include $(srctree)/drivers/firmware/efi/libstub/Makefile.zboot
#9 lore raw
In reply to Ard Biesheuvel
Quoted 15 lines · Ard Biesheuvel

Relatively modern architectures such as arm64 or RISC-V don't implement a self-decompressing kernel, and leave it up to the bootloader to decompress the compressed image before executing it. For bare metal boot, this policy makes sense, as a self-decompressing image essentially duplicates a lot of fiddly preparation work to create a 1:1 mapping and set up the C runtime, and to discover or infer where DRAM lives from device trees or other firmware tables.

For EFI boot, the situation is a bit different: the EFI entrypoint is called with a 1:1 cached mapping covering all of DRAM already active, and with a stack, a heap, a memory map and boot services to load and start images. This means it is rather trivial to implement a self-decompressing wrapper for EFI boot in a generic manner, and reuse it across architectures that implement EFI boot.

Quoted 6 lines

The only slight downside is that when UEFI secure boot is enabled, the generic LoadImage/StartImage only allow signed images to be loaded and started, and we prefer to avoid the need to sign both the inner and outer PE/COFF images. This series adopts the EFI shim approach, i.e., to override an internal UEFI/PI protocol that is used by the image loader, to allow the inner image to be booted after decompression. This has been

We should avoid requiring anything that is not in the UEFI specification. If you have any additional requirements, please, create a change request for the UEFI specification.

Overriding the services of the system table is dangerous and should be avoided.

There is no need for two UEFI binaries one inside the other and we should avoid such overengineering.

Today we append an uncompressed kernel to the EFI stub. The stub relocates it, sets up the memory map and calls it entry point.

Just add decompressor code to the EFI stub and instead of appending an uncompressed kernel append a compressed one. Then sign a binary consisting of the EFI stub and the compressed kernel.

This way you don't need any change to UEFI firmware at all and you don't need to override UEFI services.

Another reasonable approach would be to zip the signed UEFI binary (EFI stub with uncompressed kernel) and let the UEFI firmware unzip it and check the signature of the decompressed UEFI binary. This would not require any patch in Linux at all and would be simple to implement in U-Boot.

Best regards

Heinrich

Quoted 55 lines

tested to work with Tianocore based EFI implementations on arm64, but u-boot will need some interoperability tweaks as well, ideally just a protocol that exposes a LoadImage/StartImage combo that the decompresor can use directly to circumvent the signature check. (Note that EFI apps have full control over the CPU, page tables, etc. so having code that circumvents authentication checks is not as crazy as it sounds, given that the app can do anything it pleases already.)

The code is wired up for arm64 and RISC-V. The latter was build tested only.

Cc "James E.J. Bottomley" <[email protected]>
Cc Matthew Garrett <[email protected]>
Cc Peter Jones <[email protected]>
Cc Ilias Apalodimas <[email protected]>
Cc Heinrich Schuchardt <[email protected]>
Cc AKASHI Takahiro <[email protected]>
Cc Palmer Dabbelt <[email protected]>
Cc Atish Patra <[email protected]>
Cc Arnd Bergmann <[email protected]>
Cc Huacai Chen <[email protected]>
Cc Lennart Poettering <[email protected]>

Ard Biesheuvel (6):
efi: stub: add some missing boot service prototypes
efi: stub: split off printk() routines
efi: stub: move efi_system_table global var into separate object
efi: stub: implement generic EFI zboot
arm64: efi: enable generic EFI compressed boot
riscv: efi: enable generic EFI compressed boot

  arch/arm64/Makefile                            |   5 +
  arch/arm64/boot/Makefile                       |  12 ++
  arch/riscv/Makefile                            |   5 +
  arch/riscv/boot/Makefile                       |  14 ++
  drivers/firmware/efi/Kconfig                   |   9 +
  drivers/firmware/efi/libstub/Makefile          |   7 +-
  drivers/firmware/efi/libstub/Makefile.zboot    |  30 +++
  drivers/firmware/efi/libstub/efi-stub-helper.c | 141 ---------------
  drivers/firmware/efi/libstub/efi-stub.c        |   2 -
  drivers/firmware/efi/libstub/efistub.h         |  12 +-
  drivers/firmware/efi/libstub/printk.c          | 158 ++++++++++++++++
  drivers/firmware/efi/libstub/systable.c        |   8 +
  drivers/firmware/efi/libstub/zboot-header.S    | 144 +++++++++++++++
  drivers/firmware/efi/libstub/zboot.c           | 191 ++++++++++++++++++++
  drivers/firmware/efi/libstub/zboot.lds         |  41 +++++
  include/linux/efi.h                            |   2 +
  16 files changed, 633 insertions(+), 148 deletions(-)

create mode 100644 drivers/firmware/efi/libstub/Makefile.zboot create mode 100644 drivers/firmware/efi/libstub/printk.c create mode 100644 drivers/firmware/efi/libstub/systable.c create mode 100644 drivers/firmware/efi/libstub/zboot-header.S create mode 100644 drivers/firmware/efi/libstub/zboot.c create mode 100644 drivers/firmware/efi/libstub/zboot.lds

author maintainer
#10 lore raw
Quoted 24 lines · Heinrich Schuchardt
Quoted 15 lines · Ard Biesheuvel

Relatively modern architectures such as arm64 or RISC-V don't implement a self-decompressing kernel, and leave it up to the bootloader to decompress the compressed image before executing it. For bare metal boot, this policy makes sense, as a self-decompressing image essentially duplicates a lot of fiddly preparation work to create a 1:1 mapping and set up the C runtime, and to discover or infer where DRAM lives from device trees or other firmware tables.

For EFI boot, the situation is a bit different: the EFI entrypoint is called with a 1:1 cached mapping covering all of DRAM already active, and with a stack, a heap, a memory map and boot services to load and start images. This means it is rather trivial to implement a self-decompressing wrapper for EFI boot in a generic manner, and reuse it across architectures that implement EFI boot.

Quoted 6 lines

The only slight downside is that when UEFI secure boot is enabled, the generic LoadImage/StartImage only allow signed images to be loaded and started, and we prefer to avoid the need to sign both the inner and outer PE/COFF images. This series adopts the EFI shim approach, i.e., to override an internal UEFI/PI protocol that is used by the image loader, to allow the inner image to be booted after decompression. This has been

Quoted 4 lines

We should avoid requiring anything that is not in the UEFI specification. If you have any additional requirements, please, create a change request for the UEFI specification.

As I have explained numerous times before, the EFI spec was intended to be extensible (hence the 'E'). The ACPI, SMBIOS and TCG specs all augment the EFI specification by defining protocols, GUIDs and other things that are only relevant in a EFI context, but none of those are covered by the EFI spec itself.

Quoted 3 lines

Overriding the services of the system table is dangerous and should be avoided.

Agreed. But this is not what is happening here.

Quoted 3 lines

There is no need for two UEFI binaries one inside the other and we should avoid such overengineering.

I disagree. Using an EFI app to encapsulate another one is the only generic way to go about this, as far as I can tell.

Quoted 3 lines

Today we append an uncompressed kernel to the EFI stub. The stub relocates it, sets up the memory map and calls it entry point.

Not exactly. On arm64 as well as RISC-V, the EFI stub and the kernel proper are essentially the same executable image.

Quoted 4 lines

Just add decompressor code to the EFI stub and instead of appending an uncompressed kernel append a compressed one. Then sign a binary consisting of the EFI stub and the compressed kernel.

Yes, this would be a cleaner approach, although it would require more re-engineering of the EFI stub, in particular, it would require cloning more code, and adding additional build and link steps.

Quoted 9 lines

This way you don't need any change to UEFI firmware at all and you don't need to override UEFI services.

Another reasonable approach would be to zip the signed UEFI binary (EFI stub with uncompressed kernel) and let the UEFI firmware unzip it and check the signature of the decompressed UEFI binary. This would not require any patch in Linux at all and would be simple to implement in U-Boot.

This is how it works today. One problem with this is that the image needs to be decompressed in order to sign it, or verify its signature. In general, having compression at the outside like this is fiddly because it is no longer a PE/COFF image, and the EFI spec only reasons about PE/COFF images as executable images. So we'd need to change the UEFI spec or the PE/COFF spec.

#11 lore raw
Quoted 41 lines · Ard Biesheuvel
Quoted 24 lines · Heinrich Schuchardt
Quoted 15 lines · Ard Biesheuvel

Relatively modern architectures such as arm64 or RISC-V don't implement a self-decompressing kernel, and leave it up to the bootloader to decompress the compressed image before executing it. For bare metal boot, this policy makes sense, as a self-decompressing image essentially duplicates a lot of fiddly preparation work to create a 1:1 mapping and set up the C runtime, and to discover or infer where DRAM lives from device trees or other firmware tables.

For EFI boot, the situation is a bit different: the EFI entrypoint is called with a 1:1 cached mapping covering all of DRAM already active, and with a stack, a heap, a memory map and boot services to load and start images. This means it is rather trivial to implement a self-decompressing wrapper for EFI boot in a generic manner, and reuse it across architectures that implement EFI boot.

Quoted 6 lines

The only slight downside is that when UEFI secure boot is enabled, the generic LoadImage/StartImage only allow signed images to be loaded and started, and we prefer to avoid the need to sign both the inner and outer PE/COFF images. This series adopts the EFI shim approach, i.e., to override an internal UEFI/PI protocol that is used by the image loader, to allow the inner image to be booted after decompression. This has been

Quoted 4 lines

We should avoid requiring anything that is not in the UEFI specification. If you have any additional requirements, please, create a change request for the UEFI specification.

As I have explained numerous times before, the EFI spec was intended to be extensible (hence the 'E'). The ACPI, SMBIOS and TCG specs all augment the EFI specification by defining protocols, GUIDs and other things that are only relevant in a EFI context, but none of those are covered by the EFI spec itself.

Quoted 3 lines

Overriding the services of the system table is dangerous and should be avoided.

Quoted 8 lines

Agreed. But this is not what is happening here.

Quoted 3 lines

There is no need for two UEFI binaries one inside the other and we should avoid such overengineering.

I disagree. Using an EFI app to encapsulate another one is the only generic way to go about this, as far as I can tell.

Please, elaborate why the inner compressed binary needs to be UEFI to boot into Linux while currently we don't need a an uncompressed inner UEFI binary.

Quoted 7 lines
Quoted 4 lines

Today we append an uncompressed kernel to the EFI stub. The stub relocates it, sets up the memory map and calls it entry point.

Not exactly. On arm64 as well as RISC-V, the EFI stub and the kernel proper are essentially the same executable image.

Currently on ARM and RISC-V you have a file with two entry points:

* EFI stub
* legacy entry point

The EFI stub calls the legacy entry point. In the EFI case some part of the EFI stub lives on at runtime. The same pointers passed to the legacy entry point could also be passed to a decompressed legacy kernel.

The EFI stub and the kernel should be completely separate binaries. Then you just need the cp command to join them.

Quoted 9 lines
Quoted 5 lines

Just add decompressor code to the EFI stub and instead of appending an uncompressed kernel append a compressed one. Then sign a binary consisting of the EFI stub and the compressed kernel.

Yes, this would be a cleaner approach, although it would require more re-engineering of the EFI stub, in particular, it would require cloning more code, and adding additional build and link steps.

If this is the cleanest approach, we should go for it.

Best regards

Heinrich

Quoted 17 lines
Quoted 10 lines

This way you don't need any change to UEFI firmware at all and you don't need to override UEFI services.

Another reasonable approach would be to zip the signed UEFI binary (EFI stub with uncompressed kernel) and let the UEFI firmware unzip it and check the signature of the decompressed UEFI binary. This would not require any patch in Linux at all and would be simple to implement in U-Boot.

This is how it works today. One problem with this is that the image needs to be decompressed in order to sign it, or verify its signature. In general, having compression at the outside like this is fiddly because it is no longer a PE/COFF image, and the EFI spec only reasons about PE/COFF images as executable images. So we'd need to change the UEFI spec or the PE/COFF spec.

author maintainer
#12 lore raw
Quoted 49 lines · Heinrich Schuchardt
Quoted 47 lines · Ard Biesheuvel
Quoted 24 lines · Heinrich Schuchardt
Quoted 15 lines · Ard Biesheuvel

Relatively modern architectures such as arm64 or RISC-V don't implement a self-decompressing kernel, and leave it up to the bootloader to decompress the compressed image before executing it. For bare metal boot, this policy makes sense, as a self-decompressing image essentially duplicates a lot of fiddly preparation work to create a 1:1 mapping and set up the C runtime, and to discover or infer where DRAM lives from device trees or other firmware tables.

For EFI boot, the situation is a bit different: the EFI entrypoint is called with a 1:1 cached mapping covering all of DRAM already active, and with a stack, a heap, a memory map and boot services to load and start images. This means it is rather trivial to implement a self-decompressing wrapper for EFI boot in a generic manner, and reuse it across architectures that implement EFI boot.

Quoted 6 lines

The only slight downside is that when UEFI secure boot is enabled, the generic LoadImage/StartImage only allow signed images to be loaded and started, and we prefer to avoid the need to sign both the inner and outer PE/COFF images. This series adopts the EFI shim approach, i.e., to override an internal UEFI/PI protocol that is used by the image loader, to allow the inner image to be booted after decompression. This has been

Quoted 4 lines

We should avoid requiring anything that is not in the UEFI specification. If you have any additional requirements, please, create a change request for the UEFI specification.

As I have explained numerous times before, the EFI spec was intended to be extensible (hence the 'E'). The ACPI, SMBIOS and TCG specs all augment the EFI specification by defining protocols, GUIDs and other things that are only relevant in a EFI context, but none of those are covered by the EFI spec itself.

Quoted 3 lines

Overriding the services of the system table is dangerous and should be avoided.

Agreed. But this is not what is happening here.

Quoted 3 lines

There is no need for two UEFI binaries one inside the other and we should avoid such overengineering.

Quoted 7 lines
Quoted 2 lines

I disagree. Using an EFI app to encapsulate another one is the only generic way to go about this, as far as I can tell.

Please, elaborate why the inner compressed binary needs to be UEFI to boot into Linux while currently we don't need a an uncompressed inner UEFI binary.

If the inner binary is not EFI / PE/COFF, there is no generic way to boot it. Every architecture has its own rules of how this needs to be implemented.

Quoted 8 lines
Quoted 7 lines
Quoted 4 lines

Today we append an uncompressed kernel to the EFI stub. The stub relocates it, sets up the memory map and calls it entry point.

Not exactly. On arm64 as well as RISC-V, the EFI stub and the kernel proper are essentially the same executable image.

Quoted 9 lines

Currently on ARM and RISC-V you have a file with two entry points:

* EFI stub
* legacy entry point

The EFI stub calls the legacy entry point. In the EFI case some part of the EFI stub lives on at runtime. The same pointers passed to the legacy entry point could also be passed to a decompressed legacy kernel.

Nit: no part of the EFI stub lives on at runtime, but that is beside the point.

You are right that the EFI stub could be logically disjoint from the payload, and so it could theoretically decompress its payload before starting it.

Quoted 3 lines

The EFI stub and the kernel should be completely separate binaries. Then you just need the cp command to join them.

It is not that simple, unfortunately. The EFI stub on arm64 and RISC-V refers to many ELF symbols directly, and so splitting those images requires some intrusive build system changes.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

Just add decompressor code to the EFI stub and instead of appending an uncompressed kernel append a compressed one. Then sign a binary consisting of the EFI stub and the compressed kernel.

Quoted 6 lines
Quoted 3 lines

Yes, this would be a cleaner approach, although it would require more re-engineering of the EFI stub, in particular, it would require cloning more code, and adding additional build and link steps.

If this is the cleanest approach, we should go for it.

That is a fair point. Unfortunately, it will no longer be generic, and each architecture will have to implement this individually.

#13 lore raw
In reply to Ard Biesheuvel
Quoted 5 lines · Ard Biesheuvel

The only slight downside is that when UEFI secure boot is enabled, the generic LoadImage/StartImage only allow signed images to be loaded and started, and we prefer to avoid the need to sign both the inner and outer PE/COFF images. This series adopts the EFI shim approach, i.e., to override an internal UEFI/PI protocol that is used by the image loader,

Quoted 8 lines

to allow the inner image to be booted after decompression. This has been tested to work with Tianocore based EFI implementations on arm64, but u-boot will need some interoperability tweaks as well, ideally just a protocol that exposes a LoadImage/StartImage combo that the decompresor can use directly to circumvent the signature check. (Note that EFI apps have full control over the CPU, page tables, etc. so having code that circumvents authentication checks is not as crazy as it sounds, given that the app can do anything it pleases already.)

I think it's worth mentioning that Shim doesn't do this by default because it's not guaranteed that something implementing the UEFI DXE layer also implements the PI layer. I don't have an objection to it being an expectation on any Linux-supporting platform that the implementation works this way, but it should maybe be made clear that this could unexpectedly fail on generic platforms.