Commit a0509109 authored by Ard Biesheuvel's avatar Ard Biesheuvel
Browse files

efi/libstub: implement generic EFI zboot



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.

Signed-off-by: default avatarArd Biesheuvel <ardb@kernel.org>
parent 514377d8
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -105,6 +105,47 @@ 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
	select HAVE_KERNEL_XZ
	select HAVE_KERNEL_ZSTD
	help
	  Create the bootable image as an EFI application that carries the
	  actual kernel image in compressed form, and decompresses it into
	  memory before executing it via LoadImage/StartImage EFI boot service
	  calls. For compatibility with non-EFI loaders, the payload can be
	  decompressed and executed by the loader as well, provided that the
	  loader implements the decompression algorithm and that non-EFI boot
	  is supported by the encapsulated image. (The compression algorithm
	  used is described in the zboot image header)

config EFI_ZBOOT_SIGNED
	def_bool y
	depends on EFI_ZBOOT_SIGNING_CERT != ""
	depends on EFI_ZBOOT_SIGNING_KEY != ""

config EFI_ZBOOT_SIGNING
	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_SIGNING

config EFI_ZBOOT_SIGNING_KEY
	string "Private key to use for signing the compressed EFI boot image"
	depends on EFI_ZBOOT_SIGNING

config EFI_ARMSTUB_DTB_LOADER
	bool "Enable the DTB loader"
	depends on EFI_GENERIC_STUB && !RISCV && !LOONGARCH
+6 −3
Original line number Diff line number Diff line
@@ -77,6 +77,12 @@ lib-$(CONFIG_LOONGARCH) += loongarch-stub.o

CFLAGS_arm32-stub.o		:= -DTEXT_OFFSET=$(TEXT_OFFSET)

zboot-obj-$(CONFIG_RISCV)	:= lib-clz_ctz.o lib-ashldi3.o
lib-$(CONFIG_EFI_ZBOOT)		+= zboot.o $(zboot-obj-y)

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.
@@ -116,9 +122,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
+70 −0
Original line number Diff line number Diff line
# 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
comp-type-$(CONFIG_KERNEL_XZ)		:= xzkern
comp-type-$(CONFIG_KERNEL_ZSTD)		:= zstd22

# 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.
zboot-method-y				:= $(comp-type-y)_with_size
zboot-size-len-y			:= 4

zboot-method-$(CONFIG_KERNEL_GZIP)	:= gzip
zboot-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)

$(obj)/$(EFI_ZBOOT_PAYLOAD).signed: $(obj)/$(EFI_ZBOOT_PAYLOAD) FORCE
	$(call if_changed,sbsign)

ZBOOT_PAYLOAD-y				 := $(EFI_ZBOOT_PAYLOAD)
ZBOOT_PAYLOAD-$(CONFIG_EFI_ZBOOT_SIGNED) := $(EFI_ZBOOT_PAYLOAD).signed

$(obj)/vmlinuz: $(obj)/$(ZBOOT_PAYLOAD-y) FORCE
	$(call if_changed,$(zboot-method-y))

OBJCOPYFLAGS_vmlinuz.o := -I binary -O $(EFI_ZBOOT_BFD_TARGET) \
			 --rename-section .data=.gzdata,load,alloc,readonly,contents
$(obj)/vmlinuz.o: $(obj)/vmlinuz FORCE
	$(call if_changed,objcopy)

AFLAGS_zboot-header.o += -DMACHINE_TYPE=IMAGE_FILE_MACHINE_$(EFI_ZBOOT_MACH_TYPE) \
			 -DZBOOT_EFI_PATH="\"$(realpath $(obj)/vmlinuz.efi.elf)\"" \
			 -DZBOOT_SIZE_LEN=$(zboot-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_vmlinuz.efi.elf := -T $(srctree)/drivers/firmware/efi/libstub/zboot.lds
$(obj)/vmlinuz.efi.elf: $(obj)/vmlinuz.o $(ZBOOT_DEPS) FORCE
	$(call if_changed,ld)

ZBOOT_EFI-y				:= vmlinuz.efi
ZBOOT_EFI-$(CONFIG_EFI_ZBOOT_SIGNED)	:= vmlinuz.efi.unsigned

OBJCOPYFLAGS_$(ZBOOT_EFI-y) := -O binary
$(obj)/$(ZBOOT_EFI-y): $(obj)/vmlinuz.efi.elf FORCE
	$(call if_changed,objcopy)

targets += zboot-header.o vmlinuz vmlinuz.o vmlinuz.efi.elf vmlinuz.efi

ifneq ($(CONFIG_EFI_ZBOOT_SIGNED),)
$(obj)/vmlinuz.efi: $(obj)/vmlinuz.efi.unsigned FORCE
	$(call if_changed,sbsign)
endif

targets += $(EFI_ZBOOT_PAYLOAD).signed vmlinuz.efi.unsigned
+18 −0
Original line number Diff line number Diff line
@@ -66,10 +66,28 @@ static efi_status_t efi_open_file(efi_file_protocol_t *volume,
static efi_status_t efi_open_volume(efi_loaded_image_t *image,
				    efi_file_protocol_t **fh)
{
	struct efi_vendor_dev_path *dp = image->file_path;
	efi_guid_t li_proto = LOADED_IMAGE_PROTOCOL_GUID;
	efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
	efi_simple_file_system_protocol_t *io;
	efi_status_t status;

	// If we are using EFI zboot, we should look for the file system
	// protocol on the parent image's handle instead
	if (IS_ENABLED(CONFIG_EFI_ZBOOT) &&
	    image->parent_handle != NULL &&
	    dp != NULL &&
	    dp->header.type == EFI_DEV_MEDIA &&
	    dp->header.sub_type == EFI_DEV_MEDIA_VENDOR &&
	    !efi_guidcmp(dp->vendorguid, LINUX_EFI_ZBOOT_MEDIA_GUID)) {
		status = efi_bs_call(handle_protocol, image->parent_handle,
				     &li_proto, (void *)&image);
		if (status != EFI_SUCCESS) {
			efi_err("Failed to locate parent image handle\n");
			return status;
		}
	}

	status = efi_bs_call(handle_protocol, image->device_handle, &fs_proto,
			     (void **)&io);
	if (status != EFI_SUCCESS) {
+143 −0
Original line number Diff line number Diff line
/* 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 - ZBOOT_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
#ifdef CONFIG_64BIT
	.quad		0, 0, 0, 0
#else
	.long		0, 0, 0, 0
#endif
	.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		ZBOOT_EFI_PATH

	.set		.Lefi_debug_entry_size, . - .Lefi_debug_entry
#endif

	.p2align	12
.Lefi_header_end:
Loading