diff mbox series

[v3,4/6] efi/libstub: implement generic EFI zboot

Message ID 20220817110345.1771267-5-ardb@kernel.org
State New
Headers show
Series efi: implement generic compressed boot support | expand

Commit Message

Ard Biesheuvel Aug. 17, 2022, 11:03 a.m. UTC
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.

There are various possible strategies to work around this requirement,
but they all rely either on overriding internal PI/DXE protocols (which
are not part of the EFI spec) or omitting the firmware provided
LoadImage() and StartImage() boot services, which is also undesirable,
given that they encapsulate platform specific policies related to secure
boot and measured boot, but also related to memory permissions (whether
or not and which types of heap allocations have both write and execute
permissions.)

The only generic and truly portable way around this is to simply sign
both the inner and the outer image with the same key/cert pair, so this
is what is implemented here.

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.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 drivers/firmware/efi/Kconfig                |  31 ++++-
 drivers/firmware/efi/libstub/Makefile       |   8 +-
 drivers/firmware/efi/libstub/Makefile.zboot |  69 ++++++++++
 drivers/firmware/efi/libstub/zboot-header.S | 139 ++++++++++++++++++++
 drivers/firmware/efi/libstub/zboot.c        | 101 ++++++++++++++
 drivers/firmware/efi/libstub/zboot.lds      |  39 ++++++
 6 files changed, 382 insertions(+), 5 deletions(-)

Comments

