efi: implement generic compressed boot support
Subsystems (4)
Ard Biesheuvel
maintainerv2 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.
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
Ard Biesheuvel
maintainerPATCH 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
@@ -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
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_interfacesboot service pointers so we can call them from the EFI zboot code.Signed-off-by Ard Biesheuvel <[email protected]>
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@@ -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;
Ard Biesheuvel
maintainerPATCH v2 2/6 efi: stub: split off printk() routines
In reply to Ard BiesheuvelWe 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
@@ -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
Ard Biesheuvel
maintainerPATCH v2 3/6 efi: stub: move efi_system_table global var into separate object
In reply to Ard BiesheuvelTo 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
@@ -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
@@ -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
@@ -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
Ard Biesheuvel
maintainerPATCH v2 4/6 efi: stub: implement generic EFI zboot
In reply to Ard BiesheuvelImplement 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
@@ -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
@@ -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
@@ -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
@@ -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
Ard Biesheuvel
maintainerPATCH v2 5/6 arm64: efi: enable generic EFI compressed boot
In reply to Ard BiesheuvelWire 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
@@ -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
@@ -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
Ard Biesheuvel
maintainerPATCH v2 6/6 riscv: efi: enable generic EFI compressed boot
In reply to Ard BiesheuvelWire 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
@@ -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
@@ -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
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.
Signed-off-by Ard Biesheuvel <[email protected]>--- arch/riscv/Makefile | 5 +++++ arch/riscv/boot/Makefile | 14 ++++++++++++++ 2 files changed, 19 insertions(+)arch/riscv/Makefile +5
-0@@ -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@@ -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
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 offprintk()routines
efi: stub: moveefi_system_tableglobal var into separate object
efi: stub: implement generic EFI zboot
arm64: efi: enable generic EFI compressed boot
riscv: efi: enable generic EFI compressed bootarch/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.zbootcreate mode 100644drivers/firmware/efi/libstub/printk.ccreate mode 100644drivers/firmware/efi/libstub/systable.ccreate mode 100644drivers/firmware/efi/libstub/zboot-header.Screate mode 100644drivers/firmware/efi/libstub/zboot.ccreate mode 100644drivers/firmware/efi/libstub/zboot.lds
Ard Biesheuvel
maintainerQuoted 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.
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.
Ard Biesheuvel
maintainerQuoted 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 pointThe 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.
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.