[PATCH 0/2] Add stub for unified Linux images

4 views
Skip to first unread message

Jan Kiszka

unread,
Apr 4, 2022, 7:08:46 AM4/4/22
to efibootg...@googlegroups.com, Christian Storm
Add a stub and generator script to build inified Linux images that
contain kernel, command line, initrd and device trees into a single UEFI
executable. This is an important building block for secure boot under
UEFI.

In contrast to the existing solution by systemd, this one comes with
support for multiple device trees that permits running the same image
on similar but not identical hardware platforms. Although the trend goes
towards firmware provided device tree, replacements in lock-step with
kernel updates will remains important in the foreseeable future, and
this stub accounts for it.

Futhermore, this approach here has a more user-friendly python-based
generator script which does not depend on too-new binutils or LLVM
versions and allows to simplify the Linux stub by arranging data in the
required way already during generation.

These patches have been moderately tested only, primarily on ARM64. The
next planned step is a test integration with isar-cip-core. Still,
reviews would already be welcome.

Jan

Jan Kiszka (2):
Add stub for unified Linux images
scripts: Add generator for unified Linux images

Makefile.am | 29 +++-
linux-stub/fdt.c | 182 +++++++++++++++++++++++++
linux-stub/initrd.c | 103 ++++++++++++++
linux-stub/linux-stub.h | 23 ++++
linux-stub/main.c | 170 +++++++++++++++++++++++
scripts/gen_unified_linux.py | 252 +++++++++++++++++++++++++++++++++++
6 files changed, 757 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
create mode 100755 scripts/gen_unified_linux.py

--
2.34.1

Jan Kiszka

unread,
Apr 4, 2022, 7:08:47 AM4/4/22
to efibootg...@googlegroups.com, Christian Storm
From: Jan Kiszka <jan.k...@siemens.com>

In contrast to systemd's objcopy approach, rely on a Python to generate
the unified kernel image. This allows to align the kernel payload in the
right way upfront, saving one copy step. Furthermore, it allows for a
more user-friendly interface.

Signed-off-by: Jan Kiszka <jan.k...@siemens.com>
---
scripts/gen_unified_linux.py | 252 +++++++++++++++++++++++++++++++++++
1 file changed, 252 insertions(+)
create mode 100755 scripts/gen_unified_linux.py

diff --git a/scripts/gen_unified_linux.py b/scripts/gen_unified_linux.py
new file mode 100755
index 0000000..eb7ed25
--- /dev/null
+++ b/scripts/gen_unified_linux.py
@@ -0,0 +1,252 @@
+#!/usr/bin/env python3
+#
+# EFI Boot Guard, unified Linux image generator
+#
+# Copyright (c) Siemens AG, 2022
+#
+# Authors:
+# Jan Kiszka <jan.k...@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
+
+import argparse
+import os
+import struct
+import sys
+
+def align(val, alignment):
+ return (val + alignment - 1) & ~(alignment - 1)
+
+
+class Section:
+ IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
+ IMAGE_SCN_MEM_READ = 0x40000000
+
+ def __init__(self, blob):
+ (self.name, self.virt_size, self.virt_addr, self.data_size,
+ self.data_offs, self.chars) = \
+ struct.unpack_from('<8sIIII12xI', blob)
+
+ def __init__(self, name, virt_size, virt_addr, data_size, data_offs,
+ chars):
+ self.name = name
+ self.virt_size = virt_size
+ self.virt_addr = virt_addr
+ self.data_size = data_size
+ self.data_offs = data_offs
+ self.chars = chars
+
+ @staticmethod
+ def from_struct(blob):
+ (name, virt_size, virt_addr, data_size, data_offs, chars) = \
+ struct.unpack_from('<8sIIII12xI', blob)
+ return Section(name, virt_size, virt_addr, data_size, data_offs,
+ chars)
+
+ def get_struct(self):
+ return struct.pack('<8sIIII12xI', self.name, self.virt_size,
+ self.virt_addr, self.data_size, self.data_offs,
+ self.chars)
+
+
+class PEHeaders:
+ def __init__(self, name, blob):
+ # Parse headers: DOS, COFF, optional header
+ if len(blob) < 0x40:
+ print("Invalid %s, image too small" % name, file=sys.stderr)
+ exit(1)
+
+ (magic, pe_offs) = struct.unpack_from('<H58xI', blob)
+
+ if magic != 0x5a4d:
+ print("Invalid %s, bad DOS header magic" % name, file=sys.stderr)
+ exit(1)
+
+ self.dos_header = blob[:pe_offs]
+
+ self.header_size = pe_offs + 0x18
+ if self.header_size > len(blob):
+ print("Invalid %s, incomplete COFF header" % name, file=sys.stderr)
+ exit(1)
+
+ self.coff_header = blob[pe_offs:self.header_size]
+
+ (magic, self.machine, num_sections, opt_header_size) = \
+ struct.unpack_from('<IHH12xH2x', self.coff_header)
+ if magic != 0x4550:
+ print("Invalid %s, bad PE header magic" % name, file=sys.stderr)
+ exit(1)
+
+ coff_offs = self.header_size
+
+ self.header_size += opt_header_size
+ if self.header_size > len(blob):
+ print("Invalid %s, incomplete optional header" % name,
+ file=sys.stderr)
+ exit(1)
+
+ self.opt_header = blob[coff_offs:self.header_size]
+
+ section_offs = self.header_size
+
+ self.header_size += num_sections * 0x28
+ if self.header_size > len(blob):
+ print("Invalid %s, incomplete section headers" % name,
+ file=sys.stderr)
+ exit(1)
+
+ self.first_data = len(blob)
+
+ self.sections = []
+ for n in range(num_sections):
+ section = Section.from_struct(
+ blob[section_offs:section_offs+0x28])
+ if section.data_offs + section.data_size > len(blob):
+ print("Invalid %s, section data missing" % name,
+ file=sys.stderr)
+ exit(1)
+
+ if section.data_offs < self.first_data:
+ self.first_data = section.data_offs
+
+ self.sections.append(section)
+
+ section_offs += 0x28
+
+ def get_size_of_image(self):
+ return struct.unpack_from('<56xI', self.opt_header)[0]
+
+ def get_section_alignment(self):
+ return struct.unpack_from('<32xI', self.opt_header)[0]
+
+ def set_section_alignment(self, alignment):
+ format = '<32sI%ds' % (len(self.opt_header) - 36)
+ self.opt_header = struct.pack(format, self.opt_header[:32], alignment,
+ self.opt_header[36:])
+
+ def add_section(self, section):
+ self.header_size += 0x28
+
+ # check space for adding extra sections
+ if self.first_data < self.header_size:
+ print("FIXME: section data requires relocation", file=sys.stderr)
+ exit(1)
+
+ self.sections.append(section)
+ self.coff_header = struct.pack('<6sH16s', self.coff_header[:6],
+ len(self.sections),
+ self.coff_header[8:])
+
+
+parser = argparse.ArgumentParser(description='Generate unified Linux image')
+parser.add_argument('-c', '--cmdline', metavar='"CMDLINE"', default='',
+ help='kernel command line')
+parser.add_argument('-d', '--dtb', metavar='DTB', action="append",
+ type=argparse.FileType('rb'),
+ help='device tree for the kernel ' \
+ '(can be specified multiple times)')
+parser.add_argument('-i', '--initrd', metavar='INITRD',
+ type=argparse.FileType('rb'),
+ help='initrd/initramfs for the kernel')
+parser.add_argument('stub', metavar='STUB',
+ type=argparse.FileType('rb'),
+ help='stub image to use')
+parser.add_argument('kernel', metavar='KERNEL',
+ type=argparse.FileType('rb'),
+ help='image of the kernel')
+parser.add_argument('output', metavar='UNIFIEDIMAGE',
+ type=argparse.FileType('wb'),
+ help='name of unified kernel image file')
+
+try:
+ args = parser.parse_args()
+except IOError as e:
+ print(e.strerror, file=sys.stderr)
+ exit(1)
+
+cmdline = args.cmdline.encode('utf-16-le')
+
+stub = args.stub.read()
+
+pe_headers = PEHeaders('stub image', stub)
+
+# Add extra section headers
+cmdline_offs = align(len(stub), 512)
+cmdline_size = align(len(cmdline) + 1, 512)
+section = Section(b'.cmdline', cmdline_size, 0x30000,
+ cmdline_size, cmdline_offs,
+ Section.IMAGE_SCN_CNT_INITIALIZED_DATA |
+ Section.IMAGE_SCN_MEM_READ)
+pe_headers.add_section(section)
+
+kernel = args.kernel.read()
+kernel_pe_headers = PEHeaders('kernel', kernel)
+
+kernel_offs = cmdline_offs + cmdline_size
+kernel_size = align(len(kernel), 512)
+kernel_virt_size = max(kernel_size, kernel_pe_headers.get_size_of_image())
+section = Section(b'.kernel', kernel_virt_size, 0x2000000,
+ kernel_size, kernel_offs,
+ Section.IMAGE_SCN_CNT_INITIALIZED_DATA |
+ Section.IMAGE_SCN_MEM_READ)
+pe_headers.add_section(section)
+pe_headers.set_section_alignment(kernel_pe_headers.get_section_alignment())
+
+initrd_offs = kernel_offs + kernel_size
+initrd_size = 0
+if args.initrd:
+ initrd = args.initrd.read()
+ initrd_size = align(len(initrd), 512)
+ section = Section(b'.initrd', initrd_size, 0x6000000,
+ initrd_size, initrd_offs,
+ Section.IMAGE_SCN_CNT_INITIALIZED_DATA |
+ Section.IMAGE_SCN_MEM_READ)
+ pe_headers.add_section(section)
+
+current_offs = initrd_offs + initrd_size
+dtb_virt = 0x40000
+dtb = []
+dtb_offs = []
+dtb_size = 0
+for n in range(len(args.dtb)):
+ dtb.append(args.dtb[n].read())
+ dtb_offs.append(current_offs)
+ dtb_size = align(len(dtb[n]), 512)
+ section = Section(bytes('.dtb-{}'.format(n + 1), 'ascii'),
+ dtb_size, dtb_virt, dtb_size, dtb_offs[n],
+ Section.IMAGE_SCN_CNT_INITIALIZED_DATA |
+ Section.IMAGE_SCN_MEM_READ)
+ pe_headers.add_section(section)
+ dtb_virt += dtb_size
+ current_offs += dtb_size
+
+# Build unified image header
+image = pe_headers.dos_header + pe_headers.coff_header + pe_headers.opt_header
+for section in pe_headers.sections:
+ image += section.get_struct()
+
+# Write remaining stub
+image += stub[len(image):]
+
+# Write data of extra sections
+image += bytearray(cmdline_offs - len(image))
+image += cmdline
+
+image += bytearray(kernel_offs - len(image))
+image += kernel
+
+if args.initrd:
+ image += bytearray(initrd_offs - len(image))
+ image += initrd
+
+for n in range(len(dtb)):
+ image += bytearray(dtb_offs[n] - len(image))
+ image += dtb[n]
+
+# Align to promised size of last section
+image += bytearray(align(len(image), 512) - len(image))
+
+args.output.write(image)
--
2.34.1