Heinrich Schuchardt Aug. 18, 2022, 4:42 p.m. UTC | #1
On 8/17/22 13:03, Ard Biesheuvel wrote:
> 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.
> 
> There are various possible strategies to work around this requirement,
> but they all rely either on overriding internal PI/DXE protocols (which
> are not part of the EFI spec) or omitting the firmware provided
> LoadImage() and StartImage() boot services, which is also undesirable,
> given that they encapsulate platform specific policies related to secure
> boot and measured boot, but also related to memory permissions (whether
> or not and which types of heap allocations have both write and execute
> permissions.)
> 
> The only generic and truly portable way around this is to simply sign
> both the inner and the outer image with the same key/cert pair, so this
> is what is implemented here.
> 
> 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.
> 
> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> ---
>   drivers/firmware/efi/Kconfig                |  31 ++++-
>   drivers/firmware/efi/libstub/Makefile       |   8 +-
>   drivers/firmware/efi/libstub/Makefile.zboot |  69 ++++++++++
>   drivers/firmware/efi/libstub/zboot-header.S | 139 ++++++++++++++++++++
>   drivers/firmware/efi/libstub/zboot.c        | 101 ++++++++++++++
>   drivers/firmware/efi/libstub/zboot.lds      |  39 ++++++
>   6 files changed, 382 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
> index 6cb7384ad2ac..0c7beb8e3633 100644
> --- a/drivers/firmware/efi/Kconfig
> +++ b/drivers/firmware/efi/Kconfig
> @@ -105,9 +105,36 @@ config EFI_RUNTIME_WRAPPERS
>   config EFI_GENERIC_STUB
>   	bool
>   
> +config EFI_ZBOOT
> +	bool "Enable the generic EFI decompressor"
> +	depends on EFI_GENERIC_STUB && !ARM
> +	select HAVE_KERNEL_GZIP
> +	select HAVE_KERNEL_LZ4
> +	select HAVE_KERNEL_LZMA
> +	select HAVE_KERNEL_LZO
> +
> +config EFI_ZBOOT_SIGNED
> +	bool "Sign the EFI decompressor for UEFI secure boot"
> +	depends on EFI_ZBOOT
> +	help
> +	  Use the 'sbsign' command line tool (which must exist on the host
> +	  path) to sign both the EFI decompressor PE/COFF image, as well as the
> +	  encapsulated PE/COFF image, which is subsequently compressed and
> +	  wrapped by the former image.
> +
> +config EFI_ZBOOT_SIGNING_CERT
> +	string "Certificate to use for signing the compressed EFI boot image"
> +	depends on EFI_ZBOOT_SIGNED
> +	default ""
> +
> +config EFI_ZBOOT_SIGNING_KEY
> +	string "Private key to use for signing the compressed EFI boot image"
> +	depends on EFI_ZBOOT_SIGNED
> +	default ""
> +
>   config EFI_ARMSTUB_DTB_LOADER
>   	bool "Enable the DTB loader"
> -	depends on EFI_GENERIC_STUB && !RISCV
> +	depends on EFI_GENERIC_STUB && !RISCV && !EFI_ZBOOT
>   	default y
>   	help
>   	  Select this config option to add support for the dtb= command
> @@ -124,7 +151,7 @@ config EFI_GENERIC_STUB_INITRD_CMDLINE_LOADER
>   	bool "Enable the command line initrd loader" if !X86
>   	depends on EFI_STUB && (EFI_GENERIC_STUB || X86)
>   	default y if X86
> -	depends on !RISCV
> +	depends on !RISCV && !EFI_ZBOOT
>   	help
>   	  Select this config option to add support for the initrd= command
>   	  line parameter, allowing an initrd that resides on the same volume
> diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile
> index 1406dc78edaa..a3d3d38d5afd 100644
> --- a/drivers/firmware/efi/libstub/Makefile
> +++ b/drivers/firmware/efi/libstub/Makefile
> @@ -73,6 +73,11 @@ lib-$(CONFIG_X86)		+= x86-stub.o
>   lib-$(CONFIG_RISCV)		+= riscv-stub.o
>   CFLAGS_arm32-stub.o		:= -DTEXT_OFFSET=$(TEXT_OFFSET)
>   
> +lib-$(CONFIG_EFI_ZBOOT)		+= zboot.o
> +
> +extra-y				:= $(lib-y)
> +lib-y				:= $(patsubst %.o,%.stub.o,$(lib-y))
> +
>   # Even when -mbranch-protection=none is set, Clang will generate a
>   # .note.gnu.property for code-less object files (like lib/ctype.c),
>   # so work around this by explicitly removing the unwanted section.
> @@ -112,9 +117,6 @@ STUBCOPY_RELOC-$(CONFIG_ARM)	:= R_ARM_ABS
>   # a verification pass to see if any absolute relocations exist in any of the
>   # object files.
>   #
> -extra-y				:= $(lib-y)
> -lib-y				:= $(patsubst %.o,%.stub.o,$(lib-y))
> -
>   STUBCOPY_FLAGS-$(CONFIG_ARM64)	+= --prefix-alloc-sections=.init \
>   				   --prefix-symbols=__efistub_
>   STUBCOPY_RELOC-$(CONFIG_ARM64)	:= R_AARCH64_ABS
> diff --git a/drivers/firmware/efi/libstub/Makefile.zboot b/drivers/firmware/efi/libstub/Makefile.zboot
> new file mode 100644
> index 000000000000..38dee29103ae
> --- /dev/null
> +++ b/drivers/firmware/efi/libstub/Makefile.zboot
> @@ -0,0 +1,69 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +# to be include'd by arch/$(ARCH)/boot/Makefile after setting
> +# EFI_ZBOOT_PAYLOAD, EFI_ZBOOT_BFD_TARGET and EFI_ZBOOT_MACH_TYPE
> +
> +comp-type-$(CONFIG_KERNEL_GZIP)		:= gzip
> +comp-type-$(CONFIG_KERNEL_LZ4)		:= lz4
> +comp-type-$(CONFIG_KERNEL_LZMA)		:= lzma
> +comp-type-$(CONFIG_KERNEL_LZO)		:= lzo
> +
> +# in GZIP, the appended le32 carrying the uncompressed size is part of the
> +# format, but in other cases, we just append it at the end for convenience,
> +# causing the original tools to complain when checking image integrity.
> +# So disregard it when calculating the payload size in the zimage header.
> +zimage-method-y				:= $(comp-type-y)_with_size
> +zimage-size-len-y			:= 4
> +
> +zimage-method-$(CONFIG_KERNEL_GZIP)	:= gzip
> +zimage-size-len-$(CONFIG_KERNEL_GZIP)	:= 0
> +
> +quiet_cmd_sbsign = SBSIGN  $@
> +      cmd_sbsign = sbsign --out $@ $< \
> +		   --key $(CONFIG_EFI_ZBOOT_SIGNING_KEY) \
> +		   --cert $(CONFIG_EFI_ZBOOT_SIGNING_CERT) \
> +		   2>/dev/null
> +
> +$(obj)/Image.signed: $(EFI_ZBOOT_PAYLOAD) FORCE
> +	$(call if_changed,sbsign)
> +
> +ZBOOT_PAYLOAD-y				 := $(EFI_ZBOOT_PAYLOAD)
> +ZBOOT_PAYLOAD-$(CONFIG_EFI_ZBOOT_SIGNED) := $(obj)/Image.signed
> +
> +$(obj)/zImage: $(ZBOOT_PAYLOAD-y) FORCE
> +	$(call if_changed,$(zimage-method-y))
> +
> +OBJCOPYFLAGS_zImage.o := -I binary -O $(EFI_ZBOOT_BFD_TARGET) \
> +			 --rename-section .data=.gzdata,load,alloc,readonly,contents
> +$(obj)/zImage.o: $(obj)/zImage FORCE
> +	$(call if_changed,objcopy)
> +
> +AFLAGS_zboot-header.o += -DMACHINE_TYPE=IMAGE_FILE_MACHINE_$(EFI_ZBOOT_MACH_TYPE) \
> +			 -DZIMG_EFI_PATH="\"$(realpath $(obj)/zImage.efi.elf)\"" \
> +			 -DZIMG_SIZE_LEN=$(zimage-size-len-y) \
> +			 -DCOMP_TYPE="\"$(comp-type-y)\""
> +
> +$(obj)/zboot-header.o: $(srctree)/drivers/firmware/efi/libstub/zboot-header.S FORCE
> +	$(call if_changed_rule,as_o_S)
> +
> +ZBOOT_DEPS := $(obj)/zboot-header.o $(objtree)/drivers/firmware/efi/libstub/lib.a
> +
> +LDFLAGS_zImage.efi.elf := -T $(srctree)/drivers/firmware/efi/libstub/zboot.lds
> +$(obj)/zImage.efi.elf: $(obj)/zImage.o $(ZBOOT_DEPS) FORCE
> +	$(call if_changed,ld)
> +
> +ZIMAGE_EFI-y				:= zImage.efi
> +ZIMAGE_EFI-$(CONFIG_EFI_ZBOOT_SIGNED)	:= zImage.efi.unsigned
> +
> +OBJCOPYFLAGS_$(ZIMAGE_EFI-y) := -O binary
> +$(obj)/$(ZIMAGE_EFI-y): $(obj)/zImage.efi.elf FORCE
> +	$(call if_changed,objcopy)
> +
> +targets += zboot-header.o zImage zImage.o zImage.efi.elf zImage.efi
> +
> +ifneq ($(CONFIG_EFI_ZBOOT_SIGNED),)
> +$(obj)/zImage.efi: $(obj)/zImage.efi.unsigned FORCE
> +	$(call if_changed,sbsign)
> +
> +targets += Image.signed zImage.efi.unsigned
> +endif
> diff --git a/drivers/firmware/efi/libstub/zboot-header.S b/drivers/firmware/efi/libstub/zboot-header.S
> new file mode 100644
> index 000000000000..ee6a3cd69773
> --- /dev/null
> +++ b/drivers/firmware/efi/libstub/zboot-header.S
> @@ -0,0 +1,139 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +#include <linux/pe.h>
> +
> +#ifdef CONFIG_64BIT
> +	.set		.Lextra_characteristics, 0x0
> +	.set		.Lpe_opt_magic, PE_OPT_MAGIC_PE32PLUS
> +#else
> +	.set		.Lextra_characteristics, IMAGE_FILE_32BIT_MACHINE
> +	.set		.Lpe_opt_magic, PE_OPT_MAGIC_PE32
> +#endif
> +
> +	.section	".head", "a"
> +	.globl		__efistub_efi_zboot_header
> +__efistub_efi_zboot_header:
> +.Ldoshdr:
> +	.long		MZ_MAGIC
> +	.ascii		"zimg"					// image type
> +	.long		__efistub__gzdata_start - .Ldoshdr	// payload offset
> +	.long		__efistub__gzdata_size - ZIMG_SIZE_LEN	// payload size
> +	.long		0, 0					// reserved
> +	.asciz		COMP_TYPE				// compression type
> +	.org		.Ldoshdr + 0x3c
> +	.long		.Lpehdr - .Ldoshdr			// PE header offset
> +
> +.Lpehdr:
> +	.long		PE_MAGIC
> +	.short		MACHINE_TYPE
> +	.short		.Lsection_count
> +	.long		0
> +	.long		0
> +	.long		0
> +	.short		.Lsection_table - .Loptional_header
> +	.short		IMAGE_FILE_DEBUG_STRIPPED | \
> +			IMAGE_FILE_EXECUTABLE_IMAGE | \
> +			IMAGE_FILE_LINE_NUMS_STRIPPED |\
> +			.Lextra_characteristics
> +
> +.Loptional_header:
> +	.short		.Lpe_opt_magic
> +	.byte		0, 0
> +	.long		_etext - .Lefi_header_end
> +	.long		__data_size
> +	.long		0
> +	.long		__efistub_efi_zboot_entry - .Ldoshdr
> +	.long		.Lefi_header_end - .Ldoshdr
> +
> +#ifdef CONFIG_64BIT
> +	.quad		0
> +#else
> +	.long		_etext - .Ldoshdr, 0x0
> +#endif
> +	.long		4096
> +	.long		512
> +	.short		0, 0
> +	.short		LINUX_EFISTUB_MAJOR_VERSION	// MajorImageVersion
> +	.short		LINUX_EFISTUB_MINOR_VERSION	// MinorImageVersion
> +	.short		0, 0
> +	.long		0
> +	.long		_end - .Ldoshdr
> +
> +	.long		.Lefi_header_end - .Ldoshdr
> +	.long		0
> +	.short		IMAGE_SUBSYSTEM_EFI_APPLICATION
> +	.short		0
> +	.quad		0, 0, 0, 0
> +	.long		0
> +	.long		(.Lsection_table - .) / 8
> +
> +	.quad		0				// ExportTable
> +	.quad		0				// ImportTable
> +	.quad		0				// ResourceTable
> +	.quad		0				// ExceptionTable
> +	.quad		0				// CertificationTable
> +	.quad		0				// BaseRelocationTable
> +#ifdef CONFIG_DEBUG_EFI
> +	.long		.Lefi_debug_table - .Ldoshdr	// DebugTable
> +	.long		.Lefi_debug_table_size
> +#endif
> +
> +.Lsection_table:
> +	.ascii		".text\0\0\0"
> +	.long		_etext - .Lefi_header_end
> +	.long		.Lefi_header_end - .Ldoshdr
> +	.long		_etext - .Lefi_header_end
> +	.long		.Lefi_header_end - .Ldoshdr
> +
> +	.long		0, 0
> +	.short		0, 0
> +	.long		IMAGE_SCN_CNT_CODE | \
> +			IMAGE_SCN_MEM_READ | \
> +			IMAGE_SCN_MEM_EXECUTE
> +
> +	.ascii		".data\0\0\0"
> +	.long		__data_size
> +	.long		_etext - .Ldoshdr
> +	.long		__data_rawsize
> +	.long		_etext - .Ldoshdr
> +
> +	.long		0, 0
> +	.short		0, 0
> +	.long		IMAGE_SCN_CNT_INITIALIZED_DATA | \
> +			IMAGE_SCN_MEM_READ | \
> +			IMAGE_SCN_MEM_WRITE
> +
> +	.set		.Lsection_count, (. - .Lsection_table) / 40
> +
> +#ifdef CONFIG_DEBUG_EFI
> +	.section	".rodata", "a"
> +	.align		2
> +.Lefi_debug_table:
> +	// EFI_IMAGE_DEBUG_DIRECTORY_ENTRY
> +	.long		0				// Characteristics
> +	.long		0				// TimeDateStamp
> +	.short		0				// MajorVersion
> +	.short		0				// MinorVersion
> +	.long		IMAGE_DEBUG_TYPE_CODEVIEW	// Type
> +	.long		.Lefi_debug_entry_size		// SizeOfData
> +	.long		0				// RVA
> +	.long		.Lefi_debug_entry - .Ldoshdr	// FileOffset
> +
> +	.set		.Lefi_debug_table_size, . - .Lefi_debug_table
> +	.previous
> +
> +.Lefi_debug_entry:
> +	// EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY
> +	.ascii		"NB10"				// Signature
> +	.long		0				// Unknown
> +	.long		0				// Unknown2
> +	.long		0				// Unknown3
> +
> +	.asciz		ZIMG_EFI_PATH
> +
> +	.set		.Lefi_debug_entry_size, . - .Lefi_debug_entry
> +#endif
> +
> +	.p2align	12
> +.Lefi_header_end:
> +
> diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c
> new file mode 100644
> index 000000000000..9cf968e90775
> --- /dev/null
> +++ b/drivers/firmware/efi/libstub/zboot.c
> @@ -0,0 +1,101 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/efi.h>
> +#include <linux/pe.h>
> +#include <asm/efi.h>
> +
> +#include "efistub.h"
> +
> +static unsigned char zboot_heap[SZ_64K] __aligned(64);
> +static unsigned long free_mem_ptr, free_mem_end_ptr;
> +
> +#define STATIC static
> +#if defined(CONFIG_KERNEL_GZIP)
> +#include "../../../../lib/decompress_inflate.c"
> +#elif defined(CONFIG_KERNEL_LZ4)
> +#include "../../../../lib/decompress_unlz4.c"
> +#elif defined(CONFIG_KERNEL_LZMA)
> +#include "../../../../lib/decompress_unlzma.c"
> +#elif defined(CONFIG_KERNEL_LZO)
> +#include "../../../../lib/decompress_unlzo.c"
> +#endif
> +
> +extern char _gzdata_start[], _gzdata_end[];
> +extern u32 uncompressed_size __aligned(1);
> +
> +static void log(efi_char16_t str[])
> +{
> +	efi_call_proto(efi_table_attr(efi_system_table, con_out),
> +		       output_string, L"EFI decompressor: ");
> +	efi_call_proto(efi_table_attr(efi_system_table, con_out),
> +		       output_string, str);
> +}
> +
> +static void error(char *x)
> +{
> +	log(L"error() called from decompressor library\n");
> +}
> +
> +efi_status_t __efiapi efi_zboot_entry(efi_handle_t handle,
> +				      efi_system_table_t *systab)
> +{
> +	static efi_guid_t loaded_image = LOADED_IMAGE_PROTOCOL_GUID;
> +	efi_loaded_image_t *parent, *child;
> +	unsigned long image_buffer;
> +	efi_handle_t child_handle;
> +	efi_status_t status;
> +	int ret;
> +
> +	WRITE_ONCE(efi_system_table, systab);
> +
> +	free_mem_ptr = (unsigned long)&zboot_heap;
> +	free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
> +
> +	status = efi_bs_call(handle_protocol, handle, &loaded_image,
> +			     (void **)&parent);
> +	if (status != EFI_SUCCESS) {
> +		log(L"Failed to locate parent's loaded image protocol\n");
> +		return status;
> +	}
> +
> +	status = efi_allocate_pages(uncompressed_size, &image_buffer, ULONG_MAX);
> +	if (status != EFI_SUCCESS) {
> +		log(L"Failed to allocate memory\n");
> +		return status;
> +	}
> +
> +	ret = __decompress(_gzdata_start, _gzdata_end - _gzdata_start, NULL,
> +			   NULL, (unsigned char *)image_buffer, 0, NULL,
> +			   error);
> +	if (ret	< 0) {
> +		log(L"Decompression failed\n");
> +		return EFI_LOAD_ERROR;
> +	}
> +
> +	status = efi_bs_call(load_image, false, handle, NULL,

I would prefer to pass the device path of the compressed image instead 
of NULL. This way information is not lost.

> +			     (void *)image_buffer, uncompressed_size,
> +			     &child_handle);
> +	if (status != EFI_SUCCESS) {
> +		log(L"Failed to load image\n");
> +		return status;
> +	}
> +
> +	status = efi_bs_call(handle_protocol, child_handle, &loaded_image,
> +			     (void **)&child);
> +	if (status != EFI_SUCCESS) {
> +		log(L"Failed to locate child's loaded image protocol\n");
> +		return status;
> +	}
> +
> +	// Copy the kernel command line
> +	child->load_options = parent->load_options;
> +	child->load_options_size = parent->load_options_size;
> +
> +	status = efi_bs_call(start_image, child_handle, NULL, NULL);
> +	if (status != EFI_SUCCESS) {
> +		log(L"StartImage() returned with error\n");

Please, pass pointers for ExitDataSize and ExitData. If ExitDataSize != 
0 a string is provided in ExitData. Return that data to the caller of 
the compressed image. You may additionally print the string here.

The caller then will then take care of freeing ExitData (via FreePool()) 
and optionally log the information.

Best regards

Heinrich

> +		return status;
> +	}
> +
> +	return EFI_SUCCESS;
> +}
> diff --git a/drivers/firmware/efi/libstub/zboot.lds b/drivers/firmware/efi/libstub/zboot.lds
> new file mode 100644
> index 000000000000..d6ba89a0c294
> --- /dev/null
> +++ b/drivers/firmware/efi/libstub/zboot.lds
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +ENTRY(__efistub_efi_zboot_header);
> +
> +SECTIONS
> +{
> +	.text : ALIGN(4096) {
> +		*(.head)
> +		*(.text* .init.text*)
> +	}
> +
> +	.rodata : ALIGN(8) {
> +		__efistub__gzdata_start = .;
> +		*(.gzdata)
> +		__efistub__gzdata_end = .;
> +		*(.rodata* .init.rodata* .srodata*)
> +		_etext = ALIGN(4096);
> +		. = _etext;
> +	}
> +
> +	.data : ALIGN(4096) {
> +		*(.data* .init.data*)
> +		_edata = ALIGN(512);
> +		. = _edata;
> +	}
> +
> +	.bss : {
> +		*(.bss* .init.bss*)
> +		_end = ALIGN(512);
> +		. = _end;
> +	}
> +}
> +
> +PROVIDE(__efistub__gzdata_size = ABSOLUTE(. - __efistub__gzdata_start));
> +
> +PROVIDE(__efistub_uncompressed_size = __efistub__gzdata_end - 4);
> +
> +PROVIDE(__data_rawsize = ABSOLUTE(_edata - _etext));
> +PROVIDE(__data_size = ABSOLUTE(_end - _etext));
Ard Biesheuvel Aug. 18, 2022, 5:10 p.m. UTC | #2
On Thu, 18 Aug 2022 at 18:42, Heinrich Schuchardt
<heinrich.schuchardt@canonical.com> wrote:
>
> On 8/17/22 13:03, Ard Biesheuvel wrote:
> > 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.
> >
> > There are various possible strategies to work around this requirement,
> > but they all rely either on overriding internal PI/DXE protocols (which
> > are not part of the EFI spec) or omitting the firmware provided
> > LoadImage() and StartImage() boot services, which is also undesirable,
> > given that they encapsulate platform specific policies related to secure
> > boot and measured boot, but also related to memory permissions (whether
> > or not and which types of heap allocations have both write and execute
> > permissions.)
> >
> > The only generic and truly portable way around this is to simply sign
> > both the inner and the outer image with the same key/cert pair, so this
> > is what is implemented here.
> >
> > 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.
> >
> > Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> > ---
> >   drivers/firmware/efi/Kconfig                |  31 ++++-
> >   drivers/firmware/efi/libstub/Makefile       |   8 +-
> >   drivers/firmware/efi/libstub/Makefile.zboot |  69 ++++++++++
> >   drivers/firmware/efi/libstub/zboot-header.S | 139 ++++++++++++++++++++
> >   drivers/firmware/efi/libstub/zboot.c        | 101 ++++++++++++++
> >   drivers/firmware/efi/libstub/zboot.lds      |  39 ++++++
> >   6 files changed, 382 insertions(+), 5 deletions(-)
> >
...
> > diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c
> > new file mode 100644
> > index 000000000000..9cf968e90775
> > --- /dev/null
> > +++ b/drivers/firmware/efi/libstub/zboot.c
...
> > +efi_status_t __efiapi efi_zboot_entry(efi_handle_t handle,
> > +                                   efi_system_table_t *systab)
> > +{
> > +     static efi_guid_t loaded_image = LOADED_IMAGE_PROTOCOL_GUID;
> > +     efi_loaded_image_t *parent, *child;
> > +     unsigned long image_buffer;
> > +     efi_handle_t child_handle;
> > +     efi_status_t status;
> > +     int ret;
> > +
> > +     WRITE_ONCE(efi_system_table, systab);
> > +
> > +     free_mem_ptr = (unsigned long)&zboot_heap;
> > +     free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
> > +
> > +     status = efi_bs_call(handle_protocol, handle, &loaded_image,
> > +                          (void **)&parent);
> > +     if (status != EFI_SUCCESS) {
> > +             log(L"Failed to locate parent's loaded image protocol\n");
> > +             return status;
> > +     }
> > +
> > +     status = efi_allocate_pages(uncompressed_size, &image_buffer, ULONG_MAX);
> > +     if (status != EFI_SUCCESS) {
> > +             log(L"Failed to allocate memory\n");
> > +             return status;
> > +     }
> > +
> > +     ret = __decompress(_gzdata_start, _gzdata_end - _gzdata_start, NULL,
> > +                        NULL, (unsigned char *)image_buffer, 0, NULL,
> > +                        error);
> > +     if (ret < 0) {
> > +             log(L"Decompression failed\n");
> > +             return EFI_LOAD_ERROR;
> > +     }
> > +
> > +     status = efi_bs_call(load_image, false, handle, NULL,
>
> I would prefer to pass the device path of the compressed image instead
> of NULL. This way information is not lost.
>

That way, we will have two loaded images with different handles
claiming to be loaded from the same device path - I don't think that
is appropriate tbh.

What we could do is define a vendor GUID for the decompressed kernel,
and create a device path for it. That way, you can grab the
loaded_image of the parent to obtain this information.

What did you have in mind as a use case?

> > +                          (void *)image_buffer, uncompressed_size,
> > +                          &child_handle);
> > +     if (status != EFI_SUCCESS) {
> > +             log(L"Failed to load image\n");
> > +             return status;
> > +     }
> > +
> > +     status = efi_bs_call(handle_protocol, child_handle, &loaded_image,
> > +                          (void **)&child);
> > +     if (status != EFI_SUCCESS) {
> > +             log(L"Failed to locate child's loaded image protocol\n");
> > +             return status;
> > +     }
> > +
> > +     // Copy the kernel command line
> > +     child->load_options = parent->load_options;
> > +     child->load_options_size = parent->load_options_size;
> > +
> > +     status = efi_bs_call(start_image, child_handle, NULL, NULL);
> > +     if (status != EFI_SUCCESS) {
> > +             log(L"StartImage() returned with error\n");
>
> Please, pass pointers for ExitDataSize and ExitData. If ExitDataSize !=
> 0 a string is provided in ExitData. Return that data to the caller of
> the compressed image. You may additionally print the string here.
>
> The caller then will then take care of freeing ExitData (via FreePool())
> and optionally log the information.
>

Good idea, I will add that.

Thanks,
Ard.
Heinrich Schuchardt Aug. 19, 2022, 5:29 a.m. UTC | #3
On 8/18/22 19:10, Ard Biesheuvel wrote:
> On Thu, 18 Aug 2022 at 18:42, Heinrich Schuchardt
> <heinrich.schuchardt@canonical.com> wrote:
>>
>> On 8/17/22 13:03, Ard Biesheuvel wrote:
>>> 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.
>>>
>>> There are various possible strategies to work around this requirement,
>>> but they all rely either on overriding internal PI/DXE protocols (which
>>> are not part of the EFI spec) or omitting the firmware provided
>>> LoadImage() and StartImage() boot services, which is also undesirable,
>>> given that they encapsulate platform specific policies related to secure
>>> boot and measured boot, but also related to memory permissions (whether
>>> or not and which types of heap allocations have both write and execute
>>> permissions.)
>>>
>>> The only generic and truly portable way around this is to simply sign
>>> both the inner and the outer image with the same key/cert pair, so this
>>> is what is implemented here.
>>>
>>> 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.
>>>
>>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
>>> ---
>>>    drivers/firmware/efi/Kconfig                |  31 ++++-
>>>    drivers/firmware/efi/libstub/Makefile       |   8 +-
>>>    drivers/firmware/efi/libstub/Makefile.zboot |  69 ++++++++++
>>>    drivers/firmware/efi/libstub/zboot-header.S | 139 ++++++++++++++++++++
>>>    drivers/firmware/efi/libstub/zboot.c        | 101 ++++++++++++++
>>>    drivers/firmware/efi/libstub/zboot.lds      |  39 ++++++
>>>    6 files changed, 382 insertions(+), 5 deletions(-)
>>>
> ...
>>> diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c
>>> new file mode 100644
>>> index 000000000000..9cf968e90775
>>> --- /dev/null
>>> +++ b/drivers/firmware/efi/libstub/zboot.c
> ...
>>> +efi_status_t __efiapi efi_zboot_entry(efi_handle_t handle,
>>> +                                   efi_system_table_t *systab)
>>> +{
>>> +     static efi_guid_t loaded_image = LOADED_IMAGE_PROTOCOL_GUID;
>>> +     efi_loaded_image_t *parent, *child;
>>> +     unsigned long image_buffer;
>>> +     efi_handle_t child_handle;
>>> +     efi_status_t status;
>>> +     int ret;
>>> +
>>> +     WRITE_ONCE(efi_system_table, systab);
>>> +
>>> +     free_mem_ptr = (unsigned long)&zboot_heap;
>>> +     free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
>>> +
>>> +     status = efi_bs_call(handle_protocol, handle, &loaded_image,
>>> +                          (void **)&parent);
>>> +     if (status != EFI_SUCCESS) {
>>> +             log(L"Failed to locate parent's loaded image protocol\n");
>>> +             return status;
>>> +     }
>>> +
>>> +     status = efi_allocate_pages(uncompressed_size, &image_buffer, ULONG_MAX);
>>> +     if (status != EFI_SUCCESS) {
>>> +             log(L"Failed to allocate memory\n");
>>> +             return status;
>>> +     }
>>> +
>>> +     ret = __decompress(_gzdata_start, _gzdata_end - _gzdata_start, NULL,
>>> +                        NULL, (unsigned char *)image_buffer, 0, NULL,
>>> +                        error);
>>> +     if (ret < 0) {
>>> +             log(L"Decompression failed\n");
>>> +             return EFI_LOAD_ERROR;
>>> +     }
>>> +
>>> +     status = efi_bs_call(load_image, false, handle, NULL,
>>
>> I would prefer to pass the device path of the compressed image instead
>> of NULL. This way information is not lost.
>>
> 
> That way, we will have two loaded images with different handles
> claiming to be loaded from the same device path - I don't think that
> is appropriate tbh.

They both are the product of the same file on disk.

> 
> What we could do is define a vendor GUID for the decompressed kernel,
> and create a device path for it. That way, you can grab the
> loaded_image of the parent to obtain this information.
> 
> What did you have in mind as a use case?

The device-path could be used in the kernel log.

It can be used to find the device or folder with initrd where we use 
initrd= on the command line.

You could use the device path to access the original file, e.g. to read 
additional information.

For all use cases you would want to have the original device path.

Best regards

Heinrich

> 
>>> +                          (void *)image_buffer, uncompressed_size,
>>> +                          &child_handle);
>>> +     if (status != EFI_SUCCESS) {
>>> +             log(L"Failed to load image\n");
>>> +             return status;
>>> +     }
>>> +
>>> +     status = efi_bs_call(handle_protocol, child_handle, &loaded_image,
>>> +                          (void **)&child);
>>> +     if (status != EFI_SUCCESS) {
>>> +             log(L"Failed to locate child's loaded image protocol\n");
>>> +             return status;
>>> +     }
>>> +
>>> +     // Copy the kernel command line
>>> +     child->load_options = parent->load_options;
>>> +     child->load_options_size = parent->load_options_size;
>>> +
>>> +     status = efi_bs_call(start_image, child_handle, NULL, NULL);
>>> +     if (status != EFI_SUCCESS) {
>>> +             log(L"StartImage() returned with error\n");
>>
>> Please, pass pointers for ExitDataSize and ExitData. If ExitDataSize !=
>> 0 a string is provided in ExitData. Return that data to the caller of
>> the compressed image. You may additionally print the string here.
>>
>> The caller then will then take care of freeing ExitData (via FreePool())
>> and optionally log the information.
>>
> 
> Good idea, I will add that.
> 
> Thanks,
> Ard.
Ard Biesheuvel Aug. 19, 2022, 6:52 a.m. UTC | #4
On Fri, 19 Aug 2022 at 07:29, Heinrich Schuchardt
<heinrich.schuchardt@canonical.com> wrote:
>
>
>
> On 8/18/22 19:10, Ard Biesheuvel wrote:
> > On Thu, 18 Aug 2022 at 18:42, Heinrich Schuchardt
> > <heinrich.schuchardt@canonical.com> wrote:
> >>
> >> On 8/17/22 13:03, Ard Biesheuvel wrote:
> >>> 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.
> >>>
> >>> There are various possible strategies to work around this requirement,
> >>> but they all rely either on overriding internal PI/DXE protocols (which
> >>> are not part of the EFI spec) or omitting the firmware provided
> >>> LoadImage() and StartImage() boot services, which is also undesirable,
> >>> given that they encapsulate platform specific policies related to secure
> >>> boot and measured boot, but also related to memory permissions (whether
> >>> or not and which types of heap allocations have both write and execute
> >>> permissions.)
> >>>
> >>> The only generic and truly portable way around this is to simply sign
> >>> both the inner and the outer image with the same key/cert pair, so this
> >>> is what is implemented here.
> >>>
> >>> 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.
> >>>
> >>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> >>> ---
> >>>    drivers/firmware/efi/Kconfig                |  31 ++++-
> >>>    drivers/firmware/efi/libstub/Makefile       |   8 +-
> >>>    drivers/firmware/efi/libstub/Makefile.zboot |  69 ++++++++++
> >>>    drivers/firmware/efi/libstub/zboot-header.S | 139 ++++++++++++++++++++
> >>>    drivers/firmware/efi/libstub/zboot.c        | 101 ++++++++++++++
> >>>    drivers/firmware/efi/libstub/zboot.lds      |  39 ++++++
> >>>    6 files changed, 382 insertions(+), 5 deletions(-)
> >>>
> > ...
> >>> diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c
> >>> new file mode 100644
> >>> index 000000000000..9cf968e90775
> >>> --- /dev/null
> >>> +++ b/drivers/firmware/efi/libstub/zboot.c
> > ...
> >>> +efi_status_t __efiapi efi_zboot_entry(efi_handle_t handle,
> >>> +                                   efi_system_table_t *systab)
> >>> +{
> >>> +     static efi_guid_t loaded_image = LOADED_IMAGE_PROTOCOL_GUID;
> >>> +     efi_loaded_image_t *parent, *child;
> >>> +     unsigned long image_buffer;
> >>> +     efi_handle_t child_handle;
> >>> +     efi_status_t status;
> >>> +     int ret;
> >>> +
> >>> +     WRITE_ONCE(efi_system_table, systab);
> >>> +
> >>> +     free_mem_ptr = (unsigned long)&zboot_heap;
> >>> +     free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
> >>> +
> >>> +     status = efi_bs_call(handle_protocol, handle, &loaded_image,
> >>> +                          (void **)&parent);
> >>> +     if (status != EFI_SUCCESS) {
> >>> +             log(L"Failed to locate parent's loaded image protocol\n");
> >>> +             return status;
> >>> +     }
> >>> +
> >>> +     status = efi_allocate_pages(uncompressed_size, &image_buffer, ULONG_MAX);
> >>> +     if (status != EFI_SUCCESS) {
> >>> +             log(L"Failed to allocate memory\n");
> >>> +             return status;
> >>> +     }
> >>> +
> >>> +     ret = __decompress(_gzdata_start, _gzdata_end - _gzdata_start, NULL,
> >>> +                        NULL, (unsigned char *)image_buffer, 0, NULL,
> >>> +                        error);
> >>> +     if (ret < 0) {
> >>> +             log(L"Decompression failed\n");
> >>> +             return EFI_LOAD_ERROR;
> >>> +     }
> >>> +
> >>> +     status = efi_bs_call(load_image, false, handle, NULL,
> >>
> >> I would prefer to pass the device path of the compressed image instead
> >> of NULL. This way information is not lost.
> >>
> >
> > That way, we will have two loaded images with different handles
> > claiming to be loaded from the same device path - I don't think that
> > is appropriate tbh.
>
> They both are the product of the same file on disk.
>

But they are not the same. When re-loading the device path (as you
suggest below) you will get a completely different file, and the only
way to get at the payload is to execute it.

So using the same device path is out of the question imo.

> >
> > What we could do is define a vendor GUID for the decompressed kernel,
> > and create a device path for it. That way, you can grab the
> > loaded_image of the parent to obtain this information.
> >
> > What did you have in mind as a use case?
>
> The device-path could be used in the kernel log.
>
> It can be used to find the device or folder with initrd where we use
> initrd= on the command line.
>
> You could use the device path to access the original file, e.g. to read
> additional information.
>
> For all use cases you would want to have the original device path.
>

What we could do is:

- define a device path in the decompressor, e.g.,

<original device path>/Offset(<start>, <end>)/VendorMedia(xxx-xxx-xxx,
<compression type>)

where start, end and compression type describe the compressed payload
inside the decompressor executable. (The compression type could be
omitted, or could be a separate node.)

- install the LoadFile2 protocol and the device path protocol onto a
handle, and move the decompression logic into the LoadFile2
implementation

- drop the SourceBuffer and SourceSize arguments to LoadImage(), and
pass the device path instead, so that LoadFile2 will be invoked by
LoadImage directly to perform the decompression.

That way, we retain the information about the outer file, and each
piece is described in detail in device path notation. As a bonus, we
could easily expose the compressed part separately, if there is a need
for that.

This doesn't cover the initrd= issue you raised, but that is something
we could address later in the stub if we wanted to (but I don't think
initrd= is something we should care too much about)
Heinrich Schuchardt Aug. 19, 2022, 7:01 a.m. UTC | #5
On 8/19/22 08:52, Ard Biesheuvel wrote:
> On Fri, 19 Aug 2022 at 07:29, Heinrich Schuchardt
> <heinrich.schuchardt@canonical.com> wrote:
>>
>>
>>
>> On 8/18/22 19:10, Ard Biesheuvel wrote:
>>> On Thu, 18 Aug 2022 at 18:42, Heinrich Schuchardt
>>> <heinrich.schuchardt@canonical.com> wrote:
>>>>
>>>> On 8/17/22 13:03, Ard Biesheuvel wrote:
>>>>> 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.
>>>>>
>>>>> There are various possible strategies to work around this requirement,
>>>>> but they all rely either on overriding internal PI/DXE protocols (which
>>>>> are not part of the EFI spec) or omitting the firmware provided
>>>>> LoadImage() and StartImage() boot services, which is also undesirable,
>>>>> given that they encapsulate platform specific policies related to secure
>>>>> boot and measured boot, but also related to memory permissions (whether
>>>>> or not and which types of heap allocations have both write and execute
>>>>> permissions.)
>>>>>
>>>>> The only generic and truly portable way around this is to simply sign
>>>>> both the inner and the outer image with the same key/cert pair, so this
>>>>> is what is implemented here.
>>>>>
>>>>> 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.
>>>>>
>>>>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
>>>>> ---
>>>>>     drivers/firmware/efi/Kconfig                |  31 ++++-
>>>>>     drivers/firmware/efi/libstub/Makefile       |   8 +-
>>>>>     drivers/firmware/efi/libstub/Makefile.zboot |  69 ++++++++++
>>>>>     drivers/firmware/efi/libstub/zboot-header.S | 139 ++++++++++++++++++++
>>>>>     drivers/firmware/efi/libstub/zboot.c        | 101 ++++++++++++++
>>>>>     drivers/firmware/efi/libstub/zboot.lds      |  39 ++++++
>>>>>     6 files changed, 382 insertions(+), 5 deletions(-)
>>>>>
>>> ...
>>>>> diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c
>>>>> new file mode 100644
>>>>> index 000000000000..9cf968e90775
>>>>> --- /dev/null
>>>>> +++ b/drivers/firmware/efi/libstub/zboot.c
>>> ...
>>>>> +efi_status_t __efiapi efi_zboot_entry(efi_handle_t handle,
>>>>> +                                   efi_system_table_t *systab)
>>>>> +{
>>>>> +     static efi_guid_t loaded_image = LOADED_IMAGE_PROTOCOL_GUID;
>>>>> +     efi_loaded_image_t *parent, *child;
>>>>> +     unsigned long image_buffer;
>>>>> +     efi_handle_t child_handle;
>>>>> +     efi_status_t status;
>>>>> +     int ret;
>>>>> +
>>>>> +     WRITE_ONCE(efi_system_table, systab);
>>>>> +
>>>>> +     free_mem_ptr = (unsigned long)&zboot_heap;
>>>>> +     free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
>>>>> +
>>>>> +     status = efi_bs_call(handle_protocol, handle, &loaded_image,
>>>>> +                          (void **)&parent);
>>>>> +     if (status != EFI_SUCCESS) {
>>>>> +             log(L"Failed to locate parent's loaded image protocol\n");
>>>>> +             return status;
>>>>> +     }
>>>>> +
>>>>> +     status = efi_allocate_pages(uncompressed_size, &image_buffer, ULONG_MAX);
>>>>> +     if (status != EFI_SUCCESS) {
>>>>> +             log(L"Failed to allocate memory\n");
>>>>> +             return status;
>>>>> +     }
>>>>> +
>>>>> +     ret = __decompress(_gzdata_start, _gzdata_end - _gzdata_start, NULL,
>>>>> +                        NULL, (unsigned char *)image_buffer, 0, NULL,
>>>>> +                        error);
>>>>> +     if (ret < 0) {
>>>>> +             log(L"Decompression failed\n");
>>>>> +             return EFI_LOAD_ERROR;
>>>>> +     }
>>>>> +
>>>>> +     status = efi_bs_call(load_image, false, handle, NULL,
>>>>
>>>> I would prefer to pass the device path of the compressed image instead
>>>> of NULL. This way information is not lost.
>>>>
>>>
>>> That way, we will have two loaded images with different handles
>>> claiming to be loaded from the same device path - I don't think that
>>> is appropriate tbh.
>>
>> They both are the product of the same file on disk.
>>
> 
> But they are not the same. When re-loading the device path (as you
> suggest below) you will get a completely different file, and the only
> way to get at the payload is to execute it.
> 
> So using the same device path is out of the question imo.

How about appending a VenHW() node with a decompressor specific GUID at 
the end of the DP?

I think that is the most UEFIish way to express that the handle is 
derived from the compressed file.

You could even put additional information into the VenHW() node like the 
compression type or the compressed size.

Best regards

Heinrich

> 
>>>
>>> What we could do is define a vendor GUID for the decompressed kernel,
>>> and create a device path for it. That way, you can grab the
>>> loaded_image of the parent to obtain this information.
>>>
>>> What did you have in mind as a use case?
>>
>> The device-path could be used in the kernel log.
>>
>> It can be used to find the device or folder with initrd where we use
>> initrd= on the command line.
>>
>> You could use the device path to access the original file, e.g. to read
>> additional information.
>>
>> For all use cases you would want to have the original device path.
>>
> 
> What we could do is:
> 
> - define a device path in the decompressor, e.g.,
> 
> <original device path>/Offset(<start>, <end>)/VendorMedia(xxx-xxx-xxx,
> <compression type>)
> 
> where start, end and compression type describe the compressed payload
> inside the decompressor executable. (The compression type could be
> omitted, or could be a separate node.)
> 
> - install the LoadFile2 protocol and the device path protocol onto a
> handle, and move the decompression logic into the LoadFile2
> implementation
> 
> - drop the SourceBuffer and SourceSize arguments to LoadImage(), and
> pass the device path instead, so that LoadFile2 will be invoked by
> LoadImage directly to perform the decompression.
> 
> That way, we retain the information about the outer file, and each
> piece is described in detail in device path notation. As a bonus, we
> could easily expose the compressed part separately, if there is a need
> for that.
> 
> This doesn't cover the initrd= issue you raised, but that is something
> we could address later in the stub if we wanted to (but I don't think
> initrd= is something we should care too much about)
Ard Biesheuvel Aug. 19, 2022, 7:07 a.m. UTC | #6
On Fri, 19 Aug 2022 at 09:01, Heinrich Schuchardt
<heinrich.schuchardt@canonical.com> wrote:
>
> On 8/19/22 08:52, Ard Biesheuvel wrote:
> > On Fri, 19 Aug 2022 at 07:29, Heinrich Schuchardt
> > <heinrich.schuchardt@canonical.com> wrote:
> >>
> >>
> >>
> >> On 8/18/22 19:10, Ard Biesheuvel wrote:
> >>> On Thu, 18 Aug 2022 at 18:42, Heinrich Schuchardt
> >>> <heinrich.schuchardt@canonical.com> wrote:
> >>>>
> >>>> On 8/17/22 13:03, Ard Biesheuvel wrote:
> >>>>> 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.
> >>>>>
> >>>>> There are various possible strategies to work around this requirement,
> >>>>> but they all rely either on overriding internal PI/DXE protocols (which
> >>>>> are not part of the EFI spec) or omitting the firmware provided
> >>>>> LoadImage() and StartImage() boot services, which is also undesirable,
> >>>>> given that they encapsulate platform specific policies related to secure
> >>>>> boot and measured boot, but also related to memory permissions (whether
> >>>>> or not and which types of heap allocations have both write and execute
> >>>>> permissions.)
> >>>>>
> >>>>> The only generic and truly portable way around this is to simply sign
> >>>>> both the inner and the outer image with the same key/cert pair, so this
> >>>>> is what is implemented here.
> >>>>>
> >>>>> 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.
> >>>>>
> >>>>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> >>>>> ---
> >>>>>     drivers/firmware/efi/Kconfig                |  31 ++++-
> >>>>>     drivers/firmware/efi/libstub/Makefile       |   8 +-
> >>>>>     drivers/firmware/efi/libstub/Makefile.zboot |  69 ++++++++++
> >>>>>     drivers/firmware/efi/libstub/zboot-header.S | 139 ++++++++++++++++++++
> >>>>>     drivers/firmware/efi/libstub/zboot.c        | 101 ++++++++++++++
> >>>>>     drivers/firmware/efi/libstub/zboot.lds      |  39 ++++++
> >>>>>     6 files changed, 382 insertions(+), 5 deletions(-)
> >>>>>
> >>> ...
> >>>>> diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c
> >>>>> new file mode 100644
> >>>>> index 000000000000..9cf968e90775
> >>>>> --- /dev/null
> >>>>> +++ b/drivers/firmware/efi/libstub/zboot.c
> >>> ...
> >>>>> +efi_status_t __efiapi efi_zboot_entry(efi_handle_t handle,
> >>>>> +                                   efi_system_table_t *systab)
> >>>>> +{
> >>>>> +     static efi_guid_t loaded_image = LOADED_IMAGE_PROTOCOL_GUID;
> >>>>> +     efi_loaded_image_t *parent, *child;
> >>>>> +     unsigned long image_buffer;
> >>>>> +     efi_handle_t child_handle;
> >>>>> +     efi_status_t status;
> >>>>> +     int ret;
> >>>>> +
> >>>>> +     WRITE_ONCE(efi_system_table, systab);
> >>>>> +
> >>>>> +     free_mem_ptr = (unsigned long)&zboot_heap;
> >>>>> +     free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
> >>>>> +
> >>>>> +     status = efi_bs_call(handle_protocol, handle, &loaded_image,
> >>>>> +                          (void **)&parent);
> >>>>> +     if (status != EFI_SUCCESS) {
> >>>>> +             log(L"Failed to locate parent's loaded image protocol\n");
> >>>>> +             return status;
> >>>>> +     }
> >>>>> +
> >>>>> +     status = efi_allocate_pages(uncompressed_size, &image_buffer, ULONG_MAX);
> >>>>> +     if (status != EFI_SUCCESS) {
> >>>>> +             log(L"Failed to allocate memory\n");
> >>>>> +             return status;
> >>>>> +     }
> >>>>> +
> >>>>> +     ret = __decompress(_gzdata_start, _gzdata_end - _gzdata_start, NULL,
> >>>>> +                        NULL, (unsigned char *)image_buffer, 0, NULL,
> >>>>> +                        error);
> >>>>> +     if (ret < 0) {
> >>>>> +             log(L"Decompression failed\n");
> >>>>> +             return EFI_LOAD_ERROR;
> >>>>> +     }
> >>>>> +
> >>>>> +     status = efi_bs_call(load_image, false, handle, NULL,
> >>>>
> >>>> I would prefer to pass the device path of the compressed image instead
> >>>> of NULL. This way information is not lost.
> >>>>
> >>>
> >>> That way, we will have two loaded images with different handles
> >>> claiming to be loaded from the same device path - I don't think that
> >>> is appropriate tbh.
> >>
> >> They both are the product of the same file on disk.
> >>
> >
> > But they are not the same. When re-loading the device path (as you
> > suggest below) you will get a completely different file, and the only
> > way to get at the payload is to execute it.
> >
> > So using the same device path is out of the question imo.
>
> How about appending a VenHW() node with a decompressor specific GUID at
> the end of the DP?
>
> I think that is the most UEFIish way to express that the handle is
> derived from the compressed file.
>
> You could even put additional information into the VenHW() node like the
> compression type or the compressed size.
>

Uhm, yes, that is what I am proposing further down in this email.

See below.

> >
> >>>
> >>> What we could do is define a vendor GUID for the decompressed kernel,
> >>> and create a device path for it. That way, you can grab the
> >>> loaded_image of the parent to obtain this information.
> >>>
> >>> What did you have in mind as a use case?
> >>
> >> The device-path could be used in the kernel log.
> >>
> >> It can be used to find the device or folder with initrd where we use
> >> initrd= on the command line.
> >>
> >> You could use the device path to access the original file, e.g. to read
> >> additional information.
> >>
> >> For all use cases you would want to have the original device path.
> >>
> >
> > What we could do is:
> >
> > - define a device path in the decompressor, e.g.,
> >
> > <original device path>/Offset(<start>, <end>)/VendorMedia(xxx-xxx-xxx,
> > <compression type>)
> >
> > where start, end and compression type describe the compressed payload
> > inside the decompressor executable. (The compression type could be
> > omitted, or could be a separate node.)
> >
> > - install the LoadFile2 protocol and the device path protocol onto a
> > handle, and move the decompression logic into the LoadFile2
> > implementation
> >
> > - drop the SourceBuffer and SourceSize arguments to LoadImage(), and
> > pass the device path instead, so that LoadFile2 will be invoked by
> > LoadImage directly to perform the decompression.
> >
> > That way, we retain the information about the outer file, and each
> > piece is described in detail in device path notation. As a bonus, we
> > could easily expose the compressed part separately, if there is a need
> > for that.
> >
> > This doesn't cover the initrd= issue you raised, but that is something
> > we could address later in the stub if we wanted to (but I don't think
> > initrd= is something we should care too much about)
>
Heinrich Schuchardt Aug. 19, 2022, 7:41 a.m. UTC | #7
On 8/19/22 09:07, Ard Biesheuvel wrote:
> On Fri, 19 Aug 2022 at 09:01, Heinrich Schuchardt
> <heinrich.schuchardt@canonical.com> wrote:
>>
>> On 8/19/22 08:52, Ard Biesheuvel wrote:
>>> On Fri, 19 Aug 2022 at 07:29, Heinrich Schuchardt
>>> <heinrich.schuchardt@canonical.com> wrote:
>>>>
>>>>
>>>>
>>>> On 8/18/22 19:10, Ard Biesheuvel wrote:
>>>>> On Thu, 18 Aug 2022 at 18:42, Heinrich Schuchardt
>>>>> <heinrich.schuchardt@canonical.com> wrote:
>>>>>>
>>>>>> On 8/17/22 13:03, Ard Biesheuvel wrote:
>>>>>>> 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.
>>>>>>>
>>>>>>> There are various possible strategies to work around this requirement,
>>>>>>> but they all rely either on overriding internal PI/DXE protocols (which
>>>>>>> are not part of the EFI spec) or omitting the firmware provided
>>>>>>> LoadImage() and StartImage() boot services, which is also undesirable,
>>>>>>> given that they encapsulate platform specific policies related to secure
>>>>>>> boot and measured boot, but also related to memory permissions (whether
>>>>>>> or not and which types of heap allocations have both write and execute
>>>>>>> permissions.)
>>>>>>>
>>>>>>> The only generic and truly portable way around this is to simply sign
>>>>>>> both the inner and the outer image with the same key/cert pair, so this
>>>>>>> is what is implemented here.
>>>>>>>
>>>>>>> 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.
>>>>>>>
>>>>>>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
>>>>>>> ---
>>>>>>>      drivers/firmware/efi/Kconfig                |  31 ++++-
>>>>>>>      drivers/firmware/efi/libstub/Makefile       |   8 +-
>>>>>>>      drivers/firmware/efi/libstub/Makefile.zboot |  69 ++++++++++
>>>>>>>      drivers/firmware/efi/libstub/zboot-header.S | 139 ++++++++++++++++++++
>>>>>>>      drivers/firmware/efi/libstub/zboot.c        | 101 ++++++++++++++
>>>>>>>      drivers/firmware/efi/libstub/zboot.lds      |  39 ++++++
>>>>>>>      6 files changed, 382 insertions(+), 5 deletions(-)
>>>>>>>
>>>>> ...
>>>>>>> diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c
>>>>>>> new file mode 100644
>>>>>>> index 000000000000..9cf968e90775
>>>>>>> --- /dev/null
>>>>>>> +++ b/drivers/firmware/efi/libstub/zboot.c
>>>>> ...
>>>>>>> +efi_status_t __efiapi efi_zboot_entry(efi_handle_t handle,
>>>>>>> +                                   efi_system_table_t *systab)
>>>>>>> +{
>>>>>>> +     static efi_guid_t loaded_image = LOADED_IMAGE_PROTOCOL_GUID;
>>>>>>> +     efi_loaded_image_t *parent, *child;
>>>>>>> +     unsigned long image_buffer;
>>>>>>> +     efi_handle_t child_handle;
>>>>>>> +     efi_status_t status;
>>>>>>> +     int ret;
>>>>>>> +
>>>>>>> +     WRITE_ONCE(efi_system_table, systab);
>>>>>>> +
>>>>>>> +     free_mem_ptr = (unsigned long)&zboot_heap;
>>>>>>> +     free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
>>>>>>> +
>>>>>>> +     status = efi_bs_call(handle_protocol, handle, &loaded_image,
>>>>>>> +                          (void **)&parent);
>>>>>>> +     if (status != EFI_SUCCESS) {
>>>>>>> +             log(L"Failed to locate parent's loaded image protocol\n");
>>>>>>> +             return status;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     status = efi_allocate_pages(uncompressed_size, &image_buffer, ULONG_MAX);
>>>>>>> +     if (status != EFI_SUCCESS) {
>>>>>>> +             log(L"Failed to allocate memory\n");
>>>>>>> +             return status;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     ret = __decompress(_gzdata_start, _gzdata_end - _gzdata_start, NULL,
>>>>>>> +                        NULL, (unsigned char *)image_buffer, 0, NULL,
>>>>>>> +                        error);
>>>>>>> +     if (ret < 0) {
>>>>>>> +             log(L"Decompression failed\n");
>>>>>>> +             return EFI_LOAD_ERROR;
>>>>>>> +     }
>>>>>>> +
>>>>>>> +     status = efi_bs_call(load_image, false, handle, NULL,
>>>>>>
>>>>>> I would prefer to pass the device path of the compressed image instead
>>>>>> of NULL. This way information is not lost.
>>>>>>
>>>>>
>>>>> That way, we will have two loaded images with different handles
>>>>> claiming to be loaded from the same device path - I don't think that
>>>>> is appropriate tbh.
>>>>
>>>> They both are the product of the same file on disk.
>>>>
>>>
>>> But they are not the same. When re-loading the device path (as you
>>> suggest below) you will get a completely different file, and the only
>>> way to get at the payload is to execute it.
>>>
>>> So using the same device path is out of the question imo.
>>
>> How about appending a VenHW() node with a decompressor specific GUID at
>> the end of the DP?
>>
>> I think that is the most UEFIish way to express that the handle is
>> derived from the compressed file.
>>
>> You could even put additional information into the VenHW() node like the
>> compression type or the compressed size.
>>
> 
> Uhm, yes, that is what I am proposing further down in this email.
> 
> See below.
> 
>>>
>>>>>
>>>>> What we could do is define a vendor GUID for the decompressed kernel,
>>>>> and create a device path for it. That way, you can grab the
>>>>> loaded_image of the parent to obtain this information.
>>>>>
>>>>> What did you have in mind as a use case?
>>>>
>>>> The device-path could be used in the kernel log.
>>>>
>>>> It can be used to find the device or folder with initrd where we use
>>>> initrd= on the command line.
>>>>
>>>> You could use the device path to access the original file, e.g. to read
>>>> additional information.
>>>>
>>>> For all use cases you would want to have the original device path.
>>>>
>>>
>>> What we could do is:
>>>
>>> - define a device path in the decompressor, e.g.,
>>>
>>> <original device path>/Offset(<start>, <end>)/VendorMedia(xxx-xxx-xxx,
>>> <compression type>)
>>>
>>> where start, end and compression type describe the compressed payload
>>> inside the decompressor executable. (The compression type could be
>>> omitted, or could be a separate node.)
>>>
>>> - install the LoadFile2 protocol and the device path protocol onto a
>>> handle, and move the decompression logic into the LoadFile2
>>> implementation

Why would you create a LoadFile2 implementation if the decompressor is 
the only user? In this case I think an internal function call is more 
adequate.

A LoadFile2 protocol would make sense if the compressed image contains 
both a compressed kernel and the initrd and you wanted to provide the 
initrd via the LoadFile2 protocol.

For Iot use cases it would make sense to have a standalone process which 
you can use to:

* compress a kernel
* create a binary containing
* * a prepended decompressor binary
* * the compressed kernel
* * an optional initrd
* * an optional device-tree
* sign the complete file

At runtime the decompressor would:

* decompress the kernel
* create a LoadFile2 protocol for the initrd
* call the firmware to fix-up the device-tree
* install the fixed-up device-tree
* invoke the EFI stub of the kernel

This way we could have one binary where all relevant components are 
inside a single signed image.

Best regards

Heinrich

>>>
>>> - drop the SourceBuffer and SourceSize arguments to LoadImage(), and
>>> pass the device path instead, so that LoadFile2 will be invoked by
>>> LoadImage directly to perform the decompression.
>>>
>>> That way, we retain the information about the outer file, and each
>>> piece is described in detail in device path notation. As a bonus, we
>>> could easily expose the compressed part separately, if there is a need
>>> for that.
>>>
>>> This doesn't cover the initrd= issue you raised, but that is something
>>> we could address later in the stub if we wanted to (but I don't think
>>> initrd= is something we should care too much about)
>>
Ard Biesheuvel Aug. 19, 2022, 8:09 a.m. UTC | #8
On Fri, 19 Aug 2022 at 09:41, Heinrich Schuchardt
<heinrich.schuchardt@canonical.com> wrote:
>
> On 8/19/22 09:07, Ard Biesheuvel wrote:
> > On Fri, 19 Aug 2022 at 09:01, Heinrich Schuchardt
> > <heinrich.schuchardt@canonical.com> wrote:
> >>
> >> On 8/19/22 08:52, Ard Biesheuvel wrote:
> >>> On Fri, 19 Aug 2022 at 07:29, Heinrich Schuchardt
> >>> <heinrich.schuchardt@canonical.com> wrote:
> >>>>
> >>>>
> >>>>
> >>>> On 8/18/22 19:10, Ard Biesheuvel wrote:
...
> >>>>> What we could do is define a vendor GUID for the decompressed kernel,
> >>>>> and create a device path for it. That way, you can grab the
> >>>>> loaded_image of the parent to obtain this information.
> >>>>>
> >>>>> What did you have in mind as a use case?
> >>>>
> >>>> The device-path could be used in the kernel log.
> >>>>
> >>>> It can be used to find the device or folder with initrd where we use
> >>>> initrd= on the command line.
> >>>>
> >>>> You could use the device path to access the original file, e.g. to read
> >>>> additional information.
> >>>>
> >>>> For all use cases you would want to have the original device path.
> >>>>
> >>>
> >>> What we could do is:
> >>>
> >>> - define a device path in the decompressor, e.g.,
> >>>
> >>> <original device path>/Offset(<start>, <end>)/VendorMedia(xxx-xxx-xxx,
> >>> <compression type>)
> >>>
> >>> where start, end and compression type describe the compressed payload
> >>> inside the decompressor executable. (The compression type could be
> >>> omitted, or could be a separate node.)
> >>>
> >>> - install the LoadFile2 protocol and the device path protocol onto a
> >>> handle, and move the decompression logic into the LoadFile2
> >>> implementation
>
> Why would you create a LoadFile2 implementation if the decompressor is
> the only user? In this case I think an internal function call is more
> adequate.
>

You argued that you would want to access the original file, no? When
using the SourceBuffer argument to LoadImage(), the original data is
gone. When you pass a LoadFile2 protocol, you can use the device path
to reload the data (compressed or uncompressed) as needed.

> A LoadFile2 protocol would make sense if the compressed image contains
> both a compressed kernel and the initrd and you wanted to provide the
> initrd via the LoadFile2 protocol.
>

The initrd loading is a completely separate issue, and the fact that
we might use LoadFile2 for both is just a coincidence.

> For Iot use cases it would make sense to have a standalone process which
> you can use to:
>
> * compress a kernel
> * create a binary containing
> * * a prepended decompressor binary
> * * the compressed kernel
> * * an optional initrd
> * * an optional device-tree
> * sign the complete file
>
> At runtime the decompressor would:
>
> * decompress the kernel
> * create a LoadFile2 protocol for the initrd
> * call the firmware to fix-up the device-tree
> * install the fixed-up device-tree
> * invoke the EFI stub of the kernel
>
> This way we could have one binary where all relevant components are
> inside a single signed image.
>

The only generic and portable way to move data into memory and execute
it as code is using LoadImage/StartImage. Everything else is a hack,
and hacks are notoriously non-portable between architectures and
firmware implementations.

This means the 'inner' executable *must* be invoked using
LoadImage/StartImage, which implies it must be signed if secure boot
is enabled.

Fixing up the device tree is tied to specific architectures, and the
notion that the OS should be involved in poking the firmware at the
right time to fix it up is also highly arch dependent. Which means it
does not belong in a generic EFI decompressor either.

What you are describing is essentially the systemd-stub, which also
omits LoadImage/StartImage, which means it has to parse the PE/COFF
image, load the sections, apply the relocations, etc etc, all of which
it surely got correct right off the bat ...

The reason I am so adamant about this is that we are digging ourselves
into a hole here, and the more instances we add of this pattern, the
more difficult it is to reason about it, and to fix it when things
break.

Shim should not need to exist, but we all know it has to. But ideally,
it would only hook LoadImage/StartImage to interpose its own
implementations, leaving subsequent stages none the wiser. I
personally find it very disappointing that distro GRUB cannot work
without shim even if the distro's signing key is in db - this is a
missed opportunity imo.

But if we repeat this pattern at every level above it, (i.e., open
code PE app launch to avoid using LoadImage/StartImage), reasoning
about secure boot and measured boot becomes very difficult, and every
stage needs to invoke the TCG protocols directly etc etc
diff mbox series

Patch

diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index 6cb7384ad2ac..0c7beb8e3633 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -105,9 +105,36 @@  config EFI_RUNTIME_WRAPPERS
 config EFI_GENERIC_STUB
 	bool
 
+config EFI_ZBOOT
+	bool "Enable the generic EFI decompressor"
+	depends on EFI_GENERIC_STUB && !ARM
+	select HAVE_KERNEL_GZIP
+	select HAVE_KERNEL_LZ4
+	select HAVE_KERNEL_LZMA
+	select HAVE_KERNEL_LZO
+
+config EFI_ZBOOT_SIGNED
+	bool "Sign the EFI decompressor for UEFI secure boot"
+	depends on EFI_ZBOOT
+	help
+	  Use the 'sbsign' command line tool (which must exist on the host
+	  path) to sign both the EFI decompressor PE/COFF image, as well as the
+	  encapsulated PE/COFF image, which is subsequently compressed and
+	  wrapped by the former image.
+
+config EFI_ZBOOT_SIGNING_CERT
+	string "Certificate to use for signing the compressed EFI boot image"
+	depends on EFI_ZBOOT_SIGNED
+	default ""
+
+config EFI_ZBOOT_SIGNING_KEY
+	string "Private key to use for signing the compressed EFI boot image"
+	depends on EFI_ZBOOT_SIGNED
+	default ""
+
 config EFI_ARMSTUB_DTB_LOADER
 	bool "Enable the DTB loader"
-	depends on EFI_GENERIC_STUB && !RISCV
+	depends on EFI_GENERIC_STUB && !RISCV && !EFI_ZBOOT
 	default y
 	help
 	  Select this config option to add support for the dtb= command
@@ -124,7 +151,7 @@  config EFI_GENERIC_STUB_INITRD_CMDLINE_LOADER
 	bool "Enable the command line initrd loader" if !X86
 	depends on EFI_STUB && (EFI_GENERIC_STUB || X86)
 	default y if X86
-	depends on !RISCV
+	depends on !RISCV && !EFI_ZBOOT
 	help
 	  Select this config option to add support for the initrd= command
 	  line parameter, allowing an initrd that resides on the same volume
diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile
index 1406dc78edaa..a3d3d38d5afd 100644
--- a/drivers/firmware/efi/libstub/Makefile
+++ b/drivers/firmware/efi/libstub/Makefile
@@ -73,6 +73,11 @@  lib-$(CONFIG_X86)		+= x86-stub.o
 lib-$(CONFIG_RISCV)		+= riscv-stub.o
 CFLAGS_arm32-stub.o		:= -DTEXT_OFFSET=$(TEXT_OFFSET)
 
+lib-$(CONFIG_EFI_ZBOOT)		+= zboot.o
+
+extra-y				:= $(lib-y)
+lib-y				:= $(patsubst %.o,%.stub.o,$(lib-y))
+
 # Even when -mbranch-protection=none is set, Clang will generate a
 # .note.gnu.property for code-less object files (like lib/ctype.c),
 # so work around this by explicitly removing the unwanted section.
@@ -112,9 +117,6 @@  STUBCOPY_RELOC-$(CONFIG_ARM)	:= R_ARM_ABS
 # a verification pass to see if any absolute relocations exist in any of the
 # object files.
 #
-extra-y				:= $(lib-y)
-lib-y				:= $(patsubst %.o,%.stub.o,$(lib-y))
-
 STUBCOPY_FLAGS-$(CONFIG_ARM64)	+= --prefix-alloc-sections=.init \
 				   --prefix-symbols=__efistub_
 STUBCOPY_RELOC-$(CONFIG_ARM64)	:= R_AARCH64_ABS
diff --git a/drivers/firmware/efi/libstub/Makefile.zboot b/drivers/firmware/efi/libstub/Makefile.zboot
new file mode 100644
index 000000000000..38dee29103ae
--- /dev/null
+++ b/drivers/firmware/efi/libstub/Makefile.zboot
@@ -0,0 +1,69 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+# to be include'd by arch/$(ARCH)/boot/Makefile after setting
+# EFI_ZBOOT_PAYLOAD, EFI_ZBOOT_BFD_TARGET and EFI_ZBOOT_MACH_TYPE
+
+comp-type-$(CONFIG_KERNEL_GZIP)		:= gzip
+comp-type-$(CONFIG_KERNEL_LZ4)		:= lz4
+comp-type-$(CONFIG_KERNEL_LZMA)		:= lzma
+comp-type-$(CONFIG_KERNEL_LZO)		:= lzo
+
+# in GZIP, the appended le32 carrying the uncompressed size is part of the
+# format, but in other cases, we just append it at the end for convenience,
+# causing the original tools to complain when checking image integrity.
+# So disregard it when calculating the payload size in the zimage header.
+zimage-method-y				:= $(comp-type-y)_with_size
+zimage-size-len-y			:= 4
+
+zimage-method-$(CONFIG_KERNEL_GZIP)	:= gzip
+zimage-size-len-$(CONFIG_KERNEL_GZIP)	:= 0
+
+quiet_cmd_sbsign = SBSIGN  $@
+      cmd_sbsign = sbsign --out $@ $< \
+		   --key $(CONFIG_EFI_ZBOOT_SIGNING_KEY) \
+		   --cert $(CONFIG_EFI_ZBOOT_SIGNING_CERT) \
+		   2>/dev/null
+
+$(obj)/Image.signed: $(EFI_ZBOOT_PAYLOAD) FORCE
+	$(call if_changed,sbsign)
+
+ZBOOT_PAYLOAD-y				 := $(EFI_ZBOOT_PAYLOAD)
+ZBOOT_PAYLOAD-$(CONFIG_EFI_ZBOOT_SIGNED) := $(obj)/Image.signed
+
+$(obj)/zImage: $(ZBOOT_PAYLOAD-y) FORCE
+	$(call if_changed,$(zimage-method-y))
+
+OBJCOPYFLAGS_zImage.o := -I binary -O $(EFI_ZBOOT_BFD_TARGET) \
+			 --rename-section .data=.gzdata,load,alloc,readonly,contents
+$(obj)/zImage.o: $(obj)/zImage FORCE
+	$(call if_changed,objcopy)
+
+AFLAGS_zboot-header.o += -DMACHINE_TYPE=IMAGE_FILE_MACHINE_$(EFI_ZBOOT_MACH_TYPE) \
+			 -DZIMG_EFI_PATH="\"$(realpath $(obj)/zImage.efi.elf)\"" \
+			 -DZIMG_SIZE_LEN=$(zimage-size-len-y) \
+			 -DCOMP_TYPE="\"$(comp-type-y)\""
+
+$(obj)/zboot-header.o: $(srctree)/drivers/firmware/efi/libstub/zboot-header.S FORCE
+	$(call if_changed_rule,as_o_S)
+
+ZBOOT_DEPS := $(obj)/zboot-header.o $(objtree)/drivers/firmware/efi/libstub/lib.a
+
+LDFLAGS_zImage.efi.elf := -T $(srctree)/drivers/firmware/efi/libstub/zboot.lds
+$(obj)/zImage.efi.elf: $(obj)/zImage.o $(ZBOOT_DEPS) FORCE
+	$(call if_changed,ld)
+
+ZIMAGE_EFI-y				:= zImage.efi
+ZIMAGE_EFI-$(CONFIG_EFI_ZBOOT_SIGNED)	:= zImage.efi.unsigned
+
+OBJCOPYFLAGS_$(ZIMAGE_EFI-y) := -O binary
+$(obj)/$(ZIMAGE_EFI-y): $(obj)/zImage.efi.elf FORCE
+	$(call if_changed,objcopy)
+
+targets += zboot-header.o zImage zImage.o zImage.efi.elf zImage.efi
+
+ifneq ($(CONFIG_EFI_ZBOOT_SIGNED),)
+$(obj)/zImage.efi: $(obj)/zImage.efi.unsigned FORCE
+	$(call if_changed,sbsign)
+
+targets += Image.signed zImage.efi.unsigned
+endif
diff --git a/drivers/firmware/efi/libstub/zboot-header.S b/drivers/firmware/efi/libstub/zboot-header.S
new file mode 100644
index 000000000000..ee6a3cd69773
--- /dev/null
+++ b/drivers/firmware/efi/libstub/zboot-header.S
@@ -0,0 +1,139 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/pe.h>
+
+#ifdef CONFIG_64BIT
+	.set		.Lextra_characteristics, 0x0
+	.set		.Lpe_opt_magic, PE_OPT_MAGIC_PE32PLUS
+#else
+	.set		.Lextra_characteristics, IMAGE_FILE_32BIT_MACHINE
+	.set		.Lpe_opt_magic, PE_OPT_MAGIC_PE32
+#endif
+
+	.section	".head", "a"
+	.globl		__efistub_efi_zboot_header
+__efistub_efi_zboot_header:
+.Ldoshdr:
+	.long		MZ_MAGIC
+	.ascii		"zimg"					// image type
+	.long		__efistub__gzdata_start - .Ldoshdr	// payload offset
+	.long		__efistub__gzdata_size - ZIMG_SIZE_LEN	// payload size
+	.long		0, 0					// reserved
+	.asciz		COMP_TYPE				// compression type
+	.org		.Ldoshdr + 0x3c
+	.long		.Lpehdr - .Ldoshdr			// PE header offset
+
+.Lpehdr:
+	.long		PE_MAGIC
+	.short		MACHINE_TYPE
+	.short		.Lsection_count
+	.long		0
+	.long		0
+	.long		0
+	.short		.Lsection_table - .Loptional_header
+	.short		IMAGE_FILE_DEBUG_STRIPPED | \
+			IMAGE_FILE_EXECUTABLE_IMAGE | \
+			IMAGE_FILE_LINE_NUMS_STRIPPED |\
+			.Lextra_characteristics
+
+.Loptional_header:
+	.short		.Lpe_opt_magic
+	.byte		0, 0
+	.long		_etext - .Lefi_header_end
+	.long		__data_size
+	.long		0
+	.long		__efistub_efi_zboot_entry - .Ldoshdr
+	.long		.Lefi_header_end - .Ldoshdr
+
+#ifdef CONFIG_64BIT
+	.quad		0
+#else
+	.long		_etext - .Ldoshdr, 0x0
+#endif
+	.long		4096
+	.long		512
+	.short		0, 0
+	.short		LINUX_EFISTUB_MAJOR_VERSION	// MajorImageVersion
+	.short		LINUX_EFISTUB_MINOR_VERSION	// MinorImageVersion
+	.short		0, 0
+	.long		0
+	.long		_end - .Ldoshdr
+
+	.long		.Lefi_header_end - .Ldoshdr
+	.long		0
+	.short		IMAGE_SUBSYSTEM_EFI_APPLICATION
+	.short		0
+	.quad		0, 0, 0, 0
+	.long		0
+	.long		(.Lsection_table - .) / 8
+
+	.quad		0				// ExportTable
+	.quad		0				// ImportTable
+	.quad		0				// ResourceTable
+	.quad		0				// ExceptionTable
+	.quad		0				// CertificationTable
+	.quad		0				// BaseRelocationTable
+#ifdef CONFIG_DEBUG_EFI
+	.long		.Lefi_debug_table - .Ldoshdr	// DebugTable
+	.long		.Lefi_debug_table_size
+#endif
+
+.Lsection_table:
+	.ascii		".text\0\0\0"
+	.long		_etext - .Lefi_header_end
+	.long		.Lefi_header_end - .Ldoshdr
+	.long		_etext - .Lefi_header_end
+	.long		.Lefi_header_end - .Ldoshdr
+
+	.long		0, 0
+	.short		0, 0
+	.long		IMAGE_SCN_CNT_CODE | \
+			IMAGE_SCN_MEM_READ | \
+			IMAGE_SCN_MEM_EXECUTE
+
+	.ascii		".data\0\0\0"
+	.long		__data_size
+	.long		_etext - .Ldoshdr
+	.long		__data_rawsize
+	.long		_etext - .Ldoshdr
+
+	.long		0, 0
+	.short		0, 0
+	.long		IMAGE_SCN_CNT_INITIALIZED_DATA | \
+			IMAGE_SCN_MEM_READ | \
+			IMAGE_SCN_MEM_WRITE
+
+	.set		.Lsection_count, (. - .Lsection_table) / 40
+
+#ifdef CONFIG_DEBUG_EFI
+	.section	".rodata", "a"
+	.align		2
+.Lefi_debug_table:
+	// EFI_IMAGE_DEBUG_DIRECTORY_ENTRY
+	.long		0				// Characteristics
+	.long		0				// TimeDateStamp
+	.short		0				// MajorVersion
+	.short		0				// MinorVersion
+	.long		IMAGE_DEBUG_TYPE_CODEVIEW	// Type
+	.long		.Lefi_debug_entry_size		// SizeOfData
+	.long		0				// RVA
+	.long		.Lefi_debug_entry - .Ldoshdr	// FileOffset
+
+	.set		.Lefi_debug_table_size, . - .Lefi_debug_table
+	.previous
+
+.Lefi_debug_entry:
+	// EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY
+	.ascii		"NB10"				// Signature
+	.long		0				// Unknown
+	.long		0				// Unknown2
+	.long		0				// Unknown3
+
+	.asciz		ZIMG_EFI_PATH
+
+	.set		.Lefi_debug_entry_size, . - .Lefi_debug_entry
+#endif
+
+	.p2align	12
+.Lefi_header_end:
+
diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c
new file mode 100644
index 000000000000..9cf968e90775
--- /dev/null
+++ b/drivers/firmware/efi/libstub/zboot.c
@@ -0,0 +1,101 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/efi.h>
+#include <linux/pe.h>
+#include <asm/efi.h>
+
+#include "efistub.h"
+
+static unsigned char zboot_heap[SZ_64K] __aligned(64);
+static unsigned long free_mem_ptr, free_mem_end_ptr;
+
+#define STATIC static
+#if defined(CONFIG_KERNEL_GZIP)
+#include "../../../../lib/decompress_inflate.c"
+#elif defined(CONFIG_KERNEL_LZ4)
+#include "../../../../lib/decompress_unlz4.c"
+#elif defined(CONFIG_KERNEL_LZMA)
+#include "../../../../lib/decompress_unlzma.c"
+#elif defined(CONFIG_KERNEL_LZO)
+#include "../../../../lib/decompress_unlzo.c"
+#endif
+
+extern char _gzdata_start[], _gzdata_end[];
+extern u32 uncompressed_size __aligned(1);
+
+static void log(efi_char16_t str[])
+{
+	efi_call_proto(efi_table_attr(efi_system_table, con_out),
+		       output_string, L"EFI decompressor: ");
+	efi_call_proto(efi_table_attr(efi_system_table, con_out),
+		       output_string, str);
+}
+
+static void error(char *x)
+{
+	log(L"error() called from decompressor library\n");
+}
+
+efi_status_t __efiapi efi_zboot_entry(efi_handle_t handle,
+				      efi_system_table_t *systab)
+{
+	static efi_guid_t loaded_image = LOADED_IMAGE_PROTOCOL_GUID;
+	efi_loaded_image_t *parent, *child;
+	unsigned long image_buffer;
+	efi_handle_t child_handle;
+	efi_status_t status;
+	int ret;
+
+	WRITE_ONCE(efi_system_table, systab);
+
+	free_mem_ptr = (unsigned long)&zboot_heap;
+	free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
+
+	status = efi_bs_call(handle_protocol, handle, &loaded_image,
+			     (void **)&parent);
+	if (status != EFI_SUCCESS) {
+		log(L"Failed to locate parent's loaded image protocol\n");
+		return status;
+	}
+
+	status = efi_allocate_pages(uncompressed_size, &image_buffer, ULONG_MAX);
+	if (status != EFI_SUCCESS) {
+		log(L"Failed to allocate memory\n");
+		return status;
+	}
+
+	ret = __decompress(_gzdata_start, _gzdata_end - _gzdata_start, NULL,
+			   NULL, (unsigned char *)image_buffer, 0, NULL,
+			   error);
+	if (ret	< 0) {
+		log(L"Decompression failed\n");
+		return EFI_LOAD_ERROR;
+	}
+
+	status = efi_bs_call(load_image, false, handle, NULL,
+			     (void *)image_buffer, uncompressed_size,
+			     &child_handle);
+	if (status != EFI_SUCCESS) {
+		log(L"Failed to load image\n");
+		return status;
+	}
+
+	status = efi_bs_call(handle_protocol, child_handle, &loaded_image,
+			     (void **)&child);
+	if (status != EFI_SUCCESS) {
+		log(L"Failed to locate child's loaded image protocol\n");
+		return status;
+	}
+
+	// Copy the kernel command line
+	child->load_options = parent->load_options;
+	child->load_options_size = parent->load_options_size;
+
+	status = efi_bs_call(start_image, child_handle, NULL, NULL);
+	if (status != EFI_SUCCESS) {
+		log(L"StartImage() returned with error\n");
+		return status;
+	}
+
+	return EFI_SUCCESS;
+}
diff --git a/drivers/firmware/efi/libstub/zboot.lds b/drivers/firmware/efi/libstub/zboot.lds
new file mode 100644
index 000000000000..d6ba89a0c294
--- /dev/null
+++ b/drivers/firmware/efi/libstub/zboot.lds
@@ -0,0 +1,39 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+
+ENTRY(__efistub_efi_zboot_header);
+
+SECTIONS
+{
+	.text : ALIGN(4096) {
+		*(.head)
+		*(.text* .init.text*)
+	}
+
+	.rodata : ALIGN(8) {
+		__efistub__gzdata_start = .;
+		*(.gzdata)
+		__efistub__gzdata_end = .;
+		*(.rodata* .init.rodata* .srodata*)
+		_etext = ALIGN(4096);
+		. = _etext;
+	}
+
+	.data : ALIGN(4096) {
+		*(.data* .init.data*)
+		_edata = ALIGN(512);
+		. = _edata;
+	}
+
+	.bss : {
+		*(.bss* .init.bss*)
+		_end = ALIGN(512);
+		. = _end;
+	}
+}
+
+PROVIDE(__efistub__gzdata_size = ABSOLUTE(. - __efistub__gzdata_start));
+
+PROVIDE(__efistub_uncompressed_size = __efistub__gzdata_end - 4);
+
+PROVIDE(__data_rawsize = ABSOLUTE(_edata - _etext));
+PROVIDE(__data_size = ABSOLUTE(_end - _etext));