From: Jan Kiszka <jan.kis...@siemens.com> This provides a simple UEFI stub which allows to form unified Linux kernel images. Besides the kernel itself, such images can consist of an initrd, a static command line and device trees. All artifacts can be combined into a single UEFI binary which can be signed and will then be validated by the UEFI firmware in secure boot mode.
The device trees come into play when the firmware-provided one is not compatible with the target kernel. The stub will match the device trees of the unified image against the firmware version and select the one that matches the compatible string. Support for loading an initrd is limited to Linux kernels 5.8 or newer. This stub is similar to what systemd provides but differs in being simpler while providing the unique device tree matching functionality. Signed-off-by: Jan Kiszka <jan.kis...@siemens.com> --- Makefile.am | 29 ++++++- linux-stub/fdt.c | 182 ++++++++++++++++++++++++++++++++++++++++ linux-stub/initrd.c | 103 +++++++++++++++++++++++ linux-stub/linux-stub.h | 23 +++++ linux-stub/main.c | 170 +++++++++++++++++++++++++++++++++++++ 5 files changed, 505 insertions(+), 2 deletions(-) create mode 100644 linux-stub/fdt.c create mode 100644 linux-stub/initrd.c create mode 100644 linux-stub/linux-stub.h create mode 100644 linux-stub/main.c diff --git a/Makefile.am b/Makefile.am index 66331ec..d664775 100644 --- a/Makefile.am +++ b/Makefile.am @@ -168,6 +168,13 @@ efi_sources = \ bootguard.c \ main.c +linux_stub_name = linux-stub$(MACHINE_TYPE_NAME).efi + +linux_stub_sources = \ + linux-stub/fdt.c \ + linux-stub/initrd.c \ + linux-stub/main.c + efi_cppflags = \ -I$(top_builddir) -include config.h \ -I$(top_srcdir)/include \ @@ -224,10 +231,15 @@ efi_objects_pre2 = $(efi_objects_pre1:.S=.o) efi_objects = $(addprefix $(top_builddir)/,$(efi_objects_pre2)) efi_solib = $(top_builddir)/efibootguard$(MACHINE_TYPE_NAME).so +linux_stub_objects_pre = $(linux_stub_sources:.c=.o) +linux_stub_objects = $(addprefix $(top_builddir)/,$(linux_stub_objects_pre)) +linux_stub_solib = $(top_builddir)/linux-stub/linux-stub$(MACHINE_TYPE_NAME).so + # automake stuff -efibootguard_DATA = $(efi_loadername) +efibootguard_DATA = $(efi_loadername) $(linux_stub_name) CLEANFILES += $(efi_objects) $(efi_solib) $(efi_loadername) -EXTRA_DIST += $(efi_sources) +CLEANFILES += $(linux_stub_objects) $(linux_stub_solib) $(linux_stub_name) +EXTRA_DIST += $(efi_sources) $(linux_stub_sources) define gnuefi_compile $(AM_V_CC) $(MKDIR_P) $(shell dirname $@)/; \ @@ -246,6 +258,9 @@ $(top_builddir)/drivers/watchdog/%.o: $(top_srcdir)/drivers/watchdog/%.c $(top_builddir)/drivers/watchdog/%.o: $(top_srcdir)/drivers/watchdog/%.S $(call gnuefi_compile) +$(top_builddir)/linux-stub/%.o: $(top_srcdir)/linux-stub/%.c + $(call gnuefi_compile) + $(top_builddir)/main.o: $(GEN_VERSION_H) $(efi_solib): $(efi_objects) @@ -257,6 +272,16 @@ $(efi_loadername): $(efi_solib) $(AM_V_GEN) $(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ -j .dynsym -j .rel -j .rela -j .reloc -j .init_array \ -j .rela.got -j .rela.data $(objcopy_format) $< $@ + +$(linux_stub_solib): $(linux_stub_objects) + $(AM_V_CCLD)$(LD) $(efi_ldflags) $(linux_stub_objects) \ + -o $@ -lefi -lgnuefi $(shell $(CC) $(CFLAGS) -print-libgcc-file-name); \ + nm -D -u $@ | grep ' U ' && exit 1 || : + +$(linux_stub_name): $(linux_stub_solib) + $(AM_V_GEN) $(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ + -j .dynsym -j .rel -j .rela -j .reloc -j .rela.got -j .rela.data \ + $(objcopy_format) $< $@ endif $(top_builddir)/tools/bg_setenv-bg_setenv.o: $(GEN_VERSION_H) diff --git a/linux-stub/fdt.c b/linux-stub/fdt.c new file mode 100644 index 0000000..41758e1 --- /dev/null +++ b/linux-stub/fdt.c @@ -0,0 +1,182 @@ +/* + * EFI Boot Guard, unified Linux stub + * + * Copyright (c) Siemens AG, 2022 + * + * Authors: + * Jan Kiszka <jan.kis...@siemens.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include <efi.h> +#include <efilib.h> +#include <byteswap.h> +#include <bits/endian.h> + +#include "linux-stub.h" + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define BE32_TO_HOST(val) bswap_32(val) +#else +#define BE32_TO_HOST(val) (val) +#endif + +#define FDT_BEGIN_NODE 0x1 +#define FDT_END_NODE 0x2 +#define FDT_PROP 0x3 +#define FDT_NOP 0x4 + +typedef struct { + UINT32 Magic; + UINT32 TotalSize; + UINT32 OffDtStruct; + UINT32 OffDtStrings; + UINT32 OffMemRsvmap; + UINT32 Version; + UINT32 LastCompVersion; + UINT32 BootCpuidPhys; + UINT32 SizeDtStrings; + UINT32 SizeDtStruct; +} FDT_HEADER; + +#ifndef EfiDtbTableGuid +static EFI_GUID gEfiDtbTableGuid = { + 0xb1b621d5, 0xf19c, 0x41a5, + {0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0} +}; +#define EfiDtbTableGuid gEfiDtbTableGuid +#endif + +#ifndef EfiDtFixupProtocol +static EFI_GUID gEfiDtFixupProtocol = { + 0xe617d64c, 0xfe08, 0x46da, + {0xf4, 0xdc, 0xbb, 0xd5, 0x87, 0x0c, 0x73, 0x00} +}; +#define EfiDtFixupProtocol gEfiDtFixupProtocol + +#define EFI_DT_APPLY_FIXUPS 0x00000001 +#define EFI_DT_RESERVE_MEMORY 0x00000002 +#define EFI_DT_INSTALL_TABLE 0x00000004 + +typedef struct _EFI_DT_FIXUP_PROTOCOL EFI_DT_FIXUP_PROTOCOL; + +typedef EFI_STATUS (EFIAPI *EFI_DT_FIXUP)(EFI_DT_FIXUP_PROTOCOL *This, + VOID *Fdt, UINTN *BufferSize, + UINT32 Flags); + +struct _EFI_DT_FIXUP_PROTOCOL { + UINT64 Revision; + EFI_DT_FIXUP Fixup; +}; +#endif + +static const VOID *get_compatible(const VOID *fdt) +{ + const FDT_HEADER *header = fdt; + const CHAR8 *strings; + const UINT32 *pos; + UINT32 len; + + if (BE32_TO_HOST(header->Magic) != 0xd00dfeed) { + return NULL; + } + + pos = fdt + BE32_TO_HOST(header->OffDtStruct); + if (BE32_TO_HOST(*pos++) != FDT_BEGIN_NODE || *pos++ != 0) { + return NULL; + } + + strings = (const CHAR8 *) fdt + BE32_TO_HOST(header->OffDtStrings); + + while (1) { + switch (BE32_TO_HOST(*pos++)) { + case FDT_PROP: + len = BE32_TO_HOST(*pos++); + if (strcmpa(strings + BE32_TO_HOST(*pos++), + (const CHAR8 *) "compatible") == 0) { + return pos; + } + pos += (len + 3) / 4; + break; + case FDT_NOP: + break; + default: + return NULL; + } + } +} + +const VOID *get_fdt_compatible(VOID) +{ + const CHAR8 *compatible = NULL; + EFI_STATUS status; + VOID *fdt; + + status = LibGetSystemConfigurationTable(&EfiDtbTableGuid, &fdt); + if (status == EFI_SUCCESS) { + compatible = get_compatible(fdt); + if (!compatible) { + error_exit(L"Invalid firmware FDT", + EFI_INVALID_PARAMETER); + } + } + + return compatible; +} + +BOOLEAN match_fdt(const VOID *fdt, const CHAR8 *compatible) +{ + const CHAR8 *alt_compatible; + + if (!compatible) { + error_exit(L"Found .dtb section but no firmware DTB\n", + EFI_NOT_FOUND); + } + + alt_compatible = get_compatible(fdt); + if (!alt_compatible) { + error_exit(L"Invalid .dtb section", EFI_INVALID_PARAMETER); + } + + return strcmpa(compatible, alt_compatible) == 0; +} + +VOID replace_fdt(const VOID *fdt) +{ + const FDT_HEADER *header = fdt; + EFI_DT_FIXUP_PROTOCOL *protocol; + EFI_STATUS status; + VOID *fdt_buffer; + UINTN size; + + status = LibLocateProtocol(&EfiDtFixupProtocol, (VOID **)&protocol); + if (EFI_ERROR(status)) { + error_exit(L"Did not find device tree fixup protocol", status); + } + + /* Find out which size we need */ + size = 0; + status = protocol->Fixup(protocol, (VOID *)fdt, &size, + EFI_DT_APPLY_FIXUPS); + if (status != EFI_BUFFER_TOO_SMALL) { + error_exit(L"Device tree fixup: unexpected error", status); + } + + fdt_buffer = AllocatePool(size); + if (!fdt_buffer) { + error_exit(L"Error allocating device tree buffer", + EFI_OUT_OF_RESOURCES); + } + + CopyMem(fdt_buffer, fdt, BE32_TO_HOST(header->TotalSize)); + status = protocol->Fixup(protocol, fdt_buffer, &size, + EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY | + EFI_DT_INSTALL_TABLE); + if (EFI_ERROR(status)) { + error_exit(L"Device tree fixup failed", status); + } +} diff --git a/linux-stub/initrd.c b/linux-stub/initrd.c new file mode 100644 index 0000000..275fa85 --- /dev/null +++ b/linux-stub/initrd.c @@ -0,0 +1,103 @@ +/* + * EFI Boot Guard, unified Linux stub + * + * Copyright (c) Siemens AG, 2022 + * + * Authors: + * Jan Kiszka <jan.kis...@siemens.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include <efi.h> +#include <efilib.h> + +#include "linux-stub.h" + +typedef struct { + VENDOR_DEVICE_PATH vendor; + EFI_DEVICE_PATH end; +} __attribute__((packed)) INITRD_DEVICE_PATH; + +#define LINUX_INITRD_MEDIA_GUID \ + {0x5568e427, 0x68fc, 0x4f3d, \ + {0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68}} + +typedef struct { + EFI_LOAD_FILE_PROTOCOL protocol; + const void *addr; + UINTN size; +} INITRD_LOADER; + +#ifndef EfiLoadFile2Protocol +static const EFI_GUID gEfiLoadFile2Protocol = { + 0x4006c0c1, 0xfcb3, 0x403e, + {0x99, 0x6d, 0x4a, 0x6c, 0x87, 0x24, 0xe0, 0x6d} +}; +#define EfiLoadFile2Protocol gEfiLoadFile2Protocol +#endif + +static const INITRD_DEVICE_PATH initrd_device_path = { + .vendor = { + .Header.Type = MEDIA_DEVICE_PATH, + .Header.SubType = MEDIA_VENDOR_DP, + .Header.Length = { + sizeof(initrd_device_path.vendor), + 0, + }, + .Guid = LINUX_INITRD_MEDIA_GUID, + }, + .end.Type = END_DEVICE_PATH_TYPE, + .end.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE, + .end.Length = { + sizeof(initrd_device_path.end), + 0, + }, +}; +static INITRD_LOADER initrd_loader; + +static EFIAPI EFI_STATUS initrd_load_file(EFI_LOAD_FILE_PROTOCOL *this, + EFI_DEVICE_PATH *file_path, + BOOLEAN boot_policy, + UINTN *buffer_size, + VOID *buffer) +{ + INITRD_LOADER *loader = (INITRD_LOADER *) this; + + if (!loader || !file_path || !buffer_size) { + return EFI_INVALID_PARAMETER; + } + if (boot_policy) { + return EFI_UNSUPPORTED; + } + if (!buffer || *buffer_size < loader->size) { + *buffer_size = loader->size; + return EFI_BUFFER_TOO_SMALL; + } + + CopyMem(buffer, loader->addr, loader->size); + *buffer_size = loader->size; + + return EFI_SUCCESS; +} + +VOID install_initrd_loader(VOID *initrd, UINTN initrd_size) +{ + EFI_HANDLE initrd_handle = NULL; + EFI_STATUS status; + + initrd_loader.protocol.LoadFile = initrd_load_file; + initrd_loader.addr = initrd; + initrd_loader.size = initrd_size; + + status = BS->InstallMultipleProtocolInterfaces( + &initrd_handle, &DevicePathProtocol, + &initrd_device_path, &EfiLoadFile2Protocol, + &initrd_loader, NULL); + if (EFI_ERROR(status)) { + error_exit(L"Error registering initrd loader", status); + } +} diff --git a/linux-stub/linux-stub.h b/linux-stub/linux-stub.h new file mode 100644 index 0000000..02b5283 --- /dev/null +++ b/linux-stub/linux-stub.h @@ -0,0 +1,23 @@ +/* + * EFI Boot Guard, unified Linux stub + * + * Copyright (c) Siemens AG, 2022 + * + * Authors: + * Jan Kiszka <jan.kis...@siemens.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include <efi.h> + +VOID __attribute__((noreturn)) error_exit(CHAR16 *message, EFI_STATUS status); + +const VOID *get_fdt_compatible(VOID); +BOOLEAN match_fdt(const VOID *fdt, const CHAR8 *compatible); +VOID replace_fdt(const VOID *fdt); + +VOID install_initrd_loader(VOID *initrd, UINTN initrd_size); diff --git a/linux-stub/main.c b/linux-stub/main.c new file mode 100644 index 0000000..4a8ffeb --- /dev/null +++ b/linux-stub/main.c @@ -0,0 +1,170 @@ +/* + * EFI Boot Guard, unified Linux stub + * + * Copyright (c) Siemens AG, 2022 + * + * Authors: + * Jan Kiszka <jan.kis...@siemens.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include <efi.h> +#include <efilib.h> + +#include "linux-stub.h" +#include "version.h" + +typedef struct { + UINT8 Ignore[60]; + UINT32 PEOffset; +} __attribute__((packed)) DOS_HEADER; + +typedef struct { + UINT8 Ignore1[2]; + UINT16 NumberOfSections; + UINT8 Ignore2[12]; + UINT16 SizeOfOptionalHeader; + UINT8 Ignore3[2]; +} __attribute__((packed)) COFF_HEADER; + +typedef struct { + UINT8 Ignore1[16]; + UINT32 AddressOfEntryPoint; + UINT8 Ignore2[220]; +} __attribute__((packed)) OPT_HEADER; + +typedef struct { + UINT32 Signature; + COFF_HEADER Coff; + OPT_HEADER Opt; +} __attribute__((packed)) PE_HEADER; + +typedef struct { + CHAR8 Name[8]; + UINT32 VirtualSize; + UINT32 VirtualAddress; + UINT8 Ignore[24]; +} __attribute__((packed)) SECTION; + +static EFI_HANDLE this_image; +static EFI_LOADED_IMAGE kernel_image; + +VOID __attribute__((noreturn)) error_exit(CHAR16 *message, EFI_STATUS status) +{ + Print(L"Linux stub: %s (%r).\n", message, status); + (VOID) BS->Stall(3 * 1000 * 1000); + (VOID) BS->Exit(this_image, status, 0, NULL); + __builtin_unreachable(); +} + +static const PE_HEADER *get_pe_header(const VOID *image) +{ + const DOS_HEADER *dos_header = image; + + return (const PE_HEADER *) (image + dos_header->PEOffset); +} + +static const SECTION *get_sections(const PE_HEADER *pe_header) +{ + return (const SECTION *) ((const UINT8 *)&pe_header->Opt + + pe_header->Coff.SizeOfOptionalHeader); +} + +EFI_STATUS efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table) +{ + const SECTION *cmdline_section = NULL; + const SECTION *kernel_section = NULL; + const SECTION *initrd_section = NULL; + EFI_HANDLE kernel_handle = NULL; + BOOLEAN has_dtbs = FALSE; + const CHAR8 *compatible; + VOID *fdt, *alt_fdt = NULL; + EFI_IMAGE_ENTRY_POINT kernel_entry; + EFI_LOADED_IMAGE *stub_image; + const PE_HEADER *pe_header; + const SECTION *section; + EFI_STATUS status; + UINTN n; + + this_image = image_handle; + InitializeLib(image_handle, system_table); + + Print(L"Linux stub (EFI Boot Guard %s)\n", L"" EFIBOOTGUARD_VERSION); + + compatible = get_fdt_compatible(); + + status = BS->OpenProtocol(image_handle, &LoadedImageProtocol, + (void **)&stub_image, image_handle, + NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(status)) { + error_exit(L"Error getting LoadedImageProtocol", status); + } + + pe_header = get_pe_header(stub_image->ImageBase); + section = get_sections(pe_header); + for (n = 0, section = get_sections(pe_header); + n < pe_header->Coff.NumberOfSections; + n++, section++) { + if (CompareMem(section->Name, ".cmdline", 8) == 0) { + cmdline_section = section; + } else if (CompareMem(section->Name, ".kernel", 8) == 0) { + kernel_section = section; + } else if (CompareMem(section->Name, ".initrd", 8) == 0) { + initrd_section = section; + } else if (CompareMem(section->Name, ".dtb-", 5) == 0) { + has_dtbs = TRUE; + fdt = stub_image->ImageBase + section->VirtualAddress; + if (match_fdt(fdt, compatible)) { + alt_fdt = fdt; + } + } + } + + if (!kernel_section) { + error_exit(L"Missing .kernel section", EFI_NOT_FOUND); + } + + kernel_image.ImageBase = (UINT8 *) stub_image->ImageBase + + kernel_section->VirtualAddress; + kernel_image.ImageSize = kernel_section->VirtualSize; + + if (cmdline_section) { + kernel_image.LoadOptions = (UINT8 *) stub_image->ImageBase + + cmdline_section->VirtualAddress; + kernel_image.LoadOptionsSize = cmdline_section->VirtualSize; + } + + if (initrd_section) { + install_initrd_loader( + (UINT8 *) stub_image->ImageBase + + initrd_section->VirtualAddress, + initrd_section->VirtualSize); + } + + status = BS->InstallMultipleProtocolInterfaces( + &kernel_handle, &LoadedImageProtocol, &kernel_image, + NULL); + if (EFI_ERROR(status)) { + error_exit(L"Error registering kernel image", status); + } + + if (alt_fdt) { + replace_fdt(alt_fdt); + Print(L"Linux stub: Using matched embedded device tree\n"); + } else { + if (has_dtbs) { + Print(L"Linux stub: WARNING: No embedded device tree matched firmware-provided one\n"); + } + Print(L"Linux stub: Using firmware-provided device tree\n"); + } + + pe_header = get_pe_header(kernel_image.ImageBase); + kernel_entry = (EFI_IMAGE_ENTRY_POINT) + (kernel_image.ImageBase + pe_header->Opt.AddressOfEntryPoint); + + return kernel_entry(kernel_handle, system_table); +} -- 2.34.1 -- You received this message because you are subscribed to the Google Groups "EFI Boot Guard" group. To unsubscribe from this group and stop receiving emails from it, send an email to efibootguard-dev+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/efibootguard-dev/955db5f936edc14d317fa35181de2b0ac0accf82.1649070513.git.jan.kiszka%40siemens.com.