Jan Kiszka

unread,
Apr 4, 2022, 7:08:47 AM4/4/22
to efibootg...@googlegroups.com, Christian Storm
From: Jan Kiszka <jan.k...@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.k...@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.k...@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.k...@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.k...@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.k...@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

Jan Kiszka

unread,
Apr 7, 2022, 3:19:55 AM4/7/22
to efibootg...@googlegroups.com, Christian Storm
Changes in v2:
- fix script for more picky UEFI firmware than U-Boot (now tested also
against OVMF on x86)
- move/rename script to tools/bg_gen_unified_linux and install it
- build fixes under Debian 10
- avoid dtb-related output of stub under x86

Add a stub and generator script to build inified Linux images that
contain kernel, command line, initrd and device trees into a single UEFI
executable. This is an important building block for secure boot under
UEFI.

In contrast to the existing solution by systemd, this one comes with
support for multiple device trees that permits running the same image
on similar but not identical hardware platforms. Although the trend goes
towards firmware provided device tree, replacements in lock-step with
kernel updates will remains important in the foreseeable future, and
this stub accounts for it.

Furthermore, this approach here has a more user-friendly python-based
generator script which does not depend on too-new binutils or LLVM
versions and allows to simplify the Linux stub by arranging data in the
required way already during generation.

These patches have been moderately tested only, primarily on ARM64. The
next planned step is a test integration with isar-cip-core. Still,
reviews would already be welcome.

Jan

Jan Kiszka (2):
Add stub for unified Linux images
tools: Add generator for unified Linux images

Makefile.am | 34 ++++-
linux-stub/fdt.c | 182 ++++++++++++++++++++++++
linux-stub/initrd.c | 103 ++++++++++++++
linux-stub/linux-stub.h | 23 +++
linux-stub/main.c | 170 ++++++++++++++++++++++
tools/bg_gen_unified_linux | 283 +++++++++++++++++++++++++++++++++++++
6 files changed, 793 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
create mode 100755 tools/bg_gen_unified_linux

--
2.34.1

Jan Kiszka

unread,
Apr 7, 2022, 3:19:56 AM4/7/22
to efibootg...@googlegroups.com, Christian Storm
From: Jan Kiszka <jan.k...@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.k...@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

index 0000000..d2ca0d8
--- /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.k...@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 <endian.h>
index 0000000..994795f
+ } else if (compatible) {

Jan Kiszka

unread,
Apr 7, 2022, 3:19:57 AM4/7/22
to efibootg...@googlegroups.com, Christian Storm
From: Jan Kiszka <jan.k...@siemens.com>

In contrast to systemd's objcopy approach, rely on a Python to generate
the unified kernel image. This allows to align the kernel payload in the
right way upfront, saving one copy step. Furthermore, it allows for a
more user-friendly interface.

Signed-off-by: Jan Kiszka <jan.k...@siemens.com>
---
Makefile.am | 5 +
tools/bg_gen_unified_linux | 283 +++++++++++++++++++++++++++++++++++++
2 files changed, 288 insertions(+)
create mode 100755 tools/bg_gen_unified_linux

diff --git a/Makefile.am b/Makefile.am
index d664775..2b34247 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -134,6 +134,11 @@ install-exec-hook:
$(DESTDIR)$(bindir)/bg_printenv$(EXEEXT)
$(RM) $(DESTDIR)$(libdir)/$(lib_LTLIBRARIES)

+#
+# Unified Linux image generator script
+#
+bin_SCRIPTS = tools/bg_gen_unified_linux
+
#
# EFI compilation
#
diff --git a/tools/bg_gen_unified_linux b/tools/bg_gen_unified_linux
new file mode 100755
index 0000000..789088c
--- /dev/null
+++ b/tools/bg_gen_unified_linux
@@ -0,0 +1,283 @@
+ OPT_OFFS_SIZE_OF_INIT_DATA = 0x8
+ OPT_OFFS_SECTION_ALIGNMENT = 0x20
+ OPT_OFFS_SIZE_OF_IMAGE = 0x38
+
+ def get_opt_header_field(self, offset):
+ format = '<%dxI' % offset
+ return struct.unpack_from(format, self.opt_header)[0]
+
+ def set_opt_header_field(self, offset, val):
+ format = '<%dsI%ds' % (offset, len(self.opt_header) - offset - 4)
+ self.opt_header = struct.pack(format, self.opt_header[:offset], val,
+ self.opt_header[offset+4:])
+
+ def get_size_of_init_data(self):
+ return self.get_opt_header_field(PEHeaders.OPT_OFFS_SIZE_OF_INIT_DATA)
+
+ def set_size_of_init_data(self, size):
+ self.set_opt_header_field(PEHeaders.OPT_OFFS_SIZE_OF_INIT_DATA, size)
+
+ def get_section_alignment(self):
+ return self.get_opt_header_field(PEHeaders.OPT_OFFS_SECTION_ALIGNMENT)
+
+ def set_section_alignment(self, alignment):
+ self.set_opt_header_field(PEHeaders.OPT_OFFS_SECTION_ALIGNMENT,
+ alignment)
+ self.set_size_of_image(align(self.get_size_of_image(), alignment))
+
+ def get_size_of_image(self):
+ return self.get_opt_header_field(PEHeaders.OPT_OFFS_SIZE_OF_IMAGE)
+
+ def set_size_of_image(self, size):
+ self.set_opt_header_field(PEHeaders.OPT_OFFS_SIZE_OF_IMAGE, size)
+
+ def add_section(self, section):
+ self.header_size += 0x28
+
+ # check space for adding extra sections
+ if self.first_data < self.header_size:
+ print("FIXME: section data requires relocation", file=sys.stderr)
+ exit(1)
+
+ self.sections.append(section)
+ self.coff_header = struct.pack('<6sH16s', self.coff_header[:6],
+ len(self.sections),
+ self.coff_header[8:])
+
+ new_size = align(section.virt_addr + section.virt_size,
+ self.get_section_alignment())
+ if new_size > self.get_size_of_image():
+ self.set_size_of_image(new_size)
+
+ if section.chars & Section.IMAGE_SCN_CNT_INITIALIZED_DATA:
+ new_size = self.get_size_of_init_data() + section.data_size
+ self.set_size_of_init_data(new_size)
+
+
+parser = argparse.ArgumentParser(description='Generate unified Linux image')
+parser.add_argument('-c', '--cmdline', metavar='"CMDLINE"', default='',
+ help='kernel command line')
+parser.add_argument('-d', '--dtb', metavar='DTB', action="append", default=[],
+ type=argparse.FileType('rb'),
+ help='device tree for the kernel ' \
+ '(can be specified multiple times)')
+parser.add_argument('-i', '--initrd', metavar='INITRD',
+ type=argparse.FileType('rb'),
+ help='initrd/initramfs for the kernel')
+parser.add_argument('stub', metavar='STUB',
+ type=argparse.FileType('rb'),
+ help='stub image to use')
+parser.add_argument('kernel', metavar='KERNEL',
+ type=argparse.FileType('rb'),
+ help='image of the kernel')
+parser.add_argument('output', metavar='UNIFIEDIMAGE',
+ type=argparse.FileType('wb'),
+ help='name of unified kernel image file')
+
+try:
+ args = parser.parse_args()
+except IOError as e:
+ print(e.strerror, file=sys.stderr)
+ exit(1)
+
+cmdline = (args.cmdline + '\0').encode('utf-16-le')
+
+stub = args.stub.read()
+
+pe_headers = PEHeaders('stub image', stub)
+
+# Add extra section headers
+cmdline_offs = align(len(stub), 512)
+cmdline_size = align(len(cmdline), 512)

Jan Kiszka

unread,
Apr 11, 2022, 3:44:07 AM4/11/22
to efibootg...@googlegroups.com, Christian Storm
From: Jan Kiszka <jan.k...@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.

For CI, we need a few more cppcheck suppression.

Signed-off-by: Jan Kiszka <jan.k...@siemens.com>
---
.github/workflows/main.yaml | 5 +
Makefile.am | 31 +++++-
linux-stub/fdt.c | 183 ++++++++++++++++++++++++++++++++++++
linux-stub/initrd.c | 103 ++++++++++++++++++++
linux-stub/linux-stub.h | 23 +++++
linux-stub/main.c | 172 +++++++++++++++++++++++++++++++++
6 files changed, 514 insertions(+), 3 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/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 2bdcb8c..ce8c523 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -125,6 +125,7 @@ jobs:
suppress+=" --suppress=*:/usr/include/bits/*"
# Function 'efi_main' is called by efi:
suppress+=" --suppress=unusedFunction:main.c"
+ suppress+=" --suppress=unusedFunction:linux-stub/main.c"
# Some functions are defined for API only
suppress+=" --suppress=unusedFunction:utils.c"
suppress+=" --suppress=unusedFunction:env/env_api.c"
@@ -138,6 +139,10 @@ jobs:
suppress+=" --suppress=comparePointers:main.c"
# False positive on constructors, first hit
suppress+=" --suppress=unusedFunction:drivers/watchdog/amdfch_wdt.c"
+ # False positive, noreturn is not recognized
+ suppress+=" --suppress=nullPointerRedundantCheck:linux-stub/main.c"
+ # Avoid noise regarding Ignore* fields
+ suppress+=" --suppress=unusedStructMember:linux-stub/main.c"

enable="--enable=warning \
--enable=style \
diff --git a/Makefile.am b/Makefile.am
index f6e7405..af01ab3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -246,7 +258,10 @@ $(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)/main.o: $(GEN_VERSION_H)
+$(top_builddir)/linux-stub/%.o: $(top_srcdir)/linux-stub/%.c
+ $(call gnuefi_compile)
+
+$(top_builddir)/main.o $(top_builddir)/linux-stub/main.o: $(GEN_VERSION_H)

$(efi_solib): $(efi_objects)
$(AM_V_CCLD)$(LD) $(efi_ldflags) $(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_envtools.o: $(GEN_VERSION_H)
diff --git a/linux-stub/fdt.c b/linux-stub/fdt.c
new file mode 100644
index 0000000..24a83f0
--- /dev/null
+++ b/linux-stub/fdt.c
@@ -0,0 +1,183 @@
+ pos = (const UINT32 *) ((const UINT8 *) fdt +
+ BE32_TO_HOST(header->OffDtStruct));
+ * EFI Boot Guard, unified Linux stub
+ *
+ * Copyright (c) Siemens AG, 2022
+ *
+ * Authors:
+ * Jan Kiszka <jan.k...@siemens.com>
+ *
+ * EFI Boot Guard, unified Linux stub
+ *
+ * Copyright (c) Siemens AG, 2022
+ *
+ * Authors:
+ * Jan Kiszka <jan.k...@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..3cb0040
--- /dev/null
+++ b/linux-stub/main.c
@@ -0,0 +1,172 @@
+/*
+ * EFI Boot Guard, unified Linux stub
+ *
+ * Copyright (c) Siemens AG, 2022
+ *
+ * Authors:
+ * Jan Kiszka <jan.k...@siemens.com>
+ *
+ return (const PE_HEADER *) ((const UINT8 *) image +
+ dos_header->PEOffset);
+}
+
+static const SECTION *get_sections(const PE_HEADER *pe_header)
+{
+ return (const SECTION *) ((const UINT8 *) &pe_header->Opt +
+ 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 = (UINT8 *) stub_image->ImageBase +
+ ((UINT8 *) kernel_image.ImageBase +

Jan Kiszka

unread,
Apr 11, 2022, 3:44:07 AM4/11/22
to efibootg...@googlegroups.com, Christian Storm
From: Jan Kiszka <jan.k...@siemens.com>

In contrast to systemd's objcopy approach, rely on a Python to generate
the unified kernel image. This allows to align the kernel payload in the
right way upfront, saving one copy step. Furthermore, it allows for a
more user-friendly interface.

Signed-off-by: Jan Kiszka <jan.k...@siemens.com>
---
Makefile.am | 5 +
tools/bg_gen_unified_linux | 284 +++++++++++++++++++++++++++++++++++++
2 files changed, 289 insertions(+)
create mode 100755 tools/bg_gen_unified_linux

diff --git a/Makefile.am b/Makefile.am
index af01ab3..0506ea1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -134,6 +134,11 @@ install-exec-hook:
$(DESTDIR)$(bindir)/bg_printenv$(EXEEXT)
$(RM) $(DESTDIR)$(libdir)/$(lib_LTLIBRARIES)

+#
+# Unified Linux image generator script
+#
+bin_SCRIPTS = tools/bg_gen_unified_linux
+
#
# EFI compilation
#
diff --git a/tools/bg_gen_unified_linux b/tools/bg_gen_unified_linux
new file mode 100755
index 0000000..faddca8
--- /dev/null
+++ b/tools/bg_gen_unified_linux
@@ -0,0 +1,284 @@

Jan Kiszka

unread,
Apr 11, 2022, 3:44:14 AM4/11/22
to efibootg...@googlegroups.com, Christian Storm
Changes in v3:
- address/suppress cppcheck findings
- add required build dependency on version.h
- address pycodestyle findings
- add documentation

Changes in v2:
- fix script for more picky UEFI firmware than U-Boot (now tested also
against OVMF on x86)
- move/rename script to tools/bg_gen_unified_linux and install it
- build fixes under Debian 10
- avoid dtb-related output of stub under x86

Add a stub and generator script to build inified Linux images that
contain kernel, command line, initrd and device trees into a single UEFI
executable. This is an important building block for secure boot under
UEFI.

In contrast to the existing solution by systemd, this one comes with
support for multiple device trees that permits running the same image
on similar but not identical hardware platforms. Although the trend goes
towards firmware provided device tree, replacements in lock-step with
kernel updates will remains important in the foreseeable future, and
this stub accounts for it.

Furthermore, this approach here has a more user-friendly python-based
generator script which does not depend on too-new binutils or LLVM
versions and allows to simplify the Linux stub by arranging data in the
required way already during generation.

These patches have been moderately tested only, primarily on ARM64. The
next planned step is a test integration with isar-cip-core. Still,
reviews would already be welcome.

Jan

Jan Kiszka (3):
Add stub for unified Linux images
tools: Add generator for unified Linux images
docs: Add description for unified Linux images

.github/workflows/main.yaml | 5 +
Makefile.am | 36 ++++-
README.md | 5 +
docs/UNIFIED-LINUX.md | 38 +++++
linux-stub/fdt.c | 183 +++++++++++++++++++++++
linux-stub/initrd.c | 103 +++++++++++++
linux-stub/linux-stub.h | 23 +++
linux-stub/main.c | 172 ++++++++++++++++++++++
tools/bg_gen_unified_linux | 284 ++++++++++++++++++++++++++++++++++++
9 files changed, 846 insertions(+), 3 deletions(-)
create mode 100644 docs/UNIFIED-LINUX.md
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

Jan Kiszka

unread,
Apr 13, 2022, 2:23:11 AM4/13/22
to efibootg...@googlegroups.com, Christian Storm
Just realized that the corresponding generator script for systemd is
dracut --uefi. But that one has the same binutils dependency as it
simply wraps the objcopy call.

>
> These patches have been moderately tested only, primarily on ARM64. The
> next planned step is a test integration with isar-cip-core. Still,
> reviews would already be welcome.

I should have dropped that paragraph: The code is not pretty well
tested, specifically using [1].

One thing I'm not yet sure about is a naming. I used "unified Linux
image" fairly consistently here while systemd talks about "unified
kernel image". I wanted to make the Linux focus clearer and the fact
that it's at least internally not the same (different interface between
generator and stub). OTOH, "unified kernel image" might already be an
established term for the result you get and boot. Thoughts?

Jan

[1]
https://gitlab.com/cip-project/cip-core/isar-cip-core/-/commits/jan/ebg-rework

--
Siemens AG, Technology
Competence Center Embedded Linux

Christian Storm

unread,
Apr 27, 2022, 10:04:37 AM4/27/22
to efibootg...@googlegroups.com
Hi Jan,

> 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

Nitpicking: "the device trees" ← double space.


> 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 is because of LoadFile2?
This refers to U-Boot's implementation, right?
Maybe it's sensible to link to it here so to get the meaning of
the DTB Fixup stuff...
Shouldn't there be somewhere 'fdt' in the signature to
flag this a belonging to the fdt handling stuff?
get_compatible() is a bit too generic for me though the
single parameter's name clarifies it a bit..


> +{
> + 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;
> + }
> +

Line with spaces only ↑.


> + 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;
> + }
> + }

Shouldn't here be an explicit `return NULL`?
Isn't here a `FreePool(fdt_buffer);` missing?
See above for an 'fdt' in the variable's name.
Hm, so it's not possible to use a command line provided by EFI
Boot Guard in !Secure Boot mode? Is this by intention?
In Secure Boot mode there could be a warning that it's ignored because
of Secure Boot...
Kind regards,
Christian

--
Dr. Christian Storm
Siemens AG, Technology, T CED SES-DE
Otto-Hahn-Ring 6, 81739 München, Germany

Christian Storm

unread,
Apr 27, 2022, 10:56:51 AM4/27/22
to efibootg...@googlegroups.com
Hi Jan,
Hm, the term "Unified Kernel Image" is already properly coined and
documented in systemd (e.g., [1]) and distributions, so deviation
from it should have good arguments.

From my perspective it's very abstractly speaking the general concept of
packing it all up into one fat EFI binary so that it can be properly
Secure Booted ― which is a technical limitation as EFI can only check
PE/COFF executable files.

So, in this sense, we're doing "Unified Kernel Image" although with some
technical differences, don't we?
Now, does this justify putting a different label on it as it's not
*technically* the very same thing although conceptually it is?
I would go along with the "Unified Kernel Image" label and point
out/explain the technical differences somewhere in the docs/README.

YMMV though....



Kind regards,
Christian

[1] https://systemd.io/BOOT_LOADER_SPECIFICATION/#type-2-efi-unified-kernel-images

Jan Kiszka

unread,
Apr 27, 2022, 11:16:59 AM4/27/22
to efibootg...@googlegroups.com
On 27.04.22 16:06, Christian Storm wrote:
> Hi Jan,
>
>> 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
>
> Nitpicking: "the device trees" ← double space.
>
>
>> 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 is because of LoadFile2?
>

Nope, LINUX_EFI_INITRD_MEDIA_GUID.
Nope, the existence of a DTB in the system table is UEFI standard by now
(likely since 2.9).
Static function in the module fdt.c. The confusion might be more about
get_compatible vs. get_fdt_compatible. Let me rethink.

>
>> +{
>> + 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;
>> + }
>> +
>
> Line with spaces only ↑.
>

Uups.

>
>> + 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;
>> + }
>> + }
>
> Shouldn't here be an explicit `return NULL`?
>

Loop will never end - except for direct return. So we never get here.
Yes - though we leak much more on fatal errors into the boot env.
Ack
Yes, by design. No use real need, rather a high risk to keep this
insecure channel accidentally open. So we are more strict here than systemd.

> In Secure Boot mode there could be a warning that it's ignored because
> of Secure Boot...

We could add a warning, yes.

Thanks!
Jan

Jan Kiszka

unread,
Apr 27, 2022, 11:22:53 AM4/27/22
to efibootg...@googlegroups.com
Yeah, though implementations are fairly Linux-centric.

> So, in this sense, we're doing "Unified Kernel Image" although with some
> technical differences, don't we?
> Now, does this justify putting a different label on it as it's not
> *technically* the very same thing although conceptually it is?
> I would go along with the "Unified Kernel Image" label and point
> out/explain the technical differences somewhere in the docs/README.

Ok, will rename consistently then. And I can add some differences
listing to UNIFIED-KERNEL.md.

Christian Storm

unread,
Apr 27, 2022, 11:27:49 AM4/27/22
to efibootg...@googlegroups.com
Hi Jan,

> In contrast to systemd's objcopy approach, rely on a Python

missing: script


> to generate
> the unified kernel image.

Here you're not using the own term "unified Linux image".


> This allows to align the kernel payload in the
> right way upfront, saving one copy step. Furthermore, it allows for a
> more user-friendly interface.

Given that Python is present and installed, that is.
For generating Unified Kernel Images you need binutils' objcopy...
You don't use os anywhere, do you?


> +import struct
> +import sys
> +
> +
> +def align(val, alignment):
> + return (val + alignment - 1) & ~(alignment - 1)
> +
> +
> +class Section:
> + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
> + IMAGE_SCN_MEM_READ = 0x40000000
> +
> + def __init__(self, blob):
> + (self.name, self.virt_size, self.virt_addr, self.data_size,
> + self.data_offs, self.chars) = \
> + struct.unpack_from('<8sIIII12xI', blob)
> +
> + def __init__(self, name, virt_size, virt_addr, data_size, data_offs,

This *overwrites* and not overloads the `__init__` above (and you don't
use this anyway below).

In Python, you have to work around the missing overloading capability
and use, e.g., `def __init__(self, *args):` and fiddle with `*args` or
use a default parameter constructor, ....
You should enclose the following in
if __name__ == "__main__":
...

so that this can be used as a "library". As it is now, it's executed on
import. No big deal for a standalone script and so it's nitpicking...
Usually, `pe_headers` should have getters() for these or a dedicated
function returning the result if these are not public attributes of the
"class". Nitpicking...

> +
> +# Write remaining stub
> +image += stub[len(image):]
> +
> +# Write data of extra sections
> +image += bytearray(cmdline_offs - len(image))
> +image += cmdline
> +
> +image += bytearray(kernel_offs - len(image))
> +image += kernel
> +
> +if args.initrd:
> + image += bytearray(initrd_offs - len(image))
> + image += initrd
> +
> +for n in range(len(dtb)):
> + image += bytearray(dtb_offs[n] - len(image))
> + image += dtb[n]
> +
> +# Align to promised size of last section
> +image += bytearray(align(len(image), 512) - len(image))
> +
> +args.output.write(image)


Kind regards,
Christian

Jan Kiszka

unread,
Apr 27, 2022, 11:49:41 AM4/27/22
to efibootg...@googlegroups.com
On 27.04.22 17:29, Christian Storm wrote:
> Hi Jan,
>
>> In contrast to systemd's objcopy approach, rely on a Python
>
> missing: script
>
>
>> to generate
>> the unified kernel image.
>
> Here you're not using the own term "unified Linux image".
>

Hehe...

>
>> This allows to align the kernel payload in the
>> right way upfront, saving one copy step. Furthermore, it allows for a
>> more user-friendly interface.
>
> Given that Python is present and installed, that is.
> For generating Unified Kernel Images you need binutils' objcopy...
>

...and that currently in a specific version for arm64.
Let me check that again...

>
>> +import struct
>> +import sys
>> +
>> +
>> +def align(val, alignment):
>> + return (val + alignment - 1) & ~(alignment - 1)
>> +
>> +
>> +class Section:
>> + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
>> + IMAGE_SCN_MEM_READ = 0x40000000
>> +
>> + def __init__(self, blob):
>> + (self.name, self.virt_size, self.virt_addr, self.data_size,
>> + self.data_offs, self.chars) = \
>> + struct.unpack_from('<8sIIII12xI', blob)
>> +
>> + def __init__(self, name, virt_size, virt_addr, data_size, data_offs,
>
> This *overwrites* and not overloads the `__init__` above (and you don't
> use this anyway below).
>
> In Python, you have to work around the missing overloading capability
> and use, e.g., `def __init__(self, *args):` and fiddle with `*args` or
> use a default parameter constructor, ....
>

Yeah, I think I forgot that again, tried to overload, failed, but didn't
remove the left-overs.
Jup, will create a proper mail() as well. Laziness.
Yes, but I was not motivated enough to write all the boilerplate code
for not much added value.
Thanks,

Jan Kiszka

unread,
Apr 28, 2022, 6:12:49 AM4/28/22
to efibootg...@googlegroups.com, Christian Storm
Changes in v4:
- renamed to "unified kernel image", also at filename level
- added error clean-up to kernel-stub (warning: untested, as usual)
- some style changes
- main function for bg_gen_unified_kernel
- remove some dead code from bg_gen_unified_kernel
These patches have been moderately tested only, primarily on ARM64. The
next planned step is a test integration with isar-cip-core. Still,
reviews would already be welcome.

Jan

Jan Kiszka (3):
Add stub for unified kernel images
tools: Add generator for unified kernel images
docs: Add description for unified kernel images

.github/workflows/main.yaml | 5 +
Makefile.am | 36 ++++-
README.md | 5 +
docs/UNIFIED-KERNEL.md | 38 +++++
kernel-stub/fdt.c | 190 ++++++++++++++++++++++++
kernel-stub/initrd.c | 121 +++++++++++++++
kernel-stub/kernel-stub.h | 24 +++
kernel-stub/main.c | 189 ++++++++++++++++++++++++
tools/bg_gen_unified_kernel | 284 ++++++++++++++++++++++++++++++++++++
9 files changed, 889 insertions(+), 3 deletions(-)
create mode 100644 docs/UNIFIED-KERNEL.md
create mode 100644 kernel-stub/fdt.c
create mode 100644 kernel-stub/initrd.c
create mode 100644 kernel-stub/kernel-stub.h
create mode 100644 kernel-stub/main.c
create mode 100755 tools/bg_gen_unified_kernel

--
2.34.1

Jan Kiszka

unread,
Apr 28, 2022, 6:12:50 AM4/28/22
to efibootg...@googlegroups.com, Christian Storm
From: Jan Kiszka <jan.k...@siemens.com>

In contrast to systemd's objcopy approach, rely on a Python to generate
the unified kernel image. This allows to align the kernel payload in the
right way upfront, saving one copy step. Furthermore, it allows for a
more user-friendly interface on the command line.

Signed-off-by: Jan Kiszka <jan.k...@siemens.com>
---
Makefile.am | 5 +
tools/bg_gen_unified_kernel | 284 ++++++++++++++++++++++++++++++++++++
2 files changed, 289 insertions(+)
create mode 100755 tools/bg_gen_unified_kernel

diff --git a/Makefile.am b/Makefile.am
index ccfa306..f0daa15 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -133,6 +133,11 @@ install-exec-hook:
$(DESTDIR)$(bindir)/bg_printenv$(EXEEXT)
$(RM) $(DESTDIR)$(libdir)/$(lib_LTLIBRARIES)

+#
+# Unified kernel image generator script
+#
+bin_SCRIPTS = tools/bg_gen_unified_kernel
+
#
# EFI compilation
#
diff --git a/tools/bg_gen_unified_kernel b/tools/bg_gen_unified_kernel
new file mode 100755
index 0000000..5f3727c
--- /dev/null
+++ b/tools/bg_gen_unified_kernel
@@ -0,0 +1,284 @@
+#!/usr/bin/env python3
+#
+# EFI Boot Guard, unified kernel image generator
+#
+# Copyright (c) Siemens AG, 2022
+#
+# Authors:
+# Jan Kiszka <jan.k...@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
+
+import argparse
+import struct
+import sys
+
+
+def align(val, alignment):
+ return (val + alignment - 1) & ~(alignment - 1)
+
+
+class Section:
+ IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
+ IMAGE_SCN_MEM_READ = 0x40000000
+
+ def __init__(self, name, virt_size, virt_addr, data_size, data_offs,
+def main():
+ parser = argparse.ArgumentParser(
+ description='Generate unified kernel image')
+ parser.add_argument('-c', '--cmdline', metavar='"CMDLINE"', default='',
+ help='kernel command line')
+ parser.add_argument('-d', '--dtb', metavar='DTB', action="append",
+ default=[], type=argparse.FileType('rb'),
+ help='device tree for the kernel '
+ '(can be specified multiple times)')
+ parser.add_argument('-i', '--initrd', metavar='INITRD',
+ type=argparse.FileType('rb'),
+ help='initrd/initramfs for the kernel')
+ parser.add_argument('stub', metavar='STUB',
+ type=argparse.FileType('rb'),
+ help='stub image to use')
+ parser.add_argument('kernel', metavar='KERNEL',
+ type=argparse.FileType('rb'),
+ help='image of the kernel')
+ parser.add_argument('output', metavar='UNIFIEDIMAGE',
+ type=argparse.FileType('wb'),
+ help='name of unified kernel image file')
+
+ try:
+ args = parser.parse_args()
+ except IOError as e:
+ print(e.strerror, file=sys.stderr)
+ exit(1)
+
+ cmdline = (args.cmdline + '\0').encode('utf-16-le')
+
+ stub = args.stub.read()
+
+ pe_headers = PEHeaders('stub image', stub)
+
+ # Add extra section headers
+ cmdline_offs = align(len(stub), 512)
+ cmdline_size = align(len(cmdline), 512)
+ section = Section(b'.cmdline', cmdline_size, 0x30000,
+ cmdline_size, cmdline_offs,
+ Section.IMAGE_SCN_CNT_INITIALIZED_DATA |
+ Section.IMAGE_SCN_MEM_READ)
+ pe_headers.add_section(section)
+
+ kernel = args.kernel.read()
+ kernel_pe_headers = PEHeaders('kernel', kernel)
+
+ kernel_offs = cmdline_offs + cmdline_size
+ kernel_size = align(len(kernel), 512)
+ kernel_virt_size = max(kernel_size, kernel_pe_headers.get_size_of_image())
+ section = Section(b'.kernel', kernel_virt_size, 0x2000000,
+ kernel_size, kernel_offs,
+ Section.IMAGE_SCN_CNT_INITIALIZED_DATA |
+ Section.IMAGE_SCN_MEM_READ)
+ pe_headers.add_section(section)
+ pe_headers.set_section_alignment(kernel_pe_headers.get_section_alignment())
+
+ initrd_offs = kernel_offs + kernel_size
+ initrd_size = 0
+ if args.initrd:
+ initrd = args.initrd.read()
+ initrd_size = align(len(initrd), 512)
+ section = Section(b'.initrd', initrd_size, 0x6000000,
+ initrd_size, initrd_offs,
+ Section.IMAGE_SCN_CNT_INITIALIZED_DATA |
+ Section.IMAGE_SCN_MEM_READ)
+ pe_headers.add_section(section)
+
+ current_offs = initrd_offs + initrd_size
+ dtb_virt = 0x40000
+ dtb = []
+ dtb_offs = []
+ dtb_size = 0
+ for n in range(len(args.dtb)):
+ dtb.append(args.dtb[n].read())
+ dtb_offs.append(current_offs)
+ dtb_size = align(len(dtb[n]), 512)
+ section = Section(bytes('.dtb-{}'.format(n + 1), 'ascii'),
+ dtb_size, dtb_virt, dtb_size, dtb_offs[n],
+ Section.IMAGE_SCN_CNT_INITIALIZED_DATA |
+ Section.IMAGE_SCN_MEM_READ)
+ pe_headers.add_section(section)
+ dtb_virt += dtb_size
+ current_offs += dtb_size
+
+ # Build unified image header
+ image = pe_headers.dos_header + pe_headers.coff_header + \
+ pe_headers.opt_header
+ for section in pe_headers.sections:
+ image += section.get_struct()
+
+ # Write remaining stub
+ image += stub[len(image):]
+
+ # Write data of extra sections
+ image += bytearray(cmdline_offs - len(image))
+ image += cmdline
+
+ image += bytearray(kernel_offs - len(image))
+ image += kernel
+
+ if args.initrd:
+ image += bytearray(initrd_offs - len(image))
+ image += initrd
+
+ for n in range(len(dtb)):
+ image += bytearray(dtb_offs[n] - len(image))
+ image += dtb[n]
+
+ # Align to promised size of last section
+ image += bytearray(align(len(image), 512) - len(image))
+
+ args.output.write(image)
+
+if __name__ == "__main__":
+ main()
--
2.34.1

Jan Kiszka

unread,
Apr 28, 2022, 6:12:50 AM4/28/22
to efibootg...@googlegroups.com, Christian Storm
From: Jan Kiszka <jan.k...@siemens.com>

Signed-off-by: Jan Kiszka <jan.k...@siemens.com>
---
README.md | 5 +++++
docs/UNIFIED-KERNEL.md | 38 ++++++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+)
create mode 100644 docs/UNIFIED-KERNEL.md

diff --git a/README.md b/README.md
index b7446eb..c6d13fd 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,10 @@ Provides the following functionality:
* Arm a hardware watchdog prior to loading an OS
* Provides a simple update mechanism with fail-safe algorithm

+In addition, this project provides a UEFI stub and generator tool to create
+unified kernel images consisting of kernel, command line and, optionally,
+initrd and device trees.
+
## Development ##

Mailing list:
@@ -51,4 +55,5 @@ See `Installation And Usage` for further information.
* [API Library](docs/API.md)
* [Compilation Instructions](docs/COMPILE.md)
* [Installation And Usage](docs/USAGE.md)
+* [Unified Kernel Images](docs/UNIFIED-KERNEL.md)
* [System Recovery](docs/RECOVERY.md)
diff --git a/docs/UNIFIED-KERNEL.md b/docs/UNIFIED-KERNEL.md
new file mode 100644
index 0000000..6e23f16
--- /dev/null
+++ b/docs/UNIFIED-KERNEL.md
@@ -0,0 +1,38 @@
+# Unified Kernel Images #
+
+A unified kernel image combines all artifacts needed to start an OS, typically
+Linux, from a single UEFI binary, even in complex scenarios. This includes:
+* Kernel (as UEFI binary)
+* Kernel command line
+* initrd/initramfs (optional, requires kernel version 5.8+)
+* alternative device trees (optional)
+
+Using a single binary enables secure boot setups by allowing to sign and later
+on validate this binary during boot-up.
+
+The ability to embed and select from multiple device trees permits to replace
+the firmware-provide device tree with an alternative one if the kernel requires
+deviation or the firmware does not permit easy updates. The final device tree
+is selected by matching its compatible property against the firmware device
+tree.
+
+## Building unified kernel images ##
+
+EFI Boot Guard provides the `bg_gen_unified_kernel` command to generate the
+image from all required artifacts, e.g.:
+
+```
+bg_gen_unified_kernel \
+ kernel-stubaa64.efi \
+ vmlinux-5.17.1 \
+ unified-linux.efi \
+ --cmdline "console=ttyS0,115200" \
+ --initrd initrd-5.17.1 \
+ --dtb board-variant-1.dtb \
+ --dtb board-variant-2.dtb
+```
+
+See also `bg_gen_unified_kernel --help`.
+
+The generated `unified-linux.efi` can then be signed with tools like `pesign`
+or `sbsign` to enable secure boot.
--
2.34.1

Jan Kiszka

unread,
Apr 28, 2022, 6:12:50 AM4/28/22
to efibootg...@googlegroups.com, Christian Storm
From: Jan Kiszka <jan.k...@siemens.com>

This provides a simple UEFI stub which allows to form unified kernel
images, typically used for Linux. 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 may 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.

For CI, we need a few more cppcheck suppression.

Signed-off-by: Jan Kiszka <jan.k...@siemens.com>
---
.github/workflows/main.yaml | 5 +
Makefile.am | 31 +++++-
kernel-stub/fdt.c | 190 ++++++++++++++++++++++++++++++++++++
kernel-stub/initrd.c | 121 +++++++++++++++++++++++
kernel-stub/kernel-stub.h | 24 +++++
kernel-stub/main.c | 189 +++++++++++++++++++++++++++++++++++
6 files changed, 557 insertions(+), 3 deletions(-)
create mode 100644 kernel-stub/fdt.c
create mode 100644 kernel-stub/initrd.c
create mode 100644 kernel-stub/kernel-stub.h
create mode 100644 kernel-stub/main.c

diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 2bdcb8c..5c25971 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -125,6 +125,7 @@ jobs:
suppress+=" --suppress=*:/usr/include/bits/*"
# Function 'efi_main' is called by efi:
suppress+=" --suppress=unusedFunction:main.c"
+ suppress+=" --suppress=unusedFunction:kernel-stub/main.c"
# Some functions are defined for API only
suppress+=" --suppress=unusedFunction:utils.c"
suppress+=" --suppress=unusedFunction:env/env_api.c"
@@ -138,6 +139,10 @@ jobs:
suppress+=" --suppress=comparePointers:main.c"
# False positive on constructors, first hit
suppress+=" --suppress=unusedFunction:drivers/watchdog/amdfch_wdt.c"
+ # False positive, noreturn is not recognized
+ suppress+=" --suppress=nullPointerRedundantCheck:kernel-stub/main.c"
+ # Avoid noise regarding Ignore* fields
+ suppress+=" --suppress=unusedStructMember:kernel-stub/main.c"

enable="--enable=warning \
--enable=style \
diff --git a/Makefile.am b/Makefile.am
index e7c0ec5..ccfa306 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -167,6 +167,13 @@ efi_sources = \
bootguard.c \
main.c

+kernel_stub_name = kernel-stub$(MACHINE_TYPE_NAME).efi
+
+kernel_stub_sources = \
+ kernel-stub/fdt.c \
+ kernel-stub/initrd.c \
+ kernel-stub/main.c
+
efi_cppflags = \
-I$(top_builddir) -include config.h \
-I$(top_srcdir)/include \
@@ -223,10 +230,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

+kernel_stub_objects_pre = $(kernel_stub_sources:.c=.o)
+kernel_stub_objects = $(addprefix $(top_builddir)/,$(kernel_stub_objects_pre))
+kernel_stub_solib = $(top_builddir)/kernel-stub/kernel-stub$(MACHINE_TYPE_NAME).so
+
# automake stuff
-efibootguard_DATA = $(efi_loadername)
+efibootguard_DATA = $(efi_loadername) $(kernel_stub_name)
CLEANFILES += $(efi_objects) $(efi_solib) $(efi_loadername)
-EXTRA_DIST += $(efi_sources)
+CLEANFILES += $(kernel_stub_objects) $(kernel_stub_solib) $(kernel_stub_name)
+EXTRA_DIST += $(efi_sources) $(kernel_stub_sources)

define gnuefi_compile
$(AM_V_CC) $(MKDIR_P) $(shell dirname $@)/; \
@@ -245,7 +257,10 @@ $(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)/main.o: $(GEN_VERSION_H)
+$(top_builddir)/kernel-stub/%.o: $(top_srcdir)/kernel-stub/%.c
+ $(call gnuefi_compile)
+
+$(top_builddir)/main.o $(top_builddir)/kernel-stub/main.o: $(GEN_VERSION_H)

$(efi_solib): $(efi_objects)
$(AM_V_CCLD)$(LD) $(efi_ldflags) $(efi_objects) \
@@ -256,6 +271,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) $< $@
+
+$(kernel_stub_solib): $(kernel_stub_objects)
+ $(AM_V_CCLD)$(LD) $(efi_ldflags) $(kernel_stub_objects) \
+ -o $@ -lefi -lgnuefi $(shell $(CC) $(CFLAGS) -print-libgcc-file-name); \
+ nm -D -u $@ | grep ' U ' && exit 1 || :
+
+$(kernel_stub_name): $(kernel_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_envtools.o: $(GEN_VERSION_H)
diff --git a/kernel-stub/fdt.c b/kernel-stub/fdt.c
new file mode 100644
index 0000000..513aa76
--- /dev/null
+++ b/kernel-stub/fdt.c
@@ -0,0 +1,190 @@
+/*
+ * EFI Boot Guard, unified kernel stub
+ *
+ * Copyright (c) Siemens AG, 2022
+ *
+ * Authors:
+ * Jan Kiszka <jan.k...@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 <endian.h>
+
+#include "kernel-stub.h"
+#define EfiDtbTableGuid gEfiDtbTableGuid
+#endif
+
+/*
+ * FDT fixup protocol, provided only by U-Boot so far, but other firmware may
+ * follow. See also
+ * - https://github.com/U-Boot-EFI/EFI_DT_FIXUP_PROTOCOL
+ * - https://github.com/ARM-software/ebbr/issues/68
+ */
+#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_from_fdt(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 = (const UINT32 *) ((const UINT8 *) 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_from_fdt(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_from_fdt(fdt);
+ FreePool(fdt_buffer);
+ error_exit(L"Device tree fixup failed", status);
+ }
+}
diff --git a/kernel-stub/initrd.c b/kernel-stub/initrd.c
new file mode 100644
index 0000000..342bf2b
--- /dev/null
+++ b/kernel-stub/initrd.c
@@ -0,0 +1,121 @@
+/*
+ * EFI Boot Guard, unified kernel stub
+ *
+ * Copyright (c) Siemens AG, 2022
+ *
+ * Authors:
+ * Jan Kiszka <jan.k...@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 "kernel-stub.h"
+static EFI_HANDLE initrd_handle;
+ 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);
+ }
+}
+
+VOID uninstall_initrd_loader(VOID)
+{
+ EFI_STATUS status;
+
+ if (!initrd_handle) {
+ return;
+ }
+
+ status = BS->UninstallMultipleProtocolInterfaces(
+ initrd_handle, &DevicePathProtocol,
+ &initrd_device_path, &EfiLoadFile2Protocol,
+ &initrd_loader, NULL);
+ if (EFI_ERROR(status)) {
+ error_exit(L"Error unregistering initrd loader", status);
+ }
+}
diff --git a/kernel-stub/kernel-stub.h b/kernel-stub/kernel-stub.h
new file mode 100644
index 0000000..4944355
--- /dev/null
+++ b/kernel-stub/kernel-stub.h
@@ -0,0 +1,24 @@
+/*
+ * EFI Boot Guard, unified kernel stub
+ *
+ * Copyright (c) Siemens AG, 2022
+ *
+ * Authors:
+ * Jan Kiszka <jan.k...@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);
+VOID uninstall_initrd_loader(VOID);
diff --git a/kernel-stub/main.c b/kernel-stub/main.c
new file mode 100644
index 0000000..36249b4
--- /dev/null
+++ b/kernel-stub/main.c
@@ -0,0 +1,189 @@
+/*
+ * EFI Boot Guard, unified kernel stub
+ *
+ * Copyright (c) Siemens AG, 2022
+ *
+ * Authors:
+ * Jan Kiszka <jan.k...@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 "kernel-stub.h"
+ Print(L"Unified kernel stub: %s (%r).\n", message, status);
+ (VOID) BS->Stall(3 * 1000 * 1000);
+ (VOID) BS->Exit(this_image, status, 0, NULL);
+ __builtin_unreachable();
+}
+
+static VOID info(CHAR16 *message)
+{
+ Print(L"Unified kernel stub: %s\n", message);
+}
+
+static const PE_HEADER *get_pe_header(const VOID *image)
+{
+ const DOS_HEADER *dos_header = image;
+
+ return (const PE_HEADER *) ((const UINT8 *) 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 *fdt_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, kernel_status;
+ UINTN n;
+
+ this_image = image_handle;
+ InitializeLib(image_handle, system_table);
+
+ Print(L"Unified kernel stub (EFI Boot Guard %s)\n",
+ L"" EFIBOOTGUARD_VERSION);
+
+ fdt_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);
+ 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 = (UINT8 *) stub_image->ImageBase +
+ section->VirtualAddress;
+ if (match_fdt(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)) {
+ uninstall_initrd_loader();
+ error_exit(L"Error registering kernel image", status);
+ }
+
+ if (alt_fdt) {
+ replace_fdt(alt_fdt);
+ info(L"Using matched embedded device tree");
+ } else if (fdt_compatible) {
+ if (has_dtbs) {
+ info(L"WARNING: No embedded device tree matched firmware-provided one");
+ }
+ info(L"Using firmware-provided device tree");
+ }
+
+ pe_header = get_pe_header(kernel_image.ImageBase);
+ kernel_entry = (EFI_IMAGE_ENTRY_POINT)
+ ((UINT8 *) kernel_image.ImageBase +
+ pe_header->Opt.AddressOfEntryPoint);
+
+ kernel_status = kernel_entry(kernel_handle, system_table);
+
+ status = BS->UninstallMultipleProtocolInterfaces(
+ kernel_handle, &LoadedImageProtocol, &kernel_image,
+ NULL);
+ if (EFI_ERROR(status)) {
+ error_exit(L"Error unregistering kernel image", status);
+ }
+ uninstall_initrd_loader();
+
+ return kernel_status;
+}
--
2.34.1

Jan Kiszka

unread,
Apr 28, 2022, 12:10:24 PM4/28/22
to efibootg...@googlegroups.com, Christian Storm
On 28.04.22 12:12, Jan Kiszka wrote:
> Changes in v4:
> - renamed to "unified kernel image", also at filename level

...except for the cover letter.

Jan
Reply all
Reply to author
Forward
0 new messages