* [PATCH v1 01/14] mfd: hgs-efi: do not claim the name of the EFI loader's device
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 02/14] efi: loader: file: report EFI_UNSUPPORTED for volumes without a filesystem Johannes Schneider
` (12 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
The coprocessor device registers as "efi", the name the EFI loader gives
its own device. With CONFIG_EFI_LOADER on a GS05, whichever registers
second fails, and the EFI loader then panics during late init:
register_device: already registered efi
initcall efi_init_params+0x0/0x7c failed: Invalid argument
PANIC: unable to handle paging request at address 0xffffffffffffffe0
Name the device "hgs-efi", after its driver, and look it up by that
name in the GS05 board code and in hgs_notify_pp4() to sidestep this
naming conflict.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
arch/arm/boards/hgs-gs05/board.c | 2 +-
common/boards/hgs/common.c | 2 +-
drivers/mfd/hgs-efi.c | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/arch/arm/boards/hgs-gs05/board.c b/arch/arm/boards/hgs-gs05/board.c
index 40dbecae08..f24a764e6c 100644
--- a/arch/arm/boards/hgs-gs05/board.c
+++ b/arch/arm/boards/hgs-gs05/board.c
@@ -123,7 +123,7 @@ hgs_gs05_set_efi_poll_intervall(struct device *efid, u64 new_polling_interval)
static struct hgs_machine *hgs_gs05_get_board(struct device *dev)
{
u8 buf[HGS_GS05_SERIAL_NUMBER_CHARS] = { };
- struct device *efi_dev = get_device_by_name("efi");
+ struct device *efi_dev = get_device_by_name("hgs-efi");
struct hgs_efi *efi = dev_get_priv(efi_dev->parent);
struct hgs_sep_cmd cmd = {
.type = HGS_SEP_MSG_TYPE_COMMAND,
diff --git a/common/boards/hgs/common.c b/common/boards/hgs/common.c
index c4a31a2797..0f95bf7bd7 100644
--- a/common/boards/hgs/common.c
+++ b/common/boards/hgs/common.c
@@ -582,7 +582,7 @@ static int hgs_notify_pp4(void)
struct device *efi;
int ret;
- efi = get_device_by_name("efi");
+ efi = get_device_by_name("hgs-efi");
ret = dev_set_param(efi, "cpu_rdy", "1");
if (ret)
return ret;
diff --git a/drivers/mfd/hgs-efi.c b/drivers/mfd/hgs-efi.c
index 51c0b7ffc9..eb7c267753 100644
--- a/drivers/mfd/hgs-efi.c
+++ b/drivers/mfd/hgs-efi.c
@@ -414,7 +414,7 @@ static int hgs_efi_register_dev(struct hgs_efi *efi)
int ret;
dev->parent = efi->serdev->dev;
- dev_set_name(dev, "efi");
+ dev_set_name(dev, "hgs-efi");
dev->id = DEVICE_ID_SINGLE;
ret = register_device(dev);
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 02/14] efi: loader: file: report EFI_UNSUPPORTED for volumes without a filesystem
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 01/14] mfd: hgs-efi: do not claim the name of the EFI loader's device Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 03/14] efi: loader: bootm: free the devicetree after installing it Johannes Schneider
` (11 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
OpenVolume() on a partition without a filesystem, such as a raw slot a
kernel or UKI is booted from, fails in cdev_mount() and returns
EFI_DEVICE_ERROR. The UEFI specification prescribes EFI_UNSUPPORTED for
that, and callers rely on it: systemd-stub skips EFI_UNSUPPORTED quietly
but logs any other error, and its log_wait() then stalls boot by up to
10 s:
stub.c:587@load_addons: Unable to open root directory: Device error
cpio.c:340@pack_cpio: Unable to open root directory: Device error (x6)
Return EFI_UNSUPPORTED when no filesystem driver recognizes the device.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/protocols/file.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/efi/loader/protocols/file.c b/efi/loader/protocols/file.c
index ea7b0df82e..bfcf14f0e0 100644
--- a/efi/loader/protocols/file.c
+++ b/efi/loader/protocols/file.c
@@ -30,6 +30,8 @@
#include <malloc.h>
#include <dirent.h>
#include <fs.h>
+#include <linux/sprintf.h>
+#include <xfuncs.h>
#define MAX_UTF8_PER_UTF16 3
@@ -1012,6 +1014,16 @@ efi_open_volume(struct efi_simple_file_system_protocol *this,
EFI_ENTRY("%p, %p", this, root);
+ /* No filesystem on the volume is EFI_UNSUPPORTED, not a device error */
+ if (!cdev_get_mount_path(fs->cdev)) {
+ char *devpath = basprintf("/dev/%s", cdev_name(fs->cdev));
+ bool has_fs = fs_detect(devpath, "");
+
+ free(devpath);
+ if (!has_fs)
+ return EFI_EXIT(EFI_UNSUPPORTED);
+ }
+
path = cdev_mount(fs->cdev);
if (IS_ERR(path))
return EFI_EXIT(EFI_DEVICE_ERROR);
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 03/14] efi: loader: bootm: free the devicetree after installing it
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 01/14] mfd: hgs-efi: do not claim the name of the EFI loader's device Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 02/14] efi: loader: file: report EFI_UNSUPPORTED for volumes without a filesystem Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 04/14] efi: loader: provide EFI_DT_FIXUP_PROTOCOL Johannes Schneider
` (10 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
bootm_get_devicetree() returns a buffer the caller has to free, and
efi_install_fdt() copies it into EFI memory. Free it once installed.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/bootm.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/efi/loader/bootm.c b/efi/loader/bootm.c
index a68db742e1..425e61fd70 100644
--- a/efi/loader/bootm.c
+++ b/efi/loader/bootm.c
@@ -245,7 +245,9 @@ static int efi_loader_bootm(struct image_data *data)
goto out;
}
if (fdt) {
+ /* efi_install_fdt() installs a copy */
ret = efi_install_fdt(fdt);
+ free(fdt);
if (ret)
goto out;
}
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 04/14] efi: loader: provide EFI_DT_FIXUP_PROTOCOL
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (2 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 03/14] efi: loader: bootm: free the devicetree after installing it Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 05/14] efi: loader: pe: add helpers for the image size and a named section Johannes Schneider
` (9 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
A payload that installs its own devicetree, like systemd-stub with a
UKI's .dtb or .dtbauto sections, replaces the one barebox installed and
with it every barebox fixup: machine compatible, serial number, OP-TEE
reservations, global overlays. systemd-stub asks the firmware to apply
them through EFI_DT_FIXUP_PROTOCOL, of which barebox only has the GUID.
Implement it on the root node:
- APPLY_FIXUPS runs of_fix_tree() on the payload's devicetree and
returns EFI_BUFFER_TOO_SMALL with the required size if the result
does not fit. The result is kept for the retry, so fixups and overlays
run only once.
- RESERVE_MEMORY keeps boot-services allocations out of the memreserve
entries and /reserved-memory nodes. The regions are requested as
boot-services data, as the EFI loader cannot allocate
EFI_RESERVED_TYPE; that suffices, because the kernel honours the
devicetree reservations after ExitBootServices(). Regions barebox
already holds, such as OP-TEE's, are skipped.
- INSTALL_TABLE installs the result as configuration table.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/protocols/Kconfig | 9 ++
efi/loader/protocols/Makefile | 1 +
efi/loader/protocols/dt_fixup.c | 185 ++++++++++++++++++++++++++++++++
include/efi/protocol/dt_fixup.h | 21 ++++
4 files changed, 216 insertions(+)
create mode 100644 efi/loader/protocols/dt_fixup.c
create mode 100644 include/efi/protocol/dt_fixup.h
diff --git a/efi/loader/protocols/Kconfig b/efi/loader/protocols/Kconfig
index ad7896065a..bc0d3adc8f 100644
--- a/efi/loader/protocols/Kconfig
+++ b/efi/loader/protocols/Kconfig
@@ -71,4 +71,13 @@ config EFI_LOADER_DEVICE_PATH_UTIL
The device path utilities protocol creates and manipulates device
paths and device nodes. It is required to run the EFI Shell.
+config EFI_LOADER_DT_FIXUP
+ bool "EFI_DT_FIXUP_PROTOCOL support"
+ depends on OFTREE
+ default y
+ help
+ Provide EFI_DT_FIXUP_PROTOCOL, so that an EFI payload installing its
+ own devicetree (like systemd-stub from a UKI's .dtb/.dtbauto section)
+ gets the barebox devicetree fixups and overlays applied to it.
+
endmenu
diff --git a/efi/loader/protocols/Makefile b/efi/loader/protocols/Makefile
index d0b55bde46..ffc86ce84d 100644
--- a/efi/loader/protocols/Makefile
+++ b/efi/loader/protocols/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_EFI_LOADER_UNICODE_COLLATION_PROTOCOL2) += unicode_collation.o
obj-$(CONFIG_EFI_LOADER_RNG) += rng.o
obj-$(CONFIG_EFI_LOADER_DEVICE_PATH_UTIL) += device_path_utilities.o
obj-$(CONFIG_EFI_LOADER_DEVICE_PATH_TO_TEXT) += device_path_to_text.o
+obj-$(CONFIG_EFI_LOADER_DT_FIXUP) += dt_fixup.o
diff --git a/efi/loader/protocols/dt_fixup.c b/efi/loader/protocols/dt_fixup.c
new file mode 100644
index 0000000000..fe48d4a160
--- /dev/null
+++ b/efi/loader/protocols/dt_fixup.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * EFI_DT_FIXUP_PROTOCOL: apply barebox's fixups and overlays to a devicetree
+ * an EFI payload brings itself
+ */
+
+#define pr_fmt(fmt) "efi-loader: dt-fixup: " fmt
+
+#include <common.h>
+#include <init.h>
+#include <malloc.h>
+#include <memory.h>
+#include <of.h>
+#include <linux/libfdt.h>
+#include <efi/loader.h>
+#include <efi/memory.h>
+#include <crc.h>
+#include <linux/err.h>
+#include <efi/protocol/dt_fixup.h>
+#include <efi/loader/table.h>
+#include <efi/loader/trace.h>
+#include <efi/guid.h>
+#include <efi/error.h>
+
+static void dt_fixup_reserve(u64 addr, u64 size)
+{
+ u64 start = ALIGN_DOWN(addr, EFI_PAGE_SIZE);
+ u64 len = ALIGN(addr + size, EFI_PAGE_SIZE) - start;
+
+ /* only needs to hold until ExitBootServices() */
+ if (!request_sdram_region_silent("dt-reserved", start, len,
+ efi_memory_type_to_resource_type(EFI_BOOT_SERVICES_DATA),
+ MEMATTRS_RW))
+ pr_debug("0x%llx+0x%llx already in use\n", addr, size);
+}
+
+/* Keep boot-services allocations out of what the devicetree reserves */
+static void dt_fixup_reserve_memory(const void *fdt)
+{
+ int i, node, sub, len, na, ns, entry;
+ u64 addr, size;
+
+ for (i = 0; i < fdt_num_mem_rsv(fdt); i++) {
+ if (!fdt_get_mem_rsv(fdt, i, &addr, &size) && size)
+ dt_fixup_reserve(addr, size);
+ }
+
+ node = fdt_path_offset(fdt, "/reserved-memory");
+ if (node < 0)
+ return;
+
+ na = fdt_address_cells(fdt, node);
+ ns = fdt_size_cells(fdt, node);
+ if (na < 1 || na > 2 || ns < 1 || ns > 2) {
+ pr_warn("/reserved-memory: unsupported #address-cells/#size-cells\n");
+ return;
+ }
+ entry = (na + ns) * sizeof(fdt32_t);
+
+ fdt_for_each_subnode(sub, fdt, node) {
+ const fdt32_t *reg = fdt_getprop(fdt, sub, "reg", &len);
+
+ if (!reg)
+ continue;
+
+ for (; len >= entry; len -= entry) {
+ addr = of_read_number(reg, na);
+ size = of_read_number(reg + na, ns);
+ reg += na + ns;
+ if (size)
+ dt_fixup_reserve(addr, size);
+ }
+ }
+}
+
+/* Kept for a caller retrying with a larger buffer, so fixups run only once */
+static struct {
+ void *fixed;
+ size_t in_len;
+ u32 in_crc;
+} dt_fixup_pending;
+
+static void *dt_fixup_apply(const void *dtb)
+{
+ size_t len = fdt_totalsize(dtb);
+ u32 crc = crc32(0, dtb, len);
+ struct device_node *root;
+ void *fixed;
+
+ if (dt_fixup_pending.fixed && dt_fixup_pending.in_len == len &&
+ dt_fixup_pending.in_crc == crc) {
+ fixed = dt_fixup_pending.fixed;
+ dt_fixup_pending.fixed = NULL;
+ return fixed;
+ }
+
+ root = of_unflatten_dtb(dtb, len);
+ if (IS_ERR(root))
+ return ERR_CAST(root);
+
+ of_fix_tree(root);
+
+ fixed = of_flatten_dtb(root);
+ of_delete_node(root);
+ if (!fixed)
+ return ERR_PTR(-ENOMEM);
+
+ fdt_add_reserve_map(fixed);
+
+ free(dt_fixup_pending.fixed);
+ dt_fixup_pending.fixed = NULL;
+ dt_fixup_pending.in_len = len;
+ dt_fixup_pending.in_crc = crc;
+
+ return fixed;
+}
+
+static efi_status_t EFIAPI dt_fixup(struct efi_dt_fixup_protocol *this,
+ void *dtb, size_t *buffer_size, u32 flags)
+{
+ efi_status_t ret = EFI_SUCCESS;
+ void *fixed = NULL;
+ size_t size;
+
+ EFI_ENTRY("%p, %p, %p, %u", this, dtb, buffer_size, flags);
+
+ if (!this || !dtb || !buffer_size || !flags || (flags & ~EFI_DT_ALL)) {
+ ret = EFI_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (fdt_check_header(dtb)) {
+ ret = EFI_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (flags & EFI_DT_APPLY_FIXUPS) {
+ fixed = dt_fixup_apply(dtb);
+ if (IS_ERR(fixed)) {
+ ret = PTR_ERR(fixed) == -ENOMEM ? EFI_OUT_OF_RESOURCES :
+ EFI_INVALID_PARAMETER;
+ fixed = NULL;
+ goto out;
+ }
+
+ size = fdt_totalsize(fixed);
+ if (size > *buffer_size) {
+ /* the caller retries with a buffer of this size */
+ *buffer_size = size;
+ dt_fixup_pending.fixed = fixed;
+ fixed = NULL;
+ ret = EFI_BUFFER_TOO_SMALL;
+ goto out;
+ }
+
+ memcpy(dtb, fixed, size);
+ } else if (fdt_totalsize(dtb) > *buffer_size) {
+ *buffer_size = fdt_totalsize(dtb);
+ ret = EFI_BUFFER_TOO_SMALL;
+ goto out;
+ }
+
+ if (flags & EFI_DT_RESERVE_MEMORY)
+ dt_fixup_reserve_memory(dtb);
+
+ if (flags & EFI_DT_INSTALL_TABLE)
+ ret = efi_install_configuration_table(&efi_fdt_guid, dtb);
+
+out:
+ free(fixed);
+ return EFI_EXIT(ret);
+}
+
+static struct efi_dt_fixup_protocol efi_dt_fixup_prot = {
+ .revision = EFI_DT_FIXUP_PROTOCOL_REVISION,
+ .fixup = dt_fixup,
+};
+
+static int efi_dt_fixup_init(void)
+{
+ efi_add_root_node_protocol_deferred(&efi_dt_fixup_protocol_guid,
+ &efi_dt_fixup_prot);
+ return 0;
+}
+device_initcall(efi_dt_fixup_init);
diff --git a/include/efi/protocol/dt_fixup.h b/include/efi/protocol/dt_fixup.h
new file mode 100644
index 0000000000..fd418793f6
--- /dev/null
+++ b/include/efi/protocol/dt_fixup.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __EFI_PROTOCOL_DT_FIXUP_H_
+#define __EFI_PROTOCOL_DT_FIXUP_H_
+
+#include <efi/types.h>
+
+#define EFI_DT_FIXUP_PROTOCOL_REVISION 0x00010000
+
+#define EFI_DT_APPLY_FIXUPS 0x00000001
+#define EFI_DT_RESERVE_MEMORY 0x00000002
+#define EFI_DT_INSTALL_TABLE 0x00000004
+#define EFI_DT_ALL (EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY | \
+ EFI_DT_INSTALL_TABLE)
+
+struct efi_dt_fixup_protocol {
+ u64 revision;
+ efi_status_t (EFIAPI *fixup)(struct efi_dt_fixup_protocol *this,
+ void *dtb, size_t *buffer_size, u32 flags);
+};
+
+#endif
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 05/14] efi: loader: pe: add helpers for the image size and a named section
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (3 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 04/14] efi: loader: provide EFI_DT_FIXUP_PROTOCOL Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 06/14] efi: loader: bootm: load only the PE image, not the whole file Johannes Schneider
` (8 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
An EFI image may be read from a raw partition far larger than the
image. Add efi_pe_image_size(), which derives the size as written from
the section and certificate tables and needs only the headers;
efi_pe_file_size(), the same for a buffer holding the whole image; and
efi_pe_find_section() and efi_pe_find_next_section(), which return the
file data of the first or the next section of a name.
All of them parse untrusted images through efi_image_parse_header(). For
efi_pe_image_size() to work on a buffer holding only the headers, the
check that the certificate table lies within the buffer moves from
efi_image_parse_header() to efi_image_parse(), the only caller that
reads the table.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/pe.c | 116 +++++++++++++++++++++++++++++++++++++++-
include/efi/loader/pe.h | 7 +++
2 files changed, 121 insertions(+), 2 deletions(-)
diff --git a/efi/loader/pe.c b/efi/loader/pe.c
index 4b7874fe2a..827b50378c 100644
--- a/efi/loader/pe.c
+++ b/efi/loader/pe.c
@@ -507,8 +507,6 @@ static bool efi_image_parse_header(void *efi, size_t len,
return false;
if (!pe_range_ok(len, 0, *header_sizep))
return false;
- if (*authszp && !pe_range_ok(len, *authoffp, *authszp))
- return false;
if (!pe_range_ok(len, 0, csum_off) ||
!pe_range_ok(len, subsys_off, 0) ||
@@ -570,6 +568,8 @@ bool efi_image_parse(void *efi, size_t len, struct efi_image_regions **regp,
if (!efi_image_parse_header(efi, len, &nt, §ions, &header_size,
&align, &authoff, &authsz))
return false;
+ if (authsz && !pe_range_ok(len, authoff, authsz))
+ return false;
/*
* Count maximum number of regions to be digested.
@@ -780,6 +780,118 @@ static int fuzz_pe(const u8 *data, size_t size)
}
fuzz_test("pe", fuzz_pe);
+/**
+ * efi_pe_image_size() - size of a PE image as written to storage
+ * @efi: buffer holding at least the PE headers
+ * @len: size of @efi
+ *
+ * Return: the end of the last section or of the certificate table, whichever
+ * is further, which may lie beyond @len, or 0 if @efi holds no valid PE
+ * headers.
+ */
+size_t efi_pe_image_size(void *efi, size_t len)
+{
+ IMAGE_NT_HEADERS32 *nt;
+ IMAGE_SECTION_HEADER *sections;
+ u32 header_size, align, authoff, authsz;
+ size_t size, end;
+ int i;
+
+ if (!efi_image_parse_header(efi, len, &nt, §ions, &header_size,
+ &align, &authoff, &authsz))
+ return 0;
+
+ size = header_size;
+
+ for (i = 0; i < nt->FileHeader.sections; i++) {
+ if (check_add_overflow((size_t)sections[i].PointerToRawData,
+ (size_t)sections[i].SizeOfRawData, &end))
+ return 0;
+ size = max(size, end);
+ }
+
+ if (check_add_overflow((size_t)authoff, (size_t)authsz, &end))
+ return 0;
+
+ return max(size, end);
+}
+
+/**
+ * efi_pe_file_size() - size of a PE image held completely in a buffer
+ * @efi: buffer holding the image
+ * @len: size of @efi, possibly larger than the image
+ *
+ * Return: efi_pe_image_size(), or 0 if the image does not fit into @len.
+ */
+size_t efi_pe_file_size(void *efi, size_t len)
+{
+ size_t size = efi_pe_image_size(efi, len);
+
+ return size <= len ? size : 0;
+}
+
+/**
+ * efi_pe_find_next_section() - locate the next section of a name
+ * @efi: PE image
+ * @len: size of @efi
+ * @name: section name, at most 8 characters
+ * @size: returns the size of the section data
+ * @index: section table index to start at; set past the section found
+ *
+ * Return: pointer to the section data in @efi, or NULL if there is none.
+ */
+const void *efi_pe_find_next_section(void *efi, size_t len, const char *name,
+ size_t *size, int *index)
+{
+ IMAGE_NT_HEADERS32 *nt;
+ IMAGE_SECTION_HEADER *sections;
+ u32 header_size, align, authoff, authsz;
+ size_t namelen = strlen(name), sz;
+ int i;
+
+ if (namelen > 8)
+ return NULL;
+
+ if (!efi_image_parse_header(efi, len, &nt, §ions, &header_size,
+ &align, &authoff, &authsz))
+ return NULL;
+
+ for (i = *index; i < nt->FileHeader.sections; i++) {
+ if (memcmp(sections[i].Name, name, namelen) ||
+ (namelen < 8 && sections[i].Name[namelen]))
+ continue;
+
+ sz = sections[i].Misc.VirtualSize ?: sections[i].SizeOfRawData;
+ sz = min_t(size_t, sz, sections[i].SizeOfRawData);
+ if (!pe_range_ok(len, sections[i].PointerToRawData, sz))
+ return NULL;
+
+ *size = sz;
+ *index = i + 1;
+ return efi + sections[i].PointerToRawData;
+ }
+
+ return NULL;
+}
+
+/**
+ * efi_pe_find_section() - locate a section's file data by name
+ * @efi: PE image
+ * @len: size of @efi
+ * @name: section name, at most 8 characters
+ * @size: returns the size of the section data
+ *
+ * Return: pointer to the first section's data in @efi, or NULL if there is
+ * none.
+ */
+const void *efi_pe_find_section(void *efi, size_t len, const char *name,
+ size_t *size)
+{
+ int index = 0;
+
+ return efi_pe_find_next_section(efi, len, name, size, &index);
+}
+
#ifdef CONFIG_EFI_LOADER
static bool efi_image_authenticate(void *efi, size_t efi_size)
{
diff --git a/include/efi/loader/pe.h b/include/efi/loader/pe.h
index b99f517cbf..2d2a19604f 100644
--- a/include/efi/loader/pe.h
+++ b/include/efi/loader/pe.h
@@ -73,6 +73,13 @@ void *efi_prepare_aligned_image(void *efi, u64 *efi_size);
bool efi_image_parse(void *efi, size_t len, struct efi_image_regions **regp,
struct _WIN_CERTIFICATE **auth, size_t *auth_len);
+size_t efi_pe_image_size(void *efi, size_t len);
+size_t efi_pe_file_size(void *efi, size_t len);
+const void *efi_pe_find_next_section(void *efi, size_t len, const char *name,
+ size_t *size, int *index);
+const void *efi_pe_find_section(void *efi, size_t len, const char *name,
+ size_t *size);
+
/* Check if a buffer contains a PE-COFF image */
efi_status_t efi_check_pe(void *buffer, size_t size, void **nt_header);
/* PE loader implementation */
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 06/14] efi: loader: bootm: load only the PE image, not the whole file
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (4 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 05/14] efi: loader: pe: add helpers for the image size and a named section Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 07/14] efi: loader: bootm: pass the kernel command line to UKIs Johannes Schneider
` (7 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
With global.bootm.image pointing at a raw partition, bootm loads all
of it, and hands the trailing data to efiloader_load_image() as part
of the image.
Read the PE headers first and load only as much as efi_pe_image_size()
reports. This saves on boot time, since only the actually needed data
is loaded.
Files whose headers do not parse are loaded in full as before.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/bootm.c | 56 +++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 53 insertions(+), 3 deletions(-)
diff --git a/efi/loader/bootm.c b/efi/loader/bootm.c
index 425e61fd70..18539565fd 100644
--- a/efi/loader/bootm.c
+++ b/efi/loader/bootm.c
@@ -35,6 +35,8 @@
#include <efi/error.h>
#include <efi/initrd.h>
#include <efi/devicepath.h>
+#include <efi/loader/pe.h>
+#include <loadable.h>
/**
* copy_fdt() - Copy the device tree to a new location available to EFI
@@ -193,6 +195,48 @@ static efi_status_t efi_install_initrd(struct image_data *data,
return EFI_SUCCESS;
}
+/* The image may sit in a much larger partition: load only the image */
+static const struct resource *efi_load_os(struct image_data *data,
+ resource_size_t start,
+ resource_size_t end)
+{
+ size_t len = SZ_64K, size = 0;
+ struct resource *res;
+ ssize_t now;
+ void *hdr;
+
+ if (data->os_res || !data->os || loadable_count(data->os) > 1)
+ return bootm_load_os(data, start, end);
+
+ hdr = xmalloc(len);
+ now = loadable_extract_into_buf(data->os, hdr, len, 0,
+ LOADABLE_EXTRACT_PARTIAL);
+ if (now > 0)
+ size = efi_pe_image_size(hdr, now);
+ free(hdr);
+
+ if (!size)
+ return bootm_load_os(data, start, end);
+ if (size > end - start + 1)
+ return ERR_PTR(-ENOSPC);
+
+ res = request_sdram_region("kernel", start, size, MEMTYPE_LOADER_CODE,
+ MEMATTRS_RWX);
+ if (!res)
+ return ERR_PTR(-EBUSY);
+
+ now = loadable_extract_into_buf(data->os, (void *)res->start, size, 0,
+ LOADABLE_EXTRACT_PARTIAL);
+ if (now != size) {
+ release_sdram_region(res);
+ return ERR_PTR(now < 0 ? now : -EIO);
+ }
+
+ data->os_res = res;
+
+ return res;
+}
+
static int efi_loader_bootm(struct image_data *data)
{
const struct resource *os_res;
@@ -208,13 +252,20 @@ static int efi_loader_bootm(struct image_data *data)
int ret = 0;
void *fdt;
int flags = 0;
+ size_t size;
memory_bank_first_find_space(&start, &end);
- os_res = bootm_load_os(data, start, end);
+ os_res = efi_load_os(data, start, end);
if (IS_ERR(os_res))
return PTR_ERR(os_res);
+ size = efi_pe_file_size((void *)os_res->start, resource_size(os_res));
+ if (!size) {
+ pr_err("%s is not a PE image\n", data->os_file);
+ return -EINVAL;
+ }
+
if (filetype_is_linux_efi_image(data->kernel_type)) {
const char *options;
@@ -262,8 +313,7 @@ static int efi_loader_bootm(struct image_data *data)
flags |= EFI_DRYRUN;
efiret = efiloader_load_image(false, efi_root, file_path,
- (void *)os_res->start,
- resource_size(os_res), &handle);
+ (void *)os_res->start, size, &handle);
if (efiret != EFI_SUCCESS) {
pr_err("Loading image failed\n");
goto out;
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 07/14] efi: loader: bootm: pass the kernel command line to UKIs
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (5 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 06/14] efi: loader: bootm: load only the PE image, not the whole file Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-05 17:18 ` Ahmad Fatoum
2026-10-04 1:19 ` [PATCH v1 08/14] efi: loader: verify Authenticode signatures against built-in keys Johannes Schneider
` (6 subsequent siblings)
13 siblings, 1 reply; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
Only Linux EFI images get the barebox kernel command line as load
options. A UKI is a plain EFI application, so root=, bootchooser.active=
and the other bootargs never reach its kernel. Pass them to every image
with a .linux section too: systemd-stub uses the load options as kernel
command line unless UEFI Secure Boot is enabled, which barebox never
reports.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/bootm.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/efi/loader/bootm.c b/efi/loader/bootm.c
index 18539565fd..8a83865458 100644
--- a/efi/loader/bootm.c
+++ b/efi/loader/bootm.c
@@ -252,7 +252,7 @@ static int efi_loader_bootm(struct image_data *data)
int ret = 0;
void *fdt;
int flags = 0;
- size_t size;
+ size_t size, section_size;
memory_bank_first_find_space(&start, &end);
@@ -266,7 +266,10 @@ static int efi_loader_bootm(struct image_data *data)
return -EINVAL;
}
- if (filetype_is_linux_efi_image(data->kernel_type)) {
+ /* systemd-stub passes the load options on as kernel command line */
+ if (filetype_is_linux_efi_image(data->kernel_type) ||
+ efi_pe_find_section((void *)os_res->start, size, ".linux",
+ §ion_size)) {
const char *options;
options = linux_bootargs_get();
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH v1 07/14] efi: loader: bootm: pass the kernel command line to UKIs
2026-10-04 1:19 ` [PATCH v1 07/14] efi: loader: bootm: pass the kernel command line to UKIs Johannes Schneider
@ 2026-10-05 17:18 ` Ahmad Fatoum
0 siblings, 0 replies; 19+ messages in thread
From: Ahmad Fatoum @ 2026-10-05 17:18 UTC (permalink / raw)
To: Johannes Schneider, barebox; +Cc: Marco Felsch
Hi,
On 10/4/26 03:19, Johannes Schneider wrote:
> Only Linux EFI images get the barebox kernel command line as load
> options. A UKI is a plain EFI application, so root=, bootchooser.active=
> and the other bootargs never reach its kernel. Pass them to every image
> with a .linux section too: systemd-stub uses the load options as kernel
> command line unless UEFI Secure Boot is enabled, which barebox never
> reports.
>
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
> ---
> efi/loader/bootm.c | 7 +++++--
> 1 file changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/efi/loader/bootm.c b/efi/loader/bootm.c
> index 18539565fd..8a83865458 100644
> --- a/efi/loader/bootm.c
> +++ b/efi/loader/bootm.c
> @@ -252,7 +252,7 @@ static int efi_loader_bootm(struct image_data *data)
> int ret = 0;
> void *fdt;
> int flags = 0;
> - size_t size;
> + size_t size, section_size;
>
> memory_bank_first_find_space(&start, &end);
>
> @@ -266,7 +266,10 @@ static int efi_loader_bootm(struct image_data *data)
> return -EINVAL;
> }
>
> - if (filetype_is_linux_efi_image(data->kernel_type)) {
> + /* systemd-stub passes the load options on as kernel command line */
> + if (filetype_is_linux_efi_image(data->kernel_type) ||
> + efi_pe_find_section((void *)os_res->start, size, ".linux",
I think this will complicate matters, because afaik systemd-stub expects the
selector for the profile (e.g. @1) to be the very first option and by passing
all options as-is, we complicate being able to choose the selector.
I am also unsure what the behavior is when there is both a .cmdline section
and extra load options. Did you check?
Here's what I had in mind (I will send a v2 next week or so):
https://lore.barebox.org/barebox/20260826121956.2936414-1-a.fatoum@pengutronix.de/
Cheers,
Ahmad
> + §ion_size)) {
> const char *options;
>
> options = linux_bootargs_get();
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v1 08/14] efi: loader: verify Authenticode signatures against built-in keys
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (6 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 07/14] efi: loader: bootm: pass the kernel command line to UKIs Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-05 5:33 ` Ahmad Fatoum
2026-10-04 1:19 ` [PATCH v1 09/14] efi: loader: authenticode: add a fuzz test Johannes Schneider
` (5 subsequent siblings)
13 siblings, 1 reply; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
barebox's EFI loader verifies no signatures: efi_image_authenticate()
accepts every image. Add efi_authenticode_verify() as the verifier for
signed EFI images: compute the Authenticode digest over the regions
efi_image_parse() collects, check it against the SpcIndirectDataContent
of the PKCS#7 signature, check the messageDigest attribute against the
digest of that content, and verify the signature over the attributes
with the keys of a barebox keyring. The following commits use it.
barebox has no ASN.1 decoder: keys are converted from certificates at
build time, and FIT signatures carry none. The PKCS#7 structure is
walked as plain DER for the fields needed, with every length checked
against what remains; the certificates it carries are skipped.
Supported are one signer, SHA-256 and RSA. As for FIT images, trust
comes from the keyring, not from X.509 chains or db/dbx.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/Kconfig | 16 ++
efi/loader/Makefile | 1 +
efi/loader/authenticode.c | 435 ++++++++++++++++++++++++++++++
include/efi/loader/authenticode.h | 9 +
4 files changed, 461 insertions(+)
create mode 100644 efi/loader/authenticode.c
create mode 100644 include/efi/loader/authenticode.h
diff --git a/efi/loader/Kconfig b/efi/loader/Kconfig
index 5692e54ebe..4099da0689 100644
--- a/efi/loader/Kconfig
+++ b/efi/loader/Kconfig
@@ -24,6 +24,22 @@ config EFI_LOADER_DEBUG_SUPPORT
config EFI_LOADER_SECURE_BOOT
bool
+config EFI_LOADER_AUTHENTICODE
+ bool "Verify Authenticode signatures of booted EFI images"
+ depends on CRYPTO_BUILTIN_KEYS && HAVE_DIGEST_SHA256
+ select CRYPTO_RSA
+ help
+ Verify the Authenticode (PKCS#7, SHA-256, RSA) signature of an EFI
+ image booted with bootm against the keys compiled into the "efi"
+ keyring (CONFIG_CRYPTO_PUBLIC_KEYS, keyring=efi). With signed images
+ forced, an EFI image then boots only if one of those keys verifies
+ it, the same way a FIT image must carry a valid signature.
+
+ X.509 certificates in the signature are not evaluated: trust is
+ anchored in the keyring. barebox does not report UEFI Secure Boot
+ to the payload, so a UKI keeps taking its command line from
+ barebox.
+
menu "UEFI services"
config EFI_LOADER_GET_TIME
diff --git a/efi/loader/Makefile b/efi/loader/Makefile
index 24850e87b1..775014dc22 100644
--- a/efi/loader/Makefile
+++ b/efi/loader/Makefile
@@ -14,6 +14,7 @@ obj-y += boot.o
obj-y += runtime.o
obj-y += setup.o
obj-y += watchdog.o
+obj-$(CONFIG_EFI_LOADER_AUTHENTICODE) += authenticode.o
obj-y += loadopts.o
obj-y += efi_var_common.o
obj-y += efi_variable.o
diff --git a/efi/loader/authenticode.c b/efi/loader/authenticode.c
new file mode 100644
index 0000000000..36e5a46fc6
--- /dev/null
+++ b/efi/loader/authenticode.c
@@ -0,0 +1,435 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Authenticode verification of PE images against barebox built-in keys:
+ * one signer, SHA-256 and RSA, the certificates in the signature are ignored
+ */
+
+#define pr_fmt(fmt) "efi-loader: authenticode: " fmt
+
+#include <common.h>
+#include <digest.h>
+#include <crypto/sha.h>
+#include <malloc.h>
+#include <crypto/public_key.h>
+#include <efi/loader/pe.h>
+#include <efi/loader/authenticode.h>
+#include <efi/error.h>
+#include <pe.h>
+
+struct der {
+ const u8 *p;
+ const u8 *end;
+};
+
+struct der_elem {
+ u8 tag;
+ const u8 *start; /* tag byte */
+ const u8 *val;
+ size_t len;
+ size_t total; /* tag + length + value */
+};
+
+#define DER_INTEGER 0x02
+#define DER_OCTET 0x04
+#define DER_OID 0x06
+#define DER_SEQ 0x30
+#define DER_SET 0x31
+#define DER_CTX0 0xa0
+#define DER_CTX1 0xa1
+
+static const u8 oid_signed_data[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 };
+static const u8 oid_spc_indirect_data[] = {
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x04
+};
+static const u8 oid_sha256[] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 };
+static const u8 oid_content_type[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x03 };
+static const u8 oid_message_digest[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x04 };
+
+static int der_next(struct der *d, struct der_elem *e)
+{
+ const u8 *p = d->p;
+ size_t len, n;
+
+ if (d->end - p < 2)
+ return -EBADMSG;
+
+ e->start = p;
+ e->tag = *p++;
+ if ((e->tag & 0x1f) == 0x1f)
+ return -EBADMSG;
+
+ len = *p++;
+ if (len & 0x80) {
+ n = len & 0x7f;
+ if (!n || n > 4 || d->end - p < n)
+ return -EBADMSG;
+ len = 0;
+ while (n--)
+ len = (len << 8) | *p++;
+ }
+
+ if (d->end - p < len)
+ return -EBADMSG;
+
+ e->val = p;
+ e->len = len;
+ e->total = p + len - e->start;
+ d->p = p + len;
+
+ return 0;
+}
+
+static int der_expect(struct der *d, u8 tag, struct der_elem *e)
+{
+ int ret = der_next(d, e);
+
+ if (ret)
+ return ret;
+
+ return e->tag == tag ? 0 : -EBADMSG;
+}
+
+static struct der der_enter(const struct der_elem *e)
+{
+ return (struct der) { .p = e->val, .end = e->val + e->len };
+}
+
+static bool der_oid_is(const struct der_elem *e, const u8 *oid, size_t len)
+{
+ return e->tag == DER_OID && e->len == len && !memcmp(e->val, oid, len);
+}
+
+/* AlgorithmIdentifier ::= SEQUENCE { OID, parameters OPTIONAL } */
+static int der_expect_sha256(struct der *d)
+{
+ struct der_elem seq, oid;
+ struct der in;
+ int ret;
+
+ ret = der_expect(d, DER_SEQ, &seq);
+ if (ret)
+ return ret;
+
+ in = der_enter(&seq);
+ ret = der_expect(&in, DER_OID, &oid);
+ if (ret)
+ return ret;
+
+ return der_oid_is(&oid, oid_sha256, sizeof(oid_sha256)) ? 0 : -EOPNOTSUPP;
+}
+
+struct authenticode {
+ const u8 *pe_digest; /* SpcIndirectDataContent.messageDigest */
+ const u8 *spc; /* SpcIndirectDataContent content octets */
+ size_t spc_len;
+ const u8 *attrs; /* [0] IMPLICIT authenticatedAttributes */
+ size_t attrs_len;
+ const u8 *attr_digest; /* messageDigest attribute value */
+ bool attr_content_type_ok;
+ const u8 *sig;
+ size_t sig_len;
+};
+
+static int authenticode_parse_attrs(struct authenticode *a,
+ const struct der_elem *attrs)
+{
+ struct der in = der_enter(attrs);
+ struct der_elem attr, oid, set, val;
+ struct der ain, sin;
+ int ret;
+
+ while (in.p < in.end) {
+ ret = der_expect(&in, DER_SEQ, &attr);
+ if (ret)
+ return ret;
+
+ ain = der_enter(&attr);
+ ret = der_expect(&ain, DER_OID, &oid);
+ if (ret)
+ return ret;
+ ret = der_expect(&ain, DER_SET, &set);
+ if (ret)
+ return ret;
+ sin = der_enter(&set);
+
+ if (der_oid_is(&oid, oid_message_digest, sizeof(oid_message_digest))) {
+ ret = der_expect(&sin, DER_OCTET, &val);
+ if (ret || val.len != SHA256_DIGEST_SIZE)
+ return -EBADMSG;
+ a->attr_digest = val.val;
+ } else if (der_oid_is(&oid, oid_content_type, sizeof(oid_content_type))) {
+ ret = der_expect(&sin, DER_OID, &val);
+ if (ret)
+ return ret;
+ a->attr_content_type_ok =
+ der_oid_is(&val, oid_spc_indirect_data,
+ sizeof(oid_spc_indirect_data));
+ }
+ }
+
+ return a->attr_digest ? 0 : -EBADMSG;
+}
+
+static int authenticode_parse(struct authenticode *a, const void *buf, size_t len)
+{
+ struct der d = { .p = buf, .end = (const u8 *)buf + len };
+ struct der_elem e, ci, sd, spc, dinfo, si;
+ struct der in, sdin, ciin, spcin, dinin, siin;
+ int ret;
+
+ /* ContentInfo ::= SEQUENCE { contentType, [0] EXPLICIT content } */
+ ret = der_expect(&d, DER_SEQ, &ci);
+ if (ret)
+ return ret;
+ in = der_enter(&ci);
+ ret = der_expect(&in, DER_OID, &e);
+ if (ret)
+ return ret;
+ if (!der_oid_is(&e, oid_signed_data, sizeof(oid_signed_data)))
+ return -EBADMSG;
+ ret = der_expect(&in, DER_CTX0, &e);
+ if (ret)
+ return ret;
+ in = der_enter(&e);
+
+ /* SignedData ::= SEQUENCE { version, digestAlgorithms, contentInfo, ... } */
+ ret = der_expect(&in, DER_SEQ, &sd);
+ if (ret)
+ return ret;
+ sdin = der_enter(&sd);
+ ret = der_expect(&sdin, DER_INTEGER, &e);
+ if (ret)
+ return ret;
+ ret = der_expect(&sdin, DER_SET, &e);
+ if (ret)
+ return ret;
+
+ /* contentInfo: SPC_INDIRECT_DATA carrying the PE image digest */
+ ret = der_expect(&sdin, DER_SEQ, &e);
+ if (ret)
+ return ret;
+ ciin = der_enter(&e);
+ ret = der_expect(&ciin, DER_OID, &e);
+ if (ret)
+ return ret;
+ if (!der_oid_is(&e, oid_spc_indirect_data, sizeof(oid_spc_indirect_data)))
+ return -EBADMSG;
+ ret = der_expect(&ciin, DER_CTX0, &e);
+ if (ret)
+ return ret;
+ ciin = der_enter(&e);
+ ret = der_expect(&ciin, DER_SEQ, &spc);
+ if (ret)
+ return ret;
+ a->spc = spc.val;
+ a->spc_len = spc.len;
+
+ spcin = der_enter(&spc);
+ ret = der_expect(&spcin, DER_SEQ, &e); /* SpcAttributeTypeAndOptionalValue */
+ if (ret)
+ return ret;
+ ret = der_expect(&spcin, DER_SEQ, &dinfo); /* DigestInfo */
+ if (ret)
+ return ret;
+ dinin = der_enter(&dinfo);
+ ret = der_expect_sha256(&dinin);
+ if (ret)
+ return ret;
+ ret = der_expect(&dinin, DER_OCTET, &e);
+ if (ret || e.len != SHA256_DIGEST_SIZE)
+ return -EBADMSG;
+ a->pe_digest = e.val;
+
+ /* skip optional certificates [0] and crls [1] */
+ do {
+ ret = der_next(&sdin, &e);
+ if (ret)
+ return ret;
+ } while (e.tag == DER_CTX0 || e.tag == DER_CTX1);
+
+ if (e.tag != DER_SET)
+ return -EBADMSG;
+
+ /* first SignerInfo only */
+ in = der_enter(&e);
+ ret = der_expect(&in, DER_SEQ, &si);
+ if (ret)
+ return ret;
+ siin = der_enter(&si);
+ ret = der_expect(&siin, DER_INTEGER, &e);
+ if (ret)
+ return ret;
+ ret = der_expect(&siin, DER_SEQ, &e); /* issuerAndSerialNumber */
+ if (ret)
+ return ret;
+ ret = der_expect_sha256(&siin);
+ if (ret)
+ return ret;
+
+ ret = der_expect(&siin, DER_CTX0, &e);
+ if (ret)
+ return ret;
+ a->attrs = e.start;
+ a->attrs_len = e.total;
+ ret = authenticode_parse_attrs(a, &e);
+ if (ret)
+ return ret;
+
+ ret = der_expect(&siin, DER_SEQ, &e); /* digestEncryptionAlgorithm */
+ if (ret)
+ return ret;
+ ret = der_expect(&siin, DER_OCTET, &e);
+ if (ret)
+ return ret;
+ a->sig = e.val;
+ a->sig_len = e.len;
+
+ return 0;
+}
+
+static int sha256_regions(const struct efi_image_regions *regs, u8 *out)
+{
+ struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
+ int i, ret;
+
+ if (!d)
+ return -EOPNOTSUPP;
+
+ ret = digest_init(d);
+ for (i = 0; !ret && i < regs->num; i++)
+ ret = digest_update(d, regs->reg[i].data, regs->reg[i].size);
+ if (!ret)
+ ret = digest_final(d, out);
+
+ digest_free(d);
+ return ret;
+}
+
+static int sha256_buf(const void *buf, size_t len, u8 *out)
+{
+ struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
+ int ret;
+
+ if (!d)
+ return -EOPNOTSUPP;
+
+ ret = digest_digest(d, buf, len, out);
+ digest_free(d);
+ return ret;
+}
+
+/* The signature covers the attributes DER-encoded as SET OF, not as [0] */
+static int sha256_attrs(const struct authenticode *a, u8 *out)
+{
+ struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
+ const u8 set_tag = DER_SET;
+ int ret;
+
+ if (!d)
+ return -EOPNOTSUPP;
+
+ ret = digest_init(d);
+ if (!ret)
+ ret = digest_update(d, &set_tag, 1);
+ if (!ret)
+ ret = digest_update(d, a->attrs + 1, a->attrs_len - 1);
+ if (!ret)
+ ret = digest_final(d, out);
+
+ digest_free(d);
+ return ret;
+}
+
+/**
+ * efi_authenticode_verify() - verify a PE image's Authenticode signature
+ * @efi: PE image
+ * @len: exact size of the image, see efi_pe_file_size()
+ * @keyring: barebox keyring holding the trusted keys
+ *
+ * Return: 0 if the image is signed by a key in @keyring, negative error code
+ * otherwise.
+ */
+int efi_authenticode_verify(void *efi, size_t len, const char *keyring)
+{
+ struct efi_image_regions *regs = NULL;
+ const struct public_key *key;
+ struct authenticode a = {};
+ WIN_CERTIFICATE *wincert;
+ size_t auth_len;
+ u8 pe_hash[SHA256_DIGEST_SIZE], hash[SHA256_DIGEST_SIZE];
+ const struct keyring *kr;
+ int ret;
+
+ if (!efi_image_parse(efi, len, ®s, &wincert, &auth_len))
+ return -EBADMSG;
+
+ if (!wincert) {
+ pr_err("image is not signed\n");
+ ret = -ENOKEY;
+ goto out;
+ }
+
+ if (wincert->dwLength > auth_len || wincert->dwLength <= sizeof(*wincert) ||
+ wincert->wRevision != WIN_CERT_REVISION_2_0 ||
+ wincert->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA) {
+ pr_err("unsupported certificate table entry\n");
+ ret = -EBADMSG;
+ goto out;
+ }
+
+ ret = authenticode_parse(&a, wincert + 1, wincert->dwLength - sizeof(*wincert));
+ if (ret) {
+ pr_err("cannot parse signature: %pe\n", ERR_PTR(ret));
+ goto out;
+ }
+
+ if (!a.attr_content_type_ok) {
+ pr_err("signed content is not SpcIndirectDataContent\n");
+ ret = -EBADMSG;
+ goto out;
+ }
+
+ ret = sha256_regions(regs, pe_hash);
+ if (ret)
+ goto out;
+ if (memcmp(pe_hash, a.pe_digest, sizeof(pe_hash))) {
+ pr_err("image digest mismatch\n");
+ ret = -EBADMSG;
+ goto out;
+ }
+
+ ret = sha256_buf(a.spc, a.spc_len, hash);
+ if (ret)
+ goto out;
+ if (memcmp(hash, a.attr_digest, sizeof(hash))) {
+ pr_err("signed attributes do not match the content\n");
+ ret = -EBADMSG;
+ goto out;
+ }
+
+ ret = sha256_attrs(&a, hash);
+ if (ret)
+ goto out;
+
+ kr = keyring_find(keyring);
+ if (!kr) {
+ pr_err("keyring '%s' not registered\n", keyring);
+ ret = -ENOKEY;
+ goto out;
+ }
+
+ ret = -ENOKEY;
+ for_each_key_in_keyring(key, kr) {
+ if (!public_key_verify(key, a.sig, a.sig_len, hash, HASH_ALGO_SHA256)) {
+ pr_info("verified with key '%s'\n", key->key_name_hint ?: "?");
+ ret = 0;
+ break;
+ }
+ }
+
+ if (ret)
+ pr_err("no key in keyring '%s' verifies the signature\n", keyring);
+out:
+ free(regs);
+ return ret;
+}
diff --git a/include/efi/loader/authenticode.h b/include/efi/loader/authenticode.h
new file mode 100644
index 0000000000..24c46ca7cf
--- /dev/null
+++ b/include/efi/loader/authenticode.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __EFI_LOADER_AUTHENTICODE_H
+#define __EFI_LOADER_AUTHENTICODE_H
+
+#include <linux/types.h>
+
+int efi_authenticode_verify(void *efi, size_t len, const char *keyring);
+
+#endif
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH v1 08/14] efi: loader: verify Authenticode signatures against built-in keys
2026-10-04 1:19 ` [PATCH v1 08/14] efi: loader: verify Authenticode signatures against built-in keys Johannes Schneider
@ 2026-10-05 5:33 ` Ahmad Fatoum
2026-10-05 5:45 ` SCHNEIDER Johannes
0 siblings, 1 reply; 19+ messages in thread
From: Ahmad Fatoum @ 2026-10-05 5:33 UTC (permalink / raw)
To: Johannes Schneider, barebox; +Cc: Marco Felsch
Hello Johannes,
On 10/4/26 03:19, Johannes Schneider wrote:
> barebox's EFI loader verifies no signatures: efi_image_authenticate()
> accepts every image. Add efi_authenticode_verify() as the verifier for
> signed EFI images: compute the Authenticode digest over the regions
> efi_image_parse() collects, check it against the SpcIndirectDataContent
> of the PKCS#7 signature, check the messageDigest attribute against the
> digest of that content, and verify the signature over the attributes
> with the keys of a barebox keyring. The following commits use it.
>
> barebox has no ASN.1 decoder: keys are converted from certificates at
> build time, and FIT signatures carry none. The PKCS#7 structure is
> walked as plain DER for the fields needed, with every length checked
> against what remains; the certificates it carries are skipped.
>
> Supported are one signer, SHA-256 and RSA. As for FIT images, trust
> comes from the keyring, not from X.509 chains or db/dbx.
We should import mbedTLS and then make use of its PKCS#7 support.
The goal being mbedTLS being updated regularly like we already do
with dts/
Cheers,
Ahmad
>
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
> ---
> efi/loader/Kconfig | 16 ++
> efi/loader/Makefile | 1 +
> efi/loader/authenticode.c | 435 ++++++++++++++++++++++++++++++
> include/efi/loader/authenticode.h | 9 +
> 4 files changed, 461 insertions(+)
> create mode 100644 efi/loader/authenticode.c
> create mode 100644 include/efi/loader/authenticode.h
>
> diff --git a/efi/loader/Kconfig b/efi/loader/Kconfig
> index 5692e54ebe..4099da0689 100644
> --- a/efi/loader/Kconfig
> +++ b/efi/loader/Kconfig
> @@ -24,6 +24,22 @@ config EFI_LOADER_DEBUG_SUPPORT
> config EFI_LOADER_SECURE_BOOT
> bool
>
> +config EFI_LOADER_AUTHENTICODE
> + bool "Verify Authenticode signatures of booted EFI images"
> + depends on CRYPTO_BUILTIN_KEYS && HAVE_DIGEST_SHA256
> + select CRYPTO_RSA
> + help
> + Verify the Authenticode (PKCS#7, SHA-256, RSA) signature of an EFI
> + image booted with bootm against the keys compiled into the "efi"
> + keyring (CONFIG_CRYPTO_PUBLIC_KEYS, keyring=efi). With signed images
> + forced, an EFI image then boots only if one of those keys verifies
> + it, the same way a FIT image must carry a valid signature.
> +
> + X.509 certificates in the signature are not evaluated: trust is
> + anchored in the keyring. barebox does not report UEFI Secure Boot
> + to the payload, so a UKI keeps taking its command line from
> + barebox.
> +
> menu "UEFI services"
>
> config EFI_LOADER_GET_TIME
> diff --git a/efi/loader/Makefile b/efi/loader/Makefile
> index 24850e87b1..775014dc22 100644
> --- a/efi/loader/Makefile
> +++ b/efi/loader/Makefile
> @@ -14,6 +14,7 @@ obj-y += boot.o
> obj-y += runtime.o
> obj-y += setup.o
> obj-y += watchdog.o
> +obj-$(CONFIG_EFI_LOADER_AUTHENTICODE) += authenticode.o
> obj-y += loadopts.o
> obj-y += efi_var_common.o
> obj-y += efi_variable.o
> diff --git a/efi/loader/authenticode.c b/efi/loader/authenticode.c
> new file mode 100644
> index 0000000000..36e5a46fc6
> --- /dev/null
> +++ b/efi/loader/authenticode.c
> @@ -0,0 +1,435 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Authenticode verification of PE images against barebox built-in keys:
> + * one signer, SHA-256 and RSA, the certificates in the signature are ignored
> + */
> +
> +#define pr_fmt(fmt) "efi-loader: authenticode: " fmt
> +
> +#include <common.h>
> +#include <digest.h>
> +#include <crypto/sha.h>
> +#include <malloc.h>
> +#include <crypto/public_key.h>
> +#include <efi/loader/pe.h>
> +#include <efi/loader/authenticode.h>
> +#include <efi/error.h>
> +#include <pe.h>
> +
> +struct der {
> + const u8 *p;
> + const u8 *end;
> +};
> +
> +struct der_elem {
> + u8 tag;
> + const u8 *start; /* tag byte */
> + const u8 *val;
> + size_t len;
> + size_t total; /* tag + length + value */
> +};
> +
> +#define DER_INTEGER 0x02
> +#define DER_OCTET 0x04
> +#define DER_OID 0x06
> +#define DER_SEQ 0x30
> +#define DER_SET 0x31
> +#define DER_CTX0 0xa0
> +#define DER_CTX1 0xa1
> +
> +static const u8 oid_signed_data[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 };
> +static const u8 oid_spc_indirect_data[] = {
> + 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x04
> +};
> +static const u8 oid_sha256[] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 };
> +static const u8 oid_content_type[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x03 };
> +static const u8 oid_message_digest[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x04 };
> +
> +static int der_next(struct der *d, struct der_elem *e)
> +{
> + const u8 *p = d->p;
> + size_t len, n;
> +
> + if (d->end - p < 2)
> + return -EBADMSG;
> +
> + e->start = p;
> + e->tag = *p++;
> + if ((e->tag & 0x1f) == 0x1f)
> + return -EBADMSG;
> +
> + len = *p++;
> + if (len & 0x80) {
> + n = len & 0x7f;
> + if (!n || n > 4 || d->end - p < n)
> + return -EBADMSG;
> + len = 0;
> + while (n--)
> + len = (len << 8) | *p++;
> + }
> +
> + if (d->end - p < len)
> + return -EBADMSG;
> +
> + e->val = p;
> + e->len = len;
> + e->total = p + len - e->start;
> + d->p = p + len;
> +
> + return 0;
> +}
> +
> +static int der_expect(struct der *d, u8 tag, struct der_elem *e)
> +{
> + int ret = der_next(d, e);
> +
> + if (ret)
> + return ret;
> +
> + return e->tag == tag ? 0 : -EBADMSG;
> +}
> +
> +static struct der der_enter(const struct der_elem *e)
> +{
> + return (struct der) { .p = e->val, .end = e->val + e->len };
> +}
> +
> +static bool der_oid_is(const struct der_elem *e, const u8 *oid, size_t len)
> +{
> + return e->tag == DER_OID && e->len == len && !memcmp(e->val, oid, len);
> +}
> +
> +/* AlgorithmIdentifier ::= SEQUENCE { OID, parameters OPTIONAL } */
> +static int der_expect_sha256(struct der *d)
> +{
> + struct der_elem seq, oid;
> + struct der in;
> + int ret;
> +
> + ret = der_expect(d, DER_SEQ, &seq);
> + if (ret)
> + return ret;
> +
> + in = der_enter(&seq);
> + ret = der_expect(&in, DER_OID, &oid);
> + if (ret)
> + return ret;
> +
> + return der_oid_is(&oid, oid_sha256, sizeof(oid_sha256)) ? 0 : -EOPNOTSUPP;
> +}
> +
> +struct authenticode {
> + const u8 *pe_digest; /* SpcIndirectDataContent.messageDigest */
> + const u8 *spc; /* SpcIndirectDataContent content octets */
> + size_t spc_len;
> + const u8 *attrs; /* [0] IMPLICIT authenticatedAttributes */
> + size_t attrs_len;
> + const u8 *attr_digest; /* messageDigest attribute value */
> + bool attr_content_type_ok;
> + const u8 *sig;
> + size_t sig_len;
> +};
> +
> +static int authenticode_parse_attrs(struct authenticode *a,
> + const struct der_elem *attrs)
> +{
> + struct der in = der_enter(attrs);
> + struct der_elem attr, oid, set, val;
> + struct der ain, sin;
> + int ret;
> +
> + while (in.p < in.end) {
> + ret = der_expect(&in, DER_SEQ, &attr);
> + if (ret)
> + return ret;
> +
> + ain = der_enter(&attr);
> + ret = der_expect(&ain, DER_OID, &oid);
> + if (ret)
> + return ret;
> + ret = der_expect(&ain, DER_SET, &set);
> + if (ret)
> + return ret;
> + sin = der_enter(&set);
> +
> + if (der_oid_is(&oid, oid_message_digest, sizeof(oid_message_digest))) {
> + ret = der_expect(&sin, DER_OCTET, &val);
> + if (ret || val.len != SHA256_DIGEST_SIZE)
> + return -EBADMSG;
> + a->attr_digest = val.val;
> + } else if (der_oid_is(&oid, oid_content_type, sizeof(oid_content_type))) {
> + ret = der_expect(&sin, DER_OID, &val);
> + if (ret)
> + return ret;
> + a->attr_content_type_ok =
> + der_oid_is(&val, oid_spc_indirect_data,
> + sizeof(oid_spc_indirect_data));
> + }
> + }
> +
> + return a->attr_digest ? 0 : -EBADMSG;
> +}
> +
> +static int authenticode_parse(struct authenticode *a, const void *buf, size_t len)
> +{
> + struct der d = { .p = buf, .end = (const u8 *)buf + len };
> + struct der_elem e, ci, sd, spc, dinfo, si;
> + struct der in, sdin, ciin, spcin, dinin, siin;
> + int ret;
> +
> + /* ContentInfo ::= SEQUENCE { contentType, [0] EXPLICIT content } */
> + ret = der_expect(&d, DER_SEQ, &ci);
> + if (ret)
> + return ret;
> + in = der_enter(&ci);
> + ret = der_expect(&in, DER_OID, &e);
> + if (ret)
> + return ret;
> + if (!der_oid_is(&e, oid_signed_data, sizeof(oid_signed_data)))
> + return -EBADMSG;
> + ret = der_expect(&in, DER_CTX0, &e);
> + if (ret)
> + return ret;
> + in = der_enter(&e);
> +
> + /* SignedData ::= SEQUENCE { version, digestAlgorithms, contentInfo, ... } */
> + ret = der_expect(&in, DER_SEQ, &sd);
> + if (ret)
> + return ret;
> + sdin = der_enter(&sd);
> + ret = der_expect(&sdin, DER_INTEGER, &e);
> + if (ret)
> + return ret;
> + ret = der_expect(&sdin, DER_SET, &e);
> + if (ret)
> + return ret;
> +
> + /* contentInfo: SPC_INDIRECT_DATA carrying the PE image digest */
> + ret = der_expect(&sdin, DER_SEQ, &e);
> + if (ret)
> + return ret;
> + ciin = der_enter(&e);
> + ret = der_expect(&ciin, DER_OID, &e);
> + if (ret)
> + return ret;
> + if (!der_oid_is(&e, oid_spc_indirect_data, sizeof(oid_spc_indirect_data)))
> + return -EBADMSG;
> + ret = der_expect(&ciin, DER_CTX0, &e);
> + if (ret)
> + return ret;
> + ciin = der_enter(&e);
> + ret = der_expect(&ciin, DER_SEQ, &spc);
> + if (ret)
> + return ret;
> + a->spc = spc.val;
> + a->spc_len = spc.len;
> +
> + spcin = der_enter(&spc);
> + ret = der_expect(&spcin, DER_SEQ, &e); /* SpcAttributeTypeAndOptionalValue */
> + if (ret)
> + return ret;
> + ret = der_expect(&spcin, DER_SEQ, &dinfo); /* DigestInfo */
> + if (ret)
> + return ret;
> + dinin = der_enter(&dinfo);
> + ret = der_expect_sha256(&dinin);
> + if (ret)
> + return ret;
> + ret = der_expect(&dinin, DER_OCTET, &e);
> + if (ret || e.len != SHA256_DIGEST_SIZE)
> + return -EBADMSG;
> + a->pe_digest = e.val;
> +
> + /* skip optional certificates [0] and crls [1] */
> + do {
> + ret = der_next(&sdin, &e);
> + if (ret)
> + return ret;
> + } while (e.tag == DER_CTX0 || e.tag == DER_CTX1);
> +
> + if (e.tag != DER_SET)
> + return -EBADMSG;
> +
> + /* first SignerInfo only */
> + in = der_enter(&e);
> + ret = der_expect(&in, DER_SEQ, &si);
> + if (ret)
> + return ret;
> + siin = der_enter(&si);
> + ret = der_expect(&siin, DER_INTEGER, &e);
> + if (ret)
> + return ret;
> + ret = der_expect(&siin, DER_SEQ, &e); /* issuerAndSerialNumber */
> + if (ret)
> + return ret;
> + ret = der_expect_sha256(&siin);
> + if (ret)
> + return ret;
> +
> + ret = der_expect(&siin, DER_CTX0, &e);
> + if (ret)
> + return ret;
> + a->attrs = e.start;
> + a->attrs_len = e.total;
> + ret = authenticode_parse_attrs(a, &e);
> + if (ret)
> + return ret;
> +
> + ret = der_expect(&siin, DER_SEQ, &e); /* digestEncryptionAlgorithm */
> + if (ret)
> + return ret;
> + ret = der_expect(&siin, DER_OCTET, &e);
> + if (ret)
> + return ret;
> + a->sig = e.val;
> + a->sig_len = e.len;
> +
> + return 0;
> +}
> +
> +static int sha256_regions(const struct efi_image_regions *regs, u8 *out)
> +{
> + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
> + int i, ret;
> +
> + if (!d)
> + return -EOPNOTSUPP;
> +
> + ret = digest_init(d);
> + for (i = 0; !ret && i < regs->num; i++)
> + ret = digest_update(d, regs->reg[i].data, regs->reg[i].size);
> + if (!ret)
> + ret = digest_final(d, out);
> +
> + digest_free(d);
> + return ret;
> +}
> +
> +static int sha256_buf(const void *buf, size_t len, u8 *out)
> +{
> + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
> + int ret;
> +
> + if (!d)
> + return -EOPNOTSUPP;
> +
> + ret = digest_digest(d, buf, len, out);
> + digest_free(d);
> + return ret;
> +}
> +
> +/* The signature covers the attributes DER-encoded as SET OF, not as [0] */
> +static int sha256_attrs(const struct authenticode *a, u8 *out)
> +{
> + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
> + const u8 set_tag = DER_SET;
> + int ret;
> +
> + if (!d)
> + return -EOPNOTSUPP;
> +
> + ret = digest_init(d);
> + if (!ret)
> + ret = digest_update(d, &set_tag, 1);
> + if (!ret)
> + ret = digest_update(d, a->attrs + 1, a->attrs_len - 1);
> + if (!ret)
> + ret = digest_final(d, out);
> +
> + digest_free(d);
> + return ret;
> +}
> +
> +/**
> + * efi_authenticode_verify() - verify a PE image's Authenticode signature
> + * @efi: PE image
> + * @len: exact size of the image, see efi_pe_file_size()
> + * @keyring: barebox keyring holding the trusted keys
> + *
> + * Return: 0 if the image is signed by a key in @keyring, negative error code
> + * otherwise.
> + */
> +int efi_authenticode_verify(void *efi, size_t len, const char *keyring)
> +{
> + struct efi_image_regions *regs = NULL;
> + const struct public_key *key;
> + struct authenticode a = {};
> + WIN_CERTIFICATE *wincert;
> + size_t auth_len;
> + u8 pe_hash[SHA256_DIGEST_SIZE], hash[SHA256_DIGEST_SIZE];
> + const struct keyring *kr;
> + int ret;
> +
> + if (!efi_image_parse(efi, len, ®s, &wincert, &auth_len))
> + return -EBADMSG;
> +
> + if (!wincert) {
> + pr_err("image is not signed\n");
> + ret = -ENOKEY;
> + goto out;
> + }
> +
> + if (wincert->dwLength > auth_len || wincert->dwLength <= sizeof(*wincert) ||
> + wincert->wRevision != WIN_CERT_REVISION_2_0 ||
> + wincert->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA) {
> + pr_err("unsupported certificate table entry\n");
> + ret = -EBADMSG;
> + goto out;
> + }
> +
> + ret = authenticode_parse(&a, wincert + 1, wincert->dwLength - sizeof(*wincert));
> + if (ret) {
> + pr_err("cannot parse signature: %pe\n", ERR_PTR(ret));
> + goto out;
> + }
> +
> + if (!a.attr_content_type_ok) {
> + pr_err("signed content is not SpcIndirectDataContent\n");
> + ret = -EBADMSG;
> + goto out;
> + }
> +
> + ret = sha256_regions(regs, pe_hash);
> + if (ret)
> + goto out;
> + if (memcmp(pe_hash, a.pe_digest, sizeof(pe_hash))) {
> + pr_err("image digest mismatch\n");
> + ret = -EBADMSG;
> + goto out;
> + }
> +
> + ret = sha256_buf(a.spc, a.spc_len, hash);
> + if (ret)
> + goto out;
> + if (memcmp(hash, a.attr_digest, sizeof(hash))) {
> + pr_err("signed attributes do not match the content\n");
> + ret = -EBADMSG;
> + goto out;
> + }
> +
> + ret = sha256_attrs(&a, hash);
> + if (ret)
> + goto out;
> +
> + kr = keyring_find(keyring);
> + if (!kr) {
> + pr_err("keyring '%s' not registered\n", keyring);
> + ret = -ENOKEY;
> + goto out;
> + }
> +
> + ret = -ENOKEY;
> + for_each_key_in_keyring(key, kr) {
> + if (!public_key_verify(key, a.sig, a.sig_len, hash, HASH_ALGO_SHA256)) {
> + pr_info("verified with key '%s'\n", key->key_name_hint ?: "?");
> + ret = 0;
> + break;
> + }
> + }
> +
> + if (ret)
> + pr_err("no key in keyring '%s' verifies the signature\n", keyring);
> +out:
> + free(regs);
> + return ret;
> +}
> diff --git a/include/efi/loader/authenticode.h b/include/efi/loader/authenticode.h
> new file mode 100644
> index 0000000000..24c46ca7cf
> --- /dev/null
> +++ b/include/efi/loader/authenticode.h
> @@ -0,0 +1,9 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __EFI_LOADER_AUTHENTICODE_H
> +#define __EFI_LOADER_AUTHENTICODE_H
> +
> +#include <linux/types.h>
> +
> +int efi_authenticode_verify(void *efi, size_t len, const char *keyring);
> +
> +#endif
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH v1 08/14] efi: loader: verify Authenticode signatures against built-in keys
2026-10-05 5:33 ` Ahmad Fatoum
@ 2026-10-05 5:45 ` SCHNEIDER Johannes
2026-10-09 0:07 ` SCHNEIDER Johannes
0 siblings, 1 reply; 19+ messages in thread
From: SCHNEIDER Johannes @ 2026-10-05 5:45 UTC (permalink / raw)
To: Ahmad Fatoum, barebox; +Cc: Marco Felsch
Hoi,
>
>
> Hello Johannes,
>
> On 10/4/26 03:19, Johannes Schneider wrote:
> > barebox's EFI loader verifies no signatures: efi_image_authenticate()
> > accepts every image. Add efi_authenticode_verify() as the verifier for
> > signed EFI images: compute the Authenticode digest over the regions
> > efi_image_parse() collects, check it against the SpcIndirectDataContent
> > of the PKCS#7 signature, check the messageDigest attribute against the
> > digest of that content, and verify the signature over the attributes
> > with the keys of a barebox keyring. The following commits use it.
> >
> > barebox has no ASN.1 decoder: keys are converted from certificates at
> > build time, and FIT signatures carry none. The PKCS#7 structure is
> > walked as plain DER for the fields needed, with every length checked
> > against what remains; the certificates it carries are skipped.
> >
> > Supported are one signer, SHA-256 and RSA. As for FIT images, trust
> > comes from the keyring, not from X.509 chains or db/dbx.
>
> We should import mbedTLS and then make use of its PKCS#7 support.
> The goal being mbedTLS being updated regularly like we already do
> with dts/
>
thanks for the pointer! you picked up the one patch i was least confident about :-D
i (or we ;-) where contemplating importing the linux kernel code here ... but
that would have pulled in lots of other/related parts
-> taking a look at mbedTLS instead.... so expect a v2,
any other things that caught your eye and {c,s}hould be reworked?
gruß
Johannes
> Cheers,
> Ahmad
>
> >
> > Assisted-by: Claude:claude-opus-5-5
> > Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
> > ---
> > efi/loader/Kconfig | 16 ++
> > efi/loader/Makefile | 1 +
> > efi/loader/authenticode.c | 435 ++++++++++++++++++++++++++++++
> > include/efi/loader/authenticode.h | 9 +
> > 4 files changed, 461 insertions(+)
> > create mode 100644 efi/loader/authenticode.c
> > create mode 100644 include/efi/loader/authenticode.h
> >
> > diff --git a/efi/loader/Kconfig b/efi/loader/Kconfig
> > index 5692e54ebe..4099da0689 100644
> > --- a/efi/loader/Kconfig
> > +++ b/efi/loader/Kconfig
> > @@ -24,6 +24,22 @@ config EFI_LOADER_DEBUG_SUPPORT
> > config EFI_LOADER_SECURE_BOOT
> > bool
> >
> > +config EFI_LOADER_AUTHENTICODE
> > + bool "Verify Authenticode signatures of booted EFI images"
> > + depends on CRYPTO_BUILTIN_KEYS && HAVE_DIGEST_SHA256
> > + select CRYPTO_RSA
> > + help
> > + Verify the Authenticode (PKCS#7, SHA-256, RSA) signature of an EFI
> > + image booted with bootm against the keys compiled into the "efi"
> > + keyring (CONFIG_CRYPTO_PUBLIC_KEYS, keyring=efi). With signed images
> > + forced, an EFI image then boots only if one of those keys verifies
> > + it, the same way a FIT image must carry a valid signature.
> > +
> > + X.509 certificates in the signature are not evaluated: trust is
> > + anchored in the keyring. barebox does not report UEFI Secure Boot
> > + to the payload, so a UKI keeps taking its command line from
> > + barebox.
> > +
> > menu "UEFI services"
> >
> > config EFI_LOADER_GET_TIME
> > diff --git a/efi/loader/Makefile b/efi/loader/Makefile
> > index 24850e87b1..775014dc22 100644
> > --- a/efi/loader/Makefile
> > +++ b/efi/loader/Makefile
> > @@ -14,6 +14,7 @@ obj-y += boot.o
> > obj-y += runtime.o
> > obj-y += setup.o
> > obj-y += watchdog.o
> > +obj-$(CONFIG_EFI_LOADER_AUTHENTICODE) += authenticode.o
> > obj-y += loadopts.o
> > obj-y += efi_var_common.o
> > obj-y += efi_variable.o
> > diff --git a/efi/loader/authenticode.c b/efi/loader/authenticode.c
> > new file mode 100644
> > index 0000000000..36e5a46fc6
> > --- /dev/null
> > +++ b/efi/loader/authenticode.c
> > @@ -0,0 +1,435 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Authenticode verification of PE images against barebox built-in keys:
> > + * one signer, SHA-256 and RSA, the certificates in the signature are ignored
> > + */
> > +
> > +#define pr_fmt(fmt) "efi-loader: authenticode: " fmt
> > +
> > +#include <common.h>
> > +#include <digest.h>
> > +#include <crypto/sha.h>
> > +#include <malloc.h>
> > +#include <crypto/public_key.h>
> > +#include <efi/loader/pe.h>
> > +#include <efi/loader/authenticode.h>
> > +#include <efi/error.h>
> > +#include <pe.h>
> > +
> > +struct der {
> > + const u8 *p;
> > + const u8 *end;
> > +};
> > +
> > +struct der_elem {
> > + u8 tag;
> > + const u8 *start; /* tag byte */
> > + const u8 *val;
> > + size_t len;
> > + size_t total; /* tag + length + value */
> > +};
> > +
> > +#define DER_INTEGER 0x02
> > +#define DER_OCTET 0x04
> > +#define DER_OID 0x06
> > +#define DER_SEQ 0x30
> > +#define DER_SET 0x31
> > +#define DER_CTX0 0xa0
> > +#define DER_CTX1 0xa1
> > +
> > +static const u8 oid_signed_data[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 };
> > +static const u8 oid_spc_indirect_data[] = {
> > + 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x04
> > +};
> > +static const u8 oid_sha256[] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 };
> > +static const u8 oid_content_type[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x03 };
> > +static const u8 oid_message_digest[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x04 };
> > +
> > +static int der_next(struct der *d, struct der_elem *e)
> > +{
> > + const u8 *p = d->p;
> > + size_t len, n;
> > +
> > + if (d->end - p < 2)
> > + return -EBADMSG;
> > +
> > + e->start = p;
> > + e->tag = *p++;
> > + if ((e->tag & 0x1f) == 0x1f)
> > + return -EBADMSG;
> > +
> > + len = *p++;
> > + if (len & 0x80) {
> > + n = len & 0x7f;
> > + if (!n || n > 4 || d->end - p < n)
> > + return -EBADMSG;
> > + len = 0;
> > + while (n--)
> > + len = (len << 8) | *p++;
> > + }
> > +
> > + if (d->end - p < len)
> > + return -EBADMSG;
> > +
> > + e->val = p;
> > + e->len = len;
> > + e->total = p + len - e->start;
> > + d->p = p + len;
> > +
> > + return 0;
> > +}
> > +
> > +static int der_expect(struct der *d, u8 tag, struct der_elem *e)
> > +{
> > + int ret = der_next(d, e);
> > +
> > + if (ret)
> > + return ret;
> > +
> > + return e->tag == tag ? 0 : -EBADMSG;
> > +}
> > +
> > +static struct der der_enter(const struct der_elem *e)
> > +{
> > + return (struct der) { .p = e->val, .end = e->val + e->len };
> > +}
> > +
> > +static bool der_oid_is(const struct der_elem *e, const u8 *oid, size_t len)
> > +{
> > + return e->tag == DER_OID && e->len == len && !memcmp(e->val, oid, len);
> > +}
> > +
> > +/* AlgorithmIdentifier ::= SEQUENCE { OID, parameters OPTIONAL } */
> > +static int der_expect_sha256(struct der *d)
> > +{
> > + struct der_elem seq, oid;
> > + struct der in;
> > + int ret;
> > +
> > + ret = der_expect(d, DER_SEQ, &seq);
> > + if (ret)
> > + return ret;
> > +
> > + in = der_enter(&seq);
> > + ret = der_expect(&in, DER_OID, &oid);
> > + if (ret)
> > + return ret;
> > +
> > + return der_oid_is(&oid, oid_sha256, sizeof(oid_sha256)) ? 0 : -EOPNOTSUPP;
> > +}
> > +
> > +struct authenticode {
> > + const u8 *pe_digest; /* SpcIndirectDataContent.messageDigest */
> > + const u8 *spc; /* SpcIndirectDataContent content octets */
> > + size_t spc_len;
> > + const u8 *attrs; /* [0] IMPLICIT authenticatedAttributes */
> > + size_t attrs_len;
> > + const u8 *attr_digest; /* messageDigest attribute value */
> > + bool attr_content_type_ok;
> > + const u8 *sig;
> > + size_t sig_len;
> > +};
> > +
> > +static int authenticode_parse_attrs(struct authenticode *a,
> > + const struct der_elem *attrs)
> > +{
> > + struct der in = der_enter(attrs);
> > + struct der_elem attr, oid, set, val;
> > + struct der ain, sin;
> > + int ret;
> > +
> > + while (in.p < in.end) {
> > + ret = der_expect(&in, DER_SEQ, &attr);
> > + if (ret)
> > + return ret;
> > +
> > + ain = der_enter(&attr);
> > + ret = der_expect(&ain, DER_OID, &oid);
> > + if (ret)
> > + return ret;
> > + ret = der_expect(&ain, DER_SET, &set);
> > + if (ret)
> > + return ret;
> > + sin = der_enter(&set);
> > +
> > + if (der_oid_is(&oid, oid_message_digest, sizeof(oid_message_digest))) {
> > + ret = der_expect(&sin, DER_OCTET, &val);
> > + if (ret || val.len != SHA256_DIGEST_SIZE)
> > + return -EBADMSG;
> > + a->attr_digest = val.val;
> > + } else if (der_oid_is(&oid, oid_content_type, sizeof(oid_content_type))) {
> > + ret = der_expect(&sin, DER_OID, &val);
> > + if (ret)
> > + return ret;
> > + a->attr_content_type_ok =
> > + der_oid_is(&val, oid_spc_indirect_data,
> > + sizeof(oid_spc_indirect_data));
> > + }
> > + }
> > +
> > + return a->attr_digest ? 0 : -EBADMSG;
> > +}
> > +
> > +static int authenticode_parse(struct authenticode *a, const void *buf, size_t len)
> > +{
> > + struct der d = { .p = buf, .end = (const u8 *)buf + len };
> > + struct der_elem e, ci, sd, spc, dinfo, si;
> > + struct der in, sdin, ciin, spcin, dinin, siin;
> > + int ret;
> > +
> > + /* ContentInfo ::= SEQUENCE { contentType, [0] EXPLICIT content } */
> > + ret = der_expect(&d, DER_SEQ, &ci);
> > + if (ret)
> > + return ret;
> > + in = der_enter(&ci);
> > + ret = der_expect(&in, DER_OID, &e);
> > + if (ret)
> > + return ret;
> > + if (!der_oid_is(&e, oid_signed_data, sizeof(oid_signed_data)))
> > + return -EBADMSG;
> > + ret = der_expect(&in, DER_CTX0, &e);
> > + if (ret)
> > + return ret;
> > + in = der_enter(&e);
> > +
> > + /* SignedData ::= SEQUENCE { version, digestAlgorithms, contentInfo, ... } */
> > + ret = der_expect(&in, DER_SEQ, &sd);
> > + if (ret)
> > + return ret;
> > + sdin = der_enter(&sd);
> > + ret = der_expect(&sdin, DER_INTEGER, &e);
> > + if (ret)
> > + return ret;
> > + ret = der_expect(&sdin, DER_SET, &e);
> > + if (ret)
> > + return ret;
> > +
> > + /* contentInfo: SPC_INDIRECT_DATA carrying the PE image digest */
> > + ret = der_expect(&sdin, DER_SEQ, &e);
> > + if (ret)
> > + return ret;
> > + ciin = der_enter(&e);
> > + ret = der_expect(&ciin, DER_OID, &e);
> > + if (ret)
> > + return ret;
> > + if (!der_oid_is(&e, oid_spc_indirect_data, sizeof(oid_spc_indirect_data)))
> > + return -EBADMSG;
> > + ret = der_expect(&ciin, DER_CTX0, &e);
> > + if (ret)
> > + return ret;
> > + ciin = der_enter(&e);
> > + ret = der_expect(&ciin, DER_SEQ, &spc);
> > + if (ret)
> > + return ret;
> > + a->spc = spc.val;
> > + a->spc_len = spc.len;
> > +
> > + spcin = der_enter(&spc);
> > + ret = der_expect(&spcin, DER_SEQ, &e); /* SpcAttributeTypeAndOptionalValue */
> > + if (ret)
> > + return ret;
> > + ret = der_expect(&spcin, DER_SEQ, &dinfo); /* DigestInfo */
> > + if (ret)
> > + return ret;
> > + dinin = der_enter(&dinfo);
> > + ret = der_expect_sha256(&dinin);
> > + if (ret)
> > + return ret;
> > + ret = der_expect(&dinin, DER_OCTET, &e);
> > + if (ret || e.len != SHA256_DIGEST_SIZE)
> > + return -EBADMSG;
> > + a->pe_digest = e.val;
> > +
> > + /* skip optional certificates [0] and crls [1] */
> > + do {
> > + ret = der_next(&sdin, &e);
> > + if (ret)
> > + return ret;
> > + } while (e.tag == DER_CTX0 || e.tag == DER_CTX1);
> > +
> > + if (e.tag != DER_SET)
> > + return -EBADMSG;
> > +
> > + /* first SignerInfo only */
> > + in = der_enter(&e);
> > + ret = der_expect(&in, DER_SEQ, &si);
> > + if (ret)
> > + return ret;
> > + siin = der_enter(&si);
> > + ret = der_expect(&siin, DER_INTEGER, &e);
> > + if (ret)
> > + return ret;
> > + ret = der_expect(&siin, DER_SEQ, &e); /* issuerAndSerialNumber */
> > + if (ret)
> > + return ret;
> > + ret = der_expect_sha256(&siin);
> > + if (ret)
> > + return ret;
> > +
> > + ret = der_expect(&siin, DER_CTX0, &e);
> > + if (ret)
> > + return ret;
> > + a->attrs = e.start;
> > + a->attrs_len = e.total;
> > + ret = authenticode_parse_attrs(a, &e);
> > + if (ret)
> > + return ret;
> > +
> > + ret = der_expect(&siin, DER_SEQ, &e); /* digestEncryptionAlgorithm */
> > + if (ret)
> > + return ret;
> > + ret = der_expect(&siin, DER_OCTET, &e);
> > + if (ret)
> > + return ret;
> > + a->sig = e.val;
> > + a->sig_len = e.len;
> > +
> > + return 0;
> > +}
> > +
> > +static int sha256_regions(const struct efi_image_regions *regs, u8 *out)
> > +{
> > + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
> > + int i, ret;
> > +
> > + if (!d)
> > + return -EOPNOTSUPP;
> > +
> > + ret = digest_init(d);
> > + for (i = 0; !ret && i < regs->num; i++)
> > + ret = digest_update(d, regs->reg[i].data, regs->reg[i].size);
> > + if (!ret)
> > + ret = digest_final(d, out);
> > +
> > + digest_free(d);
> > + return ret;
> > +}
> > +
> > +static int sha256_buf(const void *buf, size_t len, u8 *out)
> > +{
> > + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
> > + int ret;
> > +
> > + if (!d)
> > + return -EOPNOTSUPP;
> > +
> > + ret = digest_digest(d, buf, len, out);
> > + digest_free(d);
> > + return ret;
> > +}
> > +
> > +/* The signature covers the attributes DER-encoded as SET OF, not as [0] */
> > +static int sha256_attrs(const struct authenticode *a, u8 *out)
> > +{
> > + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
> > + const u8 set_tag = DER_SET;
> > + int ret;
> > +
> > + if (!d)
> > + return -EOPNOTSUPP;
> > +
> > + ret = digest_init(d);
> > + if (!ret)
> > + ret = digest_update(d, &set_tag, 1);
> > + if (!ret)
> > + ret = digest_update(d, a->attrs + 1, a->attrs_len - 1);
> > + if (!ret)
> > + ret = digest_final(d, out);
> > +
> > + digest_free(d);
> > + return ret;
> > +}
> > +
> > +/**
> > + * efi_authenticode_verify() - verify a PE image's Authenticode signature
> > + * @efi: PE image
> > + * @len: exact size of the image, see efi_pe_file_size()
> > + * @keyring: barebox keyring holding the trusted keys
> > + *
> > + * Return: 0 if the image is signed by a key in @keyring, negative error code
> > + * otherwise.
> > + */
> > +int efi_authenticode_verify(void *efi, size_t len, const char *keyring)
> > +{
> > + struct efi_image_regions *regs = NULL;
> > + const struct public_key *key;
> > + struct authenticode a = {};
> > + WIN_CERTIFICATE *wincert;
> > + size_t auth_len;
> > + u8 pe_hash[SHA256_DIGEST_SIZE], hash[SHA256_DIGEST_SIZE];
> > + const struct keyring *kr;
> > + int ret;
> > +
> > + if (!efi_image_parse(efi, len, ®s, &wincert, &auth_len))
> > + return -EBADMSG;
> > +
> > + if (!wincert) {
> > + pr_err("image is not signed\n");
> > + ret = -ENOKEY;
> > + goto out;
> > + }
> > +
> > + if (wincert->dwLength > auth_len || wincert->dwLength <= sizeof(*wincert) ||
> > + wincert->wRevision != WIN_CERT_REVISION_2_0 ||
> > + wincert->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA) {
> > + pr_err("unsupported certificate table entry\n");
> > + ret = -EBADMSG;
> > + goto out;
> > + }
> > +
> > + ret = authenticode_parse(&a, wincert + 1, wincert->dwLength - sizeof(*wincert));
> > + if (ret) {
> > + pr_err("cannot parse signature: %pe\n", ERR_PTR(ret));
> > + goto out;
> > + }
> > +
> > + if (!a.attr_content_type_ok) {
> > + pr_err("signed content is not SpcIndirectDataContent\n");
> > + ret = -EBADMSG;
> > + goto out;
> > + }
> > +
> > + ret = sha256_regions(regs, pe_hash);
> > + if (ret)
> > + goto out;
> > + if (memcmp(pe_hash, a.pe_digest, sizeof(pe_hash))) {
> > + pr_err("image digest mismatch\n");
> > + ret = -EBADMSG;
> > + goto out;
> > + }
> > +
> > + ret = sha256_buf(a.spc, a.spc_len, hash);
> > + if (ret)
> > + goto out;
> > + if (memcmp(hash, a.attr_digest, sizeof(hash))) {
> > + pr_err("signed attributes do not match the content\n");
> > + ret = -EBADMSG;
> > + goto out;
> > + }
> > +
> > + ret = sha256_attrs(&a, hash);
> > + if (ret)
> > + goto out;
> > +
> > + kr = keyring_find(keyring);
> > + if (!kr) {
> > + pr_err("keyring '%s' not registered\n", keyring);
> > + ret = -ENOKEY;
> > + goto out;
> > + }
> > +
> > + ret = -ENOKEY;
> > + for_each_key_in_keyring(key, kr) {
> > + if (!public_key_verify(key, a.sig, a.sig_len, hash, HASH_ALGO_SHA256)) {
> > + pr_info("verified with key '%s'\n", key->key_name_hint ?: "?");
> > + ret = 0;
> > + break;
> > + }
> > + }
> > +
> > + if (ret)
> > + pr_err("no key in keyring '%s' verifies the signature\n", keyring);
> > +out:
> > + free(regs);
> > + return ret;
> > +}
> > diff --git a/include/efi/loader/authenticode.h b/include/efi/loader/authenticode.h
> > new file mode 100644
> > index 0000000000..24c46ca7cf
> > --- /dev/null
> > +++ b/include/efi/loader/authenticode.h
> > @@ -0,0 +1,9 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +#ifndef __EFI_LOADER_AUTHENTICODE_H
> > +#define __EFI_LOADER_AUTHENTICODE_H
> > +
> > +#include <linux/types.h>
> > +
> > +int efi_authenticode_verify(void *efi, size_t len, const char *keyring);
> > +
> > +#endif
>
>
> --
> Pengutronix e.K. | |
> Steuerwalder Str. 21 | http://www.pengutronix.de/ |
> 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
> Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
>
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH v1 08/14] efi: loader: verify Authenticode signatures against built-in keys
2026-10-05 5:45 ` SCHNEIDER Johannes
@ 2026-10-09 0:07 ` SCHNEIDER Johannes
0 siblings, 0 replies; 19+ messages in thread
From: SCHNEIDER Johannes @ 2026-10-09 0:07 UTC (permalink / raw)
To: Ahmad Fatoum, barebox; +Cc: Marco Felsch
Hoi Ahmad,
> >
> > Hello Johannes,
> >
> > On 10/4/26 03:19, Johannes Schneider wrote:
> > > barebox's EFI loader verifies no signatures: efi_image_authenticate()
> > > accepts every image. Add efi_authenticode_verify() as the verifier for
> > > signed EFI images: compute the Authenticode digest over the regions
> > > efi_image_parse() collects, check it against the SpcIndirectDataContent
> > > of the PKCS#7 signature, check the messageDigest attribute against the
> > > digest of that content, and verify the signature over the attributes
> > > with the keys of a barebox keyring. The following commits use it.
> > >
> > > barebox has no ASN.1 decoder: keys are converted from certificates at
> > > build time, and FIT signatures carry none. The PKCS#7 structure is
> > > walked as plain DER for the fields needed, with every length checked
> > > against what remains; the certificates it carries are skipped.
> > >
> > > Supported are one signer, SHA-256 and RSA. As for FIT images, trust
> > > comes from the keyring, not from X.509 chains or db/dbx.
> >
> > We should import mbedTLS and then make use of its PKCS#7 support.
> > The goal being mbedTLS being updated regularly like we already do
> > with dts/
> >
>
> thanks for the pointer! you picked up the one patch i was least confident about :-D
>
> i (or we ;-) where contemplating importing the linux kernel code here ... but
> that would have pulled in lots of other/related parts
>
> -> taking a look at mbedTLS instead.... so expect a v2,
> any other things that caught your eye and {c,s}hould be reworked?
>
u-boot also imports mbetls - but:
1 has an older 3.6.0 version :-(
see: u-boot/lib/mbedtls/external/mbedtls/include/mbedtls/build_info.h
2 has patches ontop, to get the needed authenticode support
which is still a PR into upstream mbedtls: #9001 [1]
I would propose importing mbedtls first, then porting over the
pkcs7/authenticode patches, and afterward reworking/resuming this
=> so three separate, but order dependent patch-series - would that be OK?
about actually importing mbedtls into barebox: shall this go over the
mailinglist - which will be rather large patch series - or are there
alternate routes open for you maintainers?
gruß
Johannes
Link: https://github.com/Mbed-TLS/mbedtls/pull/9001
>
>
>
> > Cheers,
> > Ahmad
> >
> > >
> > > Assisted-by: Claude:claude-opus-5-5
> > > Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
> > > ---
> > > efi/loader/Kconfig | 16 ++
> > > efi/loader/Makefile | 1 +
> > > efi/loader/authenticode.c | 435 ++++++++++++++++++++++++++++++
> > > include/efi/loader/authenticode.h | 9 +
> > > 4 files changed, 461 insertions(+)
> > > create mode 100644 efi/loader/authenticode.c
> > > create mode 100644 include/efi/loader/authenticode.h
> > >
> > > diff --git a/efi/loader/Kconfig b/efi/loader/Kconfig
> > > index 5692e54ebe..4099da0689 100644
> > > --- a/efi/loader/Kconfig
> > > +++ b/efi/loader/Kconfig
> > > @@ -24,6 +24,22 @@ config EFI_LOADER_DEBUG_SUPPORT
> > > config EFI_LOADER_SECURE_BOOT
> > > bool
> > >
> > > +config EFI_LOADER_AUTHENTICODE
> > > + bool "Verify Authenticode signatures of booted EFI images"
> > > + depends on CRYPTO_BUILTIN_KEYS && HAVE_DIGEST_SHA256
> > > + select CRYPTO_RSA
> > > + help
> > > + Verify the Authenticode (PKCS#7, SHA-256, RSA) signature of an EFI
> > > + image booted with bootm against the keys compiled into the "efi"
> > > + keyring (CONFIG_CRYPTO_PUBLIC_KEYS, keyring=efi). With signed images
> > > + forced, an EFI image then boots only if one of those keys verifies
> > > + it, the same way a FIT image must carry a valid signature.
> > > +
> > > + X.509 certificates in the signature are not evaluated: trust is
> > > + anchored in the keyring. barebox does not report UEFI Secure Boot
> > > + to the payload, so a UKI keeps taking its command line from
> > > + barebox.
> > > +
> > > menu "UEFI services"
> > >
> > > config EFI_LOADER_GET_TIME
> > > diff --git a/efi/loader/Makefile b/efi/loader/Makefile
> > > index 24850e87b1..775014dc22 100644
> > > --- a/efi/loader/Makefile
> > > +++ b/efi/loader/Makefile
> > > @@ -14,6 +14,7 @@ obj-y += boot.o
> > > obj-y += runtime.o
> > > obj-y += setup.o
> > > obj-y += watchdog.o
> > > +obj-$(CONFIG_EFI_LOADER_AUTHENTICODE) += authenticode.o
> > > obj-y += loadopts.o
> > > obj-y += efi_var_common.o
> > > obj-y += efi_variable.o
> > > diff --git a/efi/loader/authenticode.c b/efi/loader/authenticode.c
> > > new file mode 100644
> > > index 0000000000..36e5a46fc6
> > > --- /dev/null
> > > +++ b/efi/loader/authenticode.c
> > > @@ -0,0 +1,435 @@
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +/*
> > > + * Authenticode verification of PE images against barebox built-in keys:
> > > + * one signer, SHA-256 and RSA, the certificates in the signature are ignored
> > > + */
> > > +
> > > +#define pr_fmt(fmt) "efi-loader: authenticode: " fmt
> > > +
> > > +#include <common.h>
> > > +#include <digest.h>
> > > +#include <crypto/sha.h>
> > > +#include <malloc.h>
> > > +#include <crypto/public_key.h>
> > > +#include <efi/loader/pe.h>
> > > +#include <efi/loader/authenticode.h>
> > > +#include <efi/error.h>
> > > +#include <pe.h>
> > > +
> > > +struct der {
> > > + const u8 *p;
> > > + const u8 *end;
> > > +};
> > > +
> > > +struct der_elem {
> > > + u8 tag;
> > > + const u8 *start; /* tag byte */
> > > + const u8 *val;
> > > + size_t len;
> > > + size_t total; /* tag + length + value */
> > > +};
> > > +
> > > +#define DER_INTEGER 0x02
> > > +#define DER_OCTET 0x04
> > > +#define DER_OID 0x06
> > > +#define DER_SEQ 0x30
> > > +#define DER_SET 0x31
> > > +#define DER_CTX0 0xa0
> > > +#define DER_CTX1 0xa1
> > > +
> > > +static const u8 oid_signed_data[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 };
> > > +static const u8 oid_spc_indirect_data[] = {
> > > + 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x04
> > > +};
> > > +static const u8 oid_sha256[] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 };
> > > +static const u8 oid_content_type[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x03 };
> > > +static const u8 oid_message_digest[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x04 };
> > > +
> > > +static int der_next(struct der *d, struct der_elem *e)
> > > +{
> > > + const u8 *p = d->p;
> > > + size_t len, n;
> > > +
> > > + if (d->end - p < 2)
> > > + return -EBADMSG;
> > > +
> > > + e->start = p;
> > > + e->tag = *p++;
> > > + if ((e->tag & 0x1f) == 0x1f)
> > > + return -EBADMSG;
> > > +
> > > + len = *p++;
> > > + if (len & 0x80) {
> > > + n = len & 0x7f;
> > > + if (!n || n > 4 || d->end - p < n)
> > > + return -EBADMSG;
> > > + len = 0;
> > > + while (n--)
> > > + len = (len << 8) | *p++;
> > > + }
> > > +
> > > + if (d->end - p < len)
> > > + return -EBADMSG;
> > > +
> > > + e->val = p;
> > > + e->len = len;
> > > + e->total = p + len - e->start;
> > > + d->p = p + len;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int der_expect(struct der *d, u8 tag, struct der_elem *e)
> > > +{
> > > + int ret = der_next(d, e);
> > > +
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return e->tag == tag ? 0 : -EBADMSG;
> > > +}
> > > +
> > > +static struct der der_enter(const struct der_elem *e)
> > > +{
> > > + return (struct der) { .p = e->val, .end = e->val + e->len };
> > > +}
> > > +
> > > +static bool der_oid_is(const struct der_elem *e, const u8 *oid, size_t len)
> > > +{
> > > + return e->tag == DER_OID && e->len == len && !memcmp(e->val, oid, len);
> > > +}
> > > +
> > > +/* AlgorithmIdentifier ::= SEQUENCE { OID, parameters OPTIONAL } */
> > > +static int der_expect_sha256(struct der *d)
> > > +{
> > > + struct der_elem seq, oid;
> > > + struct der in;
> > > + int ret;
> > > +
> > > + ret = der_expect(d, DER_SEQ, &seq);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + in = der_enter(&seq);
> > > + ret = der_expect(&in, DER_OID, &oid);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return der_oid_is(&oid, oid_sha256, sizeof(oid_sha256)) ? 0 : -EOPNOTSUPP;
> > > +}
> > > +
> > > +struct authenticode {
> > > + const u8 *pe_digest; /* SpcIndirectDataContent.messageDigest */
> > > + const u8 *spc; /* SpcIndirectDataContent content octets */
> > > + size_t spc_len;
> > > + const u8 *attrs; /* [0] IMPLICIT authenticatedAttributes */
> > > + size_t attrs_len;
> > > + const u8 *attr_digest; /* messageDigest attribute value */
> > > + bool attr_content_type_ok;
> > > + const u8 *sig;
> > > + size_t sig_len;
> > > +};
> > > +
> > > +static int authenticode_parse_attrs(struct authenticode *a,
> > > + const struct der_elem *attrs)
> > > +{
> > > + struct der in = der_enter(attrs);
> > > + struct der_elem attr, oid, set, val;
> > > + struct der ain, sin;
> > > + int ret;
> > > +
> > > + while (in.p < in.end) {
> > > + ret = der_expect(&in, DER_SEQ, &attr);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ain = der_enter(&attr);
> > > + ret = der_expect(&ain, DER_OID, &oid);
> > > + if (ret)
> > > + return ret;
> > > + ret = der_expect(&ain, DER_SET, &set);
> > > + if (ret)
> > > + return ret;
> > > + sin = der_enter(&set);
> > > +
> > > + if (der_oid_is(&oid, oid_message_digest, sizeof(oid_message_digest))) {
> > > + ret = der_expect(&sin, DER_OCTET, &val);
> > > + if (ret || val.len != SHA256_DIGEST_SIZE)
> > > + return -EBADMSG;
> > > + a->attr_digest = val.val;
> > > + } else if (der_oid_is(&oid, oid_content_type, sizeof(oid_content_type))) {
> > > + ret = der_expect(&sin, DER_OID, &val);
> > > + if (ret)
> > > + return ret;
> > > + a->attr_content_type_ok =
> > > + der_oid_is(&val, oid_spc_indirect_data,
> > > + sizeof(oid_spc_indirect_data));
> > > + }
> > > + }
> > > +
> > > + return a->attr_digest ? 0 : -EBADMSG;
> > > +}
> > > +
> > > +static int authenticode_parse(struct authenticode *a, const void *buf, size_t len)
> > > +{
> > > + struct der d = { .p = buf, .end = (const u8 *)buf + len };
> > > + struct der_elem e, ci, sd, spc, dinfo, si;
> > > + struct der in, sdin, ciin, spcin, dinin, siin;
> > > + int ret;
> > > +
> > > + /* ContentInfo ::= SEQUENCE { contentType, [0] EXPLICIT content } */
> > > + ret = der_expect(&d, DER_SEQ, &ci);
> > > + if (ret)
> > > + return ret;
> > > + in = der_enter(&ci);
> > > + ret = der_expect(&in, DER_OID, &e);
> > > + if (ret)
> > > + return ret;
> > > + if (!der_oid_is(&e, oid_signed_data, sizeof(oid_signed_data)))
> > > + return -EBADMSG;
> > > + ret = der_expect(&in, DER_CTX0, &e);
> > > + if (ret)
> > > + return ret;
> > > + in = der_enter(&e);
> > > +
> > > + /* SignedData ::= SEQUENCE { version, digestAlgorithms, contentInfo, ... } */
> > > + ret = der_expect(&in, DER_SEQ, &sd);
> > > + if (ret)
> > > + return ret;
> > > + sdin = der_enter(&sd);
> > > + ret = der_expect(&sdin, DER_INTEGER, &e);
> > > + if (ret)
> > > + return ret;
> > > + ret = der_expect(&sdin, DER_SET, &e);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + /* contentInfo: SPC_INDIRECT_DATA carrying the PE image digest */
> > > + ret = der_expect(&sdin, DER_SEQ, &e);
> > > + if (ret)
> > > + return ret;
> > > + ciin = der_enter(&e);
> > > + ret = der_expect(&ciin, DER_OID, &e);
> > > + if (ret)
> > > + return ret;
> > > + if (!der_oid_is(&e, oid_spc_indirect_data, sizeof(oid_spc_indirect_data)))
> > > + return -EBADMSG;
> > > + ret = der_expect(&ciin, DER_CTX0, &e);
> > > + if (ret)
> > > + return ret;
> > > + ciin = der_enter(&e);
> > > + ret = der_expect(&ciin, DER_SEQ, &spc);
> > > + if (ret)
> > > + return ret;
> > > + a->spc = spc.val;
> > > + a->spc_len = spc.len;
> > > +
> > > + spcin = der_enter(&spc);
> > > + ret = der_expect(&spcin, DER_SEQ, &e); /* SpcAttributeTypeAndOptionalValue */
> > > + if (ret)
> > > + return ret;
> > > + ret = der_expect(&spcin, DER_SEQ, &dinfo); /* DigestInfo */
> > > + if (ret)
> > > + return ret;
> > > + dinin = der_enter(&dinfo);
> > > + ret = der_expect_sha256(&dinin);
> > > + if (ret)
> > > + return ret;
> > > + ret = der_expect(&dinin, DER_OCTET, &e);
> > > + if (ret || e.len != SHA256_DIGEST_SIZE)
> > > + return -EBADMSG;
> > > + a->pe_digest = e.val;
> > > +
> > > + /* skip optional certificates [0] and crls [1] */
> > > + do {
> > > + ret = der_next(&sdin, &e);
> > > + if (ret)
> > > + return ret;
> > > + } while (e.tag == DER_CTX0 || e.tag == DER_CTX1);
> > > +
> > > + if (e.tag != DER_SET)
> > > + return -EBADMSG;
> > > +
> > > + /* first SignerInfo only */
> > > + in = der_enter(&e);
> > > + ret = der_expect(&in, DER_SEQ, &si);
> > > + if (ret)
> > > + return ret;
> > > + siin = der_enter(&si);
> > > + ret = der_expect(&siin, DER_INTEGER, &e);
> > > + if (ret)
> > > + return ret;
> > > + ret = der_expect(&siin, DER_SEQ, &e); /* issuerAndSerialNumber */
> > > + if (ret)
> > > + return ret;
> > > + ret = der_expect_sha256(&siin);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = der_expect(&siin, DER_CTX0, &e);
> > > + if (ret)
> > > + return ret;
> > > + a->attrs = e.start;
> > > + a->attrs_len = e.total;
> > > + ret = authenticode_parse_attrs(a, &e);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = der_expect(&siin, DER_SEQ, &e); /* digestEncryptionAlgorithm */
> > > + if (ret)
> > > + return ret;
> > > + ret = der_expect(&siin, DER_OCTET, &e);
> > > + if (ret)
> > > + return ret;
> > > + a->sig = e.val;
> > > + a->sig_len = e.len;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int sha256_regions(const struct efi_image_regions *regs, u8 *out)
> > > +{
> > > + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
> > > + int i, ret;
> > > +
> > > + if (!d)
> > > + return -EOPNOTSUPP;
> > > +
> > > + ret = digest_init(d);
> > > + for (i = 0; !ret && i < regs->num; i++)
> > > + ret = digest_update(d, regs->reg[i].data, regs->reg[i].size);
> > > + if (!ret)
> > > + ret = digest_final(d, out);
> > > +
> > > + digest_free(d);
> > > + return ret;
> > > +}
> > > +
> > > +static int sha256_buf(const void *buf, size_t len, u8 *out)
> > > +{
> > > + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
> > > + int ret;
> > > +
> > > + if (!d)
> > > + return -EOPNOTSUPP;
> > > +
> > > + ret = digest_digest(d, buf, len, out);
> > > + digest_free(d);
> > > + return ret;
> > > +}
> > > +
> > > +/* The signature covers the attributes DER-encoded as SET OF, not as [0] */
> > > +static int sha256_attrs(const struct authenticode *a, u8 *out)
> > > +{
> > > + struct digest *d = digest_alloc_by_algo(HASH_ALGO_SHA256);
> > > + const u8 set_tag = DER_SET;
> > > + int ret;
> > > +
> > > + if (!d)
> > > + return -EOPNOTSUPP;
> > > +
> > > + ret = digest_init(d);
> > > + if (!ret)
> > > + ret = digest_update(d, &set_tag, 1);
> > > + if (!ret)
> > > + ret = digest_update(d, a->attrs + 1, a->attrs_len - 1);
> > > + if (!ret)
> > > + ret = digest_final(d, out);
> > > +
> > > + digest_free(d);
> > > + return ret;
> > > +}
> > > +
> > > +/**
> > > + * efi_authenticode_verify() - verify a PE image's Authenticode signature
> > > + * @efi: PE image
> > > + * @len: exact size of the image, see efi_pe_file_size()
> > > + * @keyring: barebox keyring holding the trusted keys
> > > + *
> > > + * Return: 0 if the image is signed by a key in @keyring, negative error code
> > > + * otherwise.
> > > + */
> > > +int efi_authenticode_verify(void *efi, size_t len, const char *keyring)
> > > +{
> > > + struct efi_image_regions *regs = NULL;
> > > + const struct public_key *key;
> > > + struct authenticode a = {};
> > > + WIN_CERTIFICATE *wincert;
> > > + size_t auth_len;
> > > + u8 pe_hash[SHA256_DIGEST_SIZE], hash[SHA256_DIGEST_SIZE];
> > > + const struct keyring *kr;
> > > + int ret;
> > > +
> > > + if (!efi_image_parse(efi, len, ®s, &wincert, &auth_len))
> > > + return -EBADMSG;
> > > +
> > > + if (!wincert) {
> > > + pr_err("image is not signed\n");
> > > + ret = -ENOKEY;
> > > + goto out;
> > > + }
> > > +
> > > + if (wincert->dwLength > auth_len || wincert->dwLength <= sizeof(*wincert) ||
> > > + wincert->wRevision != WIN_CERT_REVISION_2_0 ||
> > > + wincert->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA) {
> > > + pr_err("unsupported certificate table entry\n");
> > > + ret = -EBADMSG;
> > > + goto out;
> > > + }
> > > +
> > > + ret = authenticode_parse(&a, wincert + 1, wincert->dwLength - sizeof(*wincert));
> > > + if (ret) {
> > > + pr_err("cannot parse signature: %pe\n", ERR_PTR(ret));
> > > + goto out;
> > > + }
> > > +
> > > + if (!a.attr_content_type_ok) {
> > > + pr_err("signed content is not SpcIndirectDataContent\n");
> > > + ret = -EBADMSG;
> > > + goto out;
> > > + }
> > > +
> > > + ret = sha256_regions(regs, pe_hash);
> > > + if (ret)
> > > + goto out;
> > > + if (memcmp(pe_hash, a.pe_digest, sizeof(pe_hash))) {
> > > + pr_err("image digest mismatch\n");
> > > + ret = -EBADMSG;
> > > + goto out;
> > > + }
> > > +
> > > + ret = sha256_buf(a.spc, a.spc_len, hash);
> > > + if (ret)
> > > + goto out;
> > > + if (memcmp(hash, a.attr_digest, sizeof(hash))) {
> > > + pr_err("signed attributes do not match the content\n");
> > > + ret = -EBADMSG;
> > > + goto out;
> > > + }
> > > +
> > > + ret = sha256_attrs(&a, hash);
> > > + if (ret)
> > > + goto out;
> > > +
> > > + kr = keyring_find(keyring);
> > > + if (!kr) {
> > > + pr_err("keyring '%s' not registered\n", keyring);
> > > + ret = -ENOKEY;
> > > + goto out;
> > > + }
> > > +
> > > + ret = -ENOKEY;
> > > + for_each_key_in_keyring(key, kr) {
> > > + if (!public_key_verify(key, a.sig, a.sig_len, hash, HASH_ALGO_SHA256)) {
> > > + pr_info("verified with key '%s'\n", key->key_name_hint ?: "?");
> > > + ret = 0;
> > > + break;
> > > + }
> > > + }
> > > +
> > > + if (ret)
> > > + pr_err("no key in keyring '%s' verifies the signature\n", keyring);
> > > +out:
> > > + free(regs);
> > > + return ret;
> > > +}
> > > diff --git a/include/efi/loader/authenticode.h b/include/efi/loader/authenticode.h
> > > new file mode 100644
> > > index 0000000000..24c46ca7cf
> > > --- /dev/null
> > > +++ b/include/efi/loader/authenticode.h
> > > @@ -0,0 +1,9 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-only */
> > > +#ifndef __EFI_LOADER_AUTHENTICODE_H
> > > +#define __EFI_LOADER_AUTHENTICODE_H
> > > +
> > > +#include <linux/types.h>
> > > +
> > > +int efi_authenticode_verify(void *efi, size_t len, const char *keyring);
> > > +
> > > +#endif
> >
> >
> > --
> > Pengutronix e.K. | |
> > Steuerwalder Str. 21 | http://www.pengutronix.de/ |
> > 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
> > Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
> >
>
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v1 09/14] efi: loader: authenticode: add a fuzz test
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (7 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 08/14] efi: loader: verify Authenticode signatures against built-in keys Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 10/14] efi: loader: authenticate LoadImage() images when signing is forced Johannes Schneider
` (4 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
authenticode_parse() walks the DER of a PKCS#7 signature taken from the
image before anything about the image is verified. Fuzz it, and the
digest of the signed attributes it hands on, the way the PE parser is
fuzzed.
As EFI_PE_PARSER does for the PE parser, a separate EFI_AUTHENTICODE
builds the verifier without the EFI loader under COMPILE_TEST;
EFI_LOADER_AUTHENTICODE selects it. The libfuzzer configuration enables
it.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
common/boards/configs/libfuzzer.config | 1 +
efi/Kconfig | 11 +++++++++++
efi/loader/Kconfig | 2 +-
efi/loader/Makefile | 2 +-
efi/loader/authenticode.c | 13 +++++++++++++
5 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/common/boards/configs/libfuzzer.config b/common/boards/configs/libfuzzer.config
index f0a298559f..40c90d644a 100644
--- a/common/boards/configs/libfuzzer.config
+++ b/common/boards/configs/libfuzzer.config
@@ -16,6 +16,7 @@ CONFIG_DEBUG_MEMLEAK=y
CONFIG_TEST=y
CONFIG_COMPILE_TEST=y
CONFIG_EFI_PE_PARSER=y
+CONFIG_EFI_AUTHENTICODE=y
CONFIG_JWT=y
CONFIG_FUZZ=y
CONFIG_FUZZ_EXTERNAL=y
diff --git a/efi/Kconfig b/efi/Kconfig
index e3f3941831..6c8d6b9de4 100644
--- a/efi/Kconfig
+++ b/efi/Kconfig
@@ -12,6 +12,17 @@ config EFI_PE_PARSER
This can be enabled without the full EFI loader to compile-test and
fuzz PE image parsing.
+config EFI_AUTHENTICODE
+ bool "Authenticode signature verifier" if COMPILE_TEST
+ depends on CRYPTO_BUILTIN_KEYS && HAVE_DIGEST_SHA256
+ select CRYPTO_RSA
+ select EFI_PE_PARSER
+ help
+ Build the Authenticode (PKCS#7) verifier used by the EFI loader.
+
+ This can be enabled without the full EFI loader to compile-test and
+ fuzz the parsing of Authenticode signatures.
+
config EFI_PAYLOAD
bool "barebox as EFI payload/app (consumer)"
depends on HAVE_EFI_PAYLOAD || COMPILE_TEST
diff --git a/efi/loader/Kconfig b/efi/loader/Kconfig
index 4099da0689..8345fccd1a 100644
--- a/efi/loader/Kconfig
+++ b/efi/loader/Kconfig
@@ -27,7 +27,7 @@ config EFI_LOADER_SECURE_BOOT
config EFI_LOADER_AUTHENTICODE
bool "Verify Authenticode signatures of booted EFI images"
depends on CRYPTO_BUILTIN_KEYS && HAVE_DIGEST_SHA256
- select CRYPTO_RSA
+ select EFI_AUTHENTICODE
help
Verify the Authenticode (PKCS#7, SHA-256, RSA) signature of an EFI
image booted with bootm against the keys compiled into the "efi"
diff --git a/efi/loader/Makefile b/efi/loader/Makefile
index 775014dc22..f590fb278a 100644
--- a/efi/loader/Makefile
+++ b/efi/loader/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_EFI_PE_PARSER) += pe.o
+obj-$(CONFIG_EFI_AUTHENTICODE) += authenticode.o
ifeq ($(CONFIG_EFI_LOADER),y)
@@ -14,7 +15,6 @@ obj-y += boot.o
obj-y += runtime.o
obj-y += setup.o
obj-y += watchdog.o
-obj-$(CONFIG_EFI_LOADER_AUTHENTICODE) += authenticode.o
obj-y += loadopts.o
obj-y += efi_var_common.o
obj-y += efi_variable.o
diff --git a/efi/loader/authenticode.c b/efi/loader/authenticode.c
index 36e5a46fc6..ef2ea895e1 100644
--- a/efi/loader/authenticode.c
+++ b/efi/loader/authenticode.c
@@ -15,6 +15,7 @@
#include <efi/loader/authenticode.h>
#include <efi/error.h>
#include <pe.h>
+#include <fuzz.h>
struct der {
const u8 *p;
@@ -340,6 +341,18 @@ static int sha256_attrs(const struct authenticode *a, u8 *out)
return ret;
}
+static int fuzz_authenticode(const u8 *data, size_t size)
+{
+ struct authenticode a = {};
+ u8 hash[SHA256_DIGEST_SIZE];
+
+ if (!authenticode_parse(&a, data, size))
+ sha256_attrs(&a, hash);
+
+ return 0;
+}
+fuzz_test("authenticode", fuzz_authenticode);
+
/**
* efi_authenticode_verify() - verify a PE image's Authenticode signature
* @efi: PE image
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 10/14] efi: loader: authenticate LoadImage() images when signing is forced
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (8 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 09/14] efi: loader: authenticode: add a fuzz test Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 11/14] efi: loader: file: expose no filesystem when signed images are forced Johannes Schneider
` (3 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
efi_image_authenticate() accepts every image, so with signed images
forced, a verified payload can still load unsigned images through
LoadImage(). systemd-stub does exactly that for the addons it finds next
to a UKI, PE files carrying .cmdline, .dtb and .initrd sections, without
shim through plain LoadImage(). An unsigned addon could thus replace
the kernel command line or devicetree of a signed UKI.
With signed images forced, verify images in LoadImage() against the
"efi" keyring, and have StartImage() refuse images that failed: as the
UEFI specification has it, LoadImage() still creates the handle when it
returns EFI_SECURITY_VIOLATION.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/Kconfig | 3 ++-
efi/loader/boot.c | 3 +++
efi/loader/pe.c | 7 ++++++-
include/efi/loader/authenticode.h | 11 +++++++++++
4 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/efi/loader/Kconfig b/efi/loader/Kconfig
index 8345fccd1a..156123212b 100644
--- a/efi/loader/Kconfig
+++ b/efi/loader/Kconfig
@@ -33,7 +33,8 @@ config EFI_LOADER_AUTHENTICODE
image booted with bootm against the keys compiled into the "efi"
keyring (CONFIG_CRYPTO_PUBLIC_KEYS, keyring=efi). With signed images
forced, an EFI image then boots only if one of those keys verifies
- it, the same way a FIT image must carry a valid signature.
+ it, the same way a FIT image must carry a valid signature, and so
+ do the images an EFI payload loads through LoadImage().
X.509 certificates in the signature are not evaluated: trust is
anchored in the keyring. barebox does not report UEFI Secure Boot
diff --git a/efi/loader/boot.c b/efi/loader/boot.c
index 2d98c95b5c..3c9489e951 100644
--- a/efi/loader/boot.c
+++ b/efi/loader/boot.c
@@ -3098,6 +3098,9 @@ efi_status_t __efi_start_image(efi_handle_t image_handle,
if (image_obj->header.type != EFI_OBJECT_TYPE_LOADED_IMAGE)
return EFI_EXIT(EFI_INVALID_PARAMETER);
+ if (image_obj->auth_status != EFI_IMAGE_AUTH_PASSED)
+ return EFI_EXIT(EFI_SECURITY_VIOLATION);
+
ret = EFI_CALL(efi_open_protocol(image_handle, &efi_loaded_image_protocol_guid,
(void **)&info, NULL, NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL));
diff --git a/efi/loader/pe.c b/efi/loader/pe.c
index 827b50378c..efec38b111 100644
--- a/efi/loader/pe.c
+++ b/efi/loader/pe.c
@@ -18,6 +18,8 @@
#include <efi/memory.h>
#include <efi/loader.h>
#include <efi/loader/pe.h>
+#include <efi/loader/authenticode.h>
+#include <bootm.h>
#include <efi/guid.h>
#include <efi/error.h>
#include <pe.h>
@@ -895,7 +897,10 @@ const void *efi_pe_find_section(void *efi, size_t len, const char *name,
#ifdef CONFIG_EFI_LOADER
static bool efi_image_authenticate(void *efi, size_t efi_size)
{
- return true;
+ if (!IS_ENABLED(CONFIG_BOOTM) || !bootm_signed_images_are_forced())
+ return true;
+
+ return !efi_authenticode_verify(efi, efi_size, EFI_AUTHENTICODE_KEYRING);
}
/**
diff --git a/include/efi/loader/authenticode.h b/include/efi/loader/authenticode.h
index 24c46ca7cf..df4fb7eab0 100644
--- a/include/efi/loader/authenticode.h
+++ b/include/efi/loader/authenticode.h
@@ -3,7 +3,18 @@
#define __EFI_LOADER_AUTHENTICODE_H
#include <linux/types.h>
+#include <linux/errno.h>
+#define EFI_AUTHENTICODE_KEYRING "efi"
+
+#ifdef CONFIG_EFI_AUTHENTICODE
int efi_authenticode_verify(void *efi, size_t len, const char *keyring);
+#else
+static inline int efi_authenticode_verify(void *efi, size_t len,
+ const char *keyring)
+{
+ return -ENOSYS;
+}
+#endif
#endif
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 11/14] efi: loader: file: expose no filesystem when signed images are forced
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (9 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 10/14] efi: loader: authenticate LoadImage() images when signing is forced Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 12/14] bootm: efi: boot signed EFI images " Johannes Schneider
` (2 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
systemd-stub packs systemd credentials (*.cred) and system and
configuration extensions (*.raw) from the directory of a UKI into its
initrd, and none of them are signed or go through LoadImage(). With
signed images forced, anyone able to write next to a UKI booted from a
filesystem could pass credentials to the booted system, such as a root
password.
With signed images forced, let OpenVolume() report EFI_UNSUPPORTED for
every volume, the answer for "no filesystem here". This also keeps
LoadImage() from loading images by file device path.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/protocols/file.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/efi/loader/protocols/file.c b/efi/loader/protocols/file.c
index bfcf14f0e0..11b9e9e6f2 100644
--- a/efi/loader/protocols/file.c
+++ b/efi/loader/protocols/file.c
@@ -32,6 +32,7 @@
#include <fs.h>
#include <linux/sprintf.h>
#include <xfuncs.h>
+#include <bootm.h>
#define MAX_UTF8_PER_UTF16 3
@@ -1014,6 +1015,9 @@ efi_open_volume(struct efi_simple_file_system_protocol *this,
EFI_ENTRY("%p, %p", this, root);
+ if (IS_ENABLED(CONFIG_BOOTM) && bootm_signed_images_are_forced())
+ return EFI_EXIT(EFI_UNSUPPORTED);
+
/* No filesystem on the volume is EFI_UNSUPPORTED, not a device error */
if (!cdev_get_mount_path(fs->cdev)) {
char *devpath = basprintf("/dev/%s", cdev_name(fs->cdev));
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 12/14] bootm: efi: boot signed EFI images when signed images are forced
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (10 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 11/14] efi: loader: file: expose no filesystem when signed images are forced Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 13/14] efi: loader: bootm: install a devicetree for matching UKI devicetrees Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 14/14] efi: loader: bootm: apply overlays carried by a UKI Johannes Schneider
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
With signed images forced, bootm refuses everything but FIT images. Let
EFI images through when Authenticode support is built in, and have the
EFI loader verify them against the "efi" keyring before loading:
mandatory with signed images forced or bootm.verify=signature, skipped
with bootm.verify=none, reported otherwise.
Not when barebox itself runs as EFI payload: its EFI application
handler passes the image to the firmware's LoadImage() and verifies
nothing itself.
An image booted this way is verified twice: here, so that barebox does
not parse anything of an image that is not authenticated, and again in
LoadImage(), which verifies every image a payload loads and cannot rely
on an earlier verdict. Skipping either would need the result of the
first carried over to the second, a shortcut that has to stay correct
as both paths change; the second verification costs 70 ms for a 30 MiB
UKI on an i.MX8MP.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
common/bootm.c | 6 +++++-
efi/loader/bootm.c | 29 +++++++++++++++++++++++++++++
2 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/common/bootm.c b/common/bootm.c
index 27da1a590b..c8fc5220a6 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -576,7 +576,11 @@ struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data)
*/
data->oftree_file = NULL;
data->initrd_file = NULL;
- if (data->image_type != filetype_fit) {
+ if (data->image_type == filetype_exe &&
+ IS_ENABLED(CONFIG_EFI_LOADER_AUTHENTICODE) &&
+ !efi_is_payload()) {
+ /* authenticated by the EFI loader before it runs */
+ } else if (data->image_type != filetype_fit) {
pr_err("Signed boot and image is no FIT image, aborting\n");
ret = -EINVAL;
goto err_out;
diff --git a/efi/loader/bootm.c b/efi/loader/bootm.c
index 8a83865458..fa12caf560 100644
--- a/efi/loader/bootm.c
+++ b/efi/loader/bootm.c
@@ -35,6 +35,7 @@
#include <efi/error.h>
#include <efi/initrd.h>
#include <efi/devicepath.h>
+#include <efi/loader/authenticode.h>
#include <efi/loader/pe.h>
#include <loadable.h>
@@ -195,6 +196,30 @@ static efi_status_t efi_install_initrd(struct image_data *data,
return EFI_SUCCESS;
}
+static int efi_loader_verify(struct image_data *data, void *efi, size_t size)
+{
+ bool required = bootm_signed_images_are_forced() ||
+ data->verify == BOOTM_VERIFY_SIGNATURE;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_EFI_LOADER_AUTHENTICODE)) {
+ if (required) {
+ pr_err("signed image required, but no Authenticode support\n");
+ return -EPERM;
+ }
+ return 0;
+ }
+
+ if (!required && data->verify == BOOTM_VERIFY_NONE)
+ return 0;
+
+ ret = efi_authenticode_verify(efi, size, EFI_AUTHENTICODE_KEYRING);
+ if (ret && required)
+ return -EPERM;
+
+ return 0;
+}
+
/* The image may sit in a much larger partition: load only the image */
static const struct resource *efi_load_os(struct image_data *data,
resource_size_t start,
@@ -266,6 +291,10 @@ static int efi_loader_bootm(struct image_data *data)
return -EINVAL;
}
+ ret = efi_loader_verify(data, (void *)os_res->start, size);
+ if (ret)
+ return ret;
+
/* systemd-stub passes the load options on as kernel command line */
if (filetype_is_linux_efi_image(data->kernel_type) ||
efi_pe_find_section((void *)os_res->start, size, ".linux",
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 13/14] efi: loader: bootm: install a devicetree for matching UKI devicetrees
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (11 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 12/14] bootm: efi: boot signed EFI images " Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
2026-10-04 1:19 ` [PATCH v1 14/14] efi: loader: bootm: apply overlays carried by a UKI Johannes Schneider
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
systemd-stub replaces the devicetree installed as EFI configuration
table with the .dtbauto section of a UKI whose first compatible equals
the installed devicetree's first compatible, or else with a .dtb
section. Without CONFIG_BOOTM_OFTREE_FALLBACK, bootm_get_devicetree()
returns nothing for an image without a devicetree of its own, the stub
would then pick no .dtbauto section, and the kernel starts on an empty
devicetree, silently without a console:
EFI stub: Generating empty DTB
EFI stub: Exiting boot services...
Have barebox check if a .dtbauto section in the UKI matches the
machine compatible, and if so install a devicetree consisting of a
root node with only the full machine compatible, vendor prefix
included, to give the stub something valid to compare and pick the
matching .dtbauto section by. barebox then fixes up that section
through EFI_DT_FIXUP_PROTOCOL.
If no section matches, the UKI carries no .dtb and bootm provides no
devicetree either, refuse the UKI before starting it, so that the boot
fails where it can be seen and bootchooser can fall back.
All of the above only if barebox describes hardware by a devicetree,
i.e. with CONFIG_OFTREE, so that the EFI loader keeps building without
the devicetree code.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/bootm.c | 99 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 98 insertions(+), 1 deletion(-)
diff --git a/efi/loader/bootm.c b/efi/loader/bootm.c
index fa12caf560..47f7a3d1e2 100644
--- a/efi/loader/bootm.c
+++ b/efi/loader/bootm.c
@@ -38,6 +38,9 @@
#include <efi/loader/authenticode.h>
#include <efi/loader/pe.h>
#include <loadable.h>
+#include <of.h>
+#include <barebox-info.h>
+#include <linux/libfdt.h>
/**
* copy_fdt() - Copy the device tree to a new location available to EFI
@@ -220,6 +223,86 @@ static int efi_loader_verify(struct image_data *data, void *efi, size_t size)
return 0;
}
+static const char *efi_machine_compatible(void)
+{
+ const char *compat = barebox_get_of_machine_compatible();
+
+ /* with vendor prefix, unlike of_get_machine_compatible() */
+ if (!compat)
+ of_property_read_string(of_get_root_node(), "compatible", &compat);
+
+ return compat;
+}
+
+/* The first compatible of the devicetree root, as systemd-stub compares it */
+static bool efi_dtb_first_compatible_is(const void *dtb, size_t len,
+ const char *compat)
+{
+ const char *prop;
+ int plen;
+
+ if (len < sizeof(struct fdt_header) || fdt_check_header(dtb) ||
+ fdt_totalsize(dtb) > len)
+ return false;
+
+ prop = fdt_getprop(dtb, 0, "compatible", &plen);
+
+ return prop && plen > 0 && strnlen(prop, plen) < plen &&
+ !strcmp(prop, compat);
+}
+
+static bool efi_uki_has_dtbauto_for(void *efi, size_t size, const char *compat)
+{
+ const void *dtb;
+ size_t len;
+ int index = 0;
+
+ while ((dtb = efi_pe_find_next_section(efi, size, ".dtbauto", &len,
+ &index)))
+ if (efi_dtb_first_compatible_is(dtb, len, compat))
+ return true;
+
+ return false;
+}
+
+/*
+ * Without a devicetree installed and without a .dtb section, systemd-stub
+ * starts the kernel on no devicetree at all, and it boots without a console
+ */
+static bool efi_uki_lacks_devicetree(void *efi, size_t size, const char *compat)
+{
+ size_t len;
+
+ if (!efi_pe_find_section(efi, size, ".linux", &len) ||
+ efi_pe_find_section(efi, size, ".dtb", &len))
+ return false;
+
+ if (!efi_pe_find_section(efi, size, ".dtbauto", &len))
+ pr_err("UKI brings no devicetree and none was given\n");
+ else if (compat)
+ pr_err("UKI has no devicetree for \"%s\"\n", compat);
+ else
+ pr_err("no machine compatible to select a UKI devicetree with\n");
+
+ return true;
+}
+
+/* Just enough of a devicetree for systemd-stub to pick a .dtbauto section */
+static void *efi_uki_matching_devicetree(const char *compat)
+{
+ struct device_node *root;
+ void *fdt;
+
+ root = of_new_node(NULL, NULL);
+ of_property_write_string(root, "compatible", compat);
+ fdt = of_flatten_dtb(root);
+ of_delete_node(root);
+
+ pr_info("selecting the UKI devicetree for \"%s\"\n", compat);
+
+ return fdt ?: ERR_PTR(-ENOMEM);
+}
+
/* The image may sit in a much larger partition: load only the image */
static const struct resource *efi_load_os(struct image_data *data,
resource_size_t start,
@@ -278,6 +361,8 @@ static int efi_loader_bootm(struct image_data *data)
void *fdt;
int flags = 0;
size_t size, section_size;
+ const char *compat;
+ bool match;
memory_bank_first_find_space(&start, &end);
@@ -295,6 +380,10 @@ static int efi_loader_bootm(struct image_data *data)
if (ret)
return ret;
+ compat = efi_machine_compatible();
+ match = IS_ENABLED(CONFIG_OFTREE) && compat &&
+ efi_uki_has_dtbauto_for((void *)os_res->start, size, compat);
+
/* systemd-stub passes the load options on as kernel command line */
if (filetype_is_linux_efi_image(data->kernel_type) ||
efi_pe_find_section((void *)os_res->start, size, ".linux",
@@ -322,11 +411,19 @@ static int efi_loader_bootm(struct image_data *data)
ret = -EINVAL;
- fdt = bootm_get_devicetree(data);
+ if (match)
+ fdt = efi_uki_matching_devicetree(compat);
+ else
+ fdt = bootm_get_devicetree(data);
if (IS_ERR(fdt)) {
ret = PTR_ERR(fdt);
goto out;
}
+ if (!fdt && IS_ENABLED(CONFIG_OFTREE) &&
+ efi_uki_lacks_devicetree((void *)os_res->start, size, compat)) {
+ ret = -ENODEV;
+ goto out;
+ }
if (fdt) {
/* efi_install_fdt() installs a copy */
ret = efi_install_fdt(fdt);
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH v1 14/14] efi: loader: bootm: apply overlays carried by a UKI
2026-10-04 1:19 [PATCH v1 00/14] efi: loader: boot Authenticode-signed UKIs Johannes Schneider
` (12 preceding siblings ...)
2026-10-04 1:19 ` [PATCH v1 13/14] efi: loader: bootm: install a devicetree for matching UKI devicetrees Johannes Schneider
@ 2026-10-04 1:19 ` Johannes Schneider
13 siblings, 0 replies; 19+ messages in thread
From: Johannes Schneider @ 2026-10-04 1:19 UTC (permalink / raw)
To: barebox; +Cc: Marco Felsch, Johannes Schneider
A UKI has no place for the overlays a FIT image carries next to its
devicetrees, and systemd-stub knows nothing about overlays. Carry them
in a ".bbdtbo" PE section, covered by the image's Authenticode
signature: a devicetree whose child nodes each hold one overlay in a
"data" property, named like the overlay file.
Unpack them to /tmp/efi-overlays and point global.of.overlay.path there
while the payload runs, so EFI_DT_FIXUP_PROTOCOL applies them to the
devicetree systemd-stub installs, filtered by global.of.overlay.filter
and pattern like overlays from a FIT image. Without such a section the
path is empty for that time, as it may point at the boot partition,
which now holds the UKI. The previous value is restored when the payload
returns.
Refuse a UKI whose overlay section cannot be unpacked, which would
otherwise boot without the hardware its overlays describe.
Only with CONFIG_OFTREE, which EFI_DT_FIXUP_PROTOCOL depends on.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
efi/loader/bootm.c | 86 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 85 insertions(+), 1 deletion(-)
diff --git a/efi/loader/bootm.c b/efi/loader/bootm.c
index 47f7a3d1e2..71c61935b1 100644
--- a/efi/loader/bootm.c
+++ b/efi/loader/bootm.c
@@ -37,6 +37,7 @@
#include <efi/devicepath.h>
#include <efi/loader/authenticode.h>
#include <efi/loader/pe.h>
+#include <globalvar.h>
#include <loadable.h>
#include <of.h>
#include <barebox-info.h>
@@ -223,6 +224,62 @@ static int efi_loader_verify(struct image_data *data, void *efi, size_t size)
return 0;
}
+#define EFI_UKI_OVERLAY_SECTION ".bbdtbo"
+#define EFI_UKI_OVERLAY_DIR "/tmp/efi-overlays"
+
+/*
+ * The overlay section is a devicetree whose child nodes each hold one overlay
+ * in a "data" property, named like the overlay file
+ */
+static int efi_uki_unpack_overlays(void *efi, size_t size)
+{
+ struct device_node *root, *np;
+ const void *blob, *ovl;
+ char *path;
+ size_t len;
+ int ovl_len, n = 0, ret = 0;
+
+ blob = efi_pe_find_section(efi, size, EFI_UKI_OVERLAY_SECTION, &len);
+ if (!blob)
+ return 0;
+
+ root = of_unflatten_dtb(blob, len);
+ if (IS_ERR(root))
+ return PTR_ERR(root);
+
+ unlink_recursive(EFI_UKI_OVERLAY_DIR, NULL);
+ ret = make_directory(EFI_UKI_OVERLAY_DIR);
+ if (ret)
+ goto out;
+
+ for_each_child_of_node(root, np) {
+ ovl = of_get_property(np, "data", &ovl_len);
+ if (!ovl)
+ continue;
+
+ path = basprintf(EFI_UKI_OVERLAY_DIR "/%s", np->name);
+ ret = write_file(path, ovl, ovl_len);
+ free(path);
+ if (ret)
+ goto out;
+ n++;
+ }
+
+ pr_info("unpacked %d overlay(s) to %s\n", n, EFI_UKI_OVERLAY_DIR);
+ ret = n;
+out:
+ of_delete_node(root);
+ return ret;
+}
+
+static bool efi_image_brings_devicetree(void *efi, size_t size)
+{
+ size_t len;
+
+ return efi_pe_find_section(efi, size, ".dtbauto", &len) ||
+ efi_pe_find_section(efi, size, ".dtb", &len);
+}
+
static const char *efi_machine_compatible(void)
{
const char *compat = barebox_get_of_machine_compatible();
@@ -361,8 +418,9 @@ static int efi_loader_bootm(struct image_data *data)
void *fdt;
int flags = 0;
size_t size, section_size;
+ char *overlay_path = NULL;
const char *compat;
- bool match;
+ bool match, uki;
memory_bank_first_find_space(&start, &end);
@@ -384,6 +442,11 @@ static int efi_loader_bootm(struct image_data *data)
match = IS_ENABLED(CONFIG_OFTREE) && compat &&
efi_uki_has_dtbauto_for((void *)os_res->start, size, compat);
+ uki = IS_ENABLED(CONFIG_OFTREE) &&
+ efi_image_brings_devicetree((void *)os_res->start, size);
+ if (uki)
+ overlay_path = xstrdup(getenv("global.of.overlay.path") ?: "");
+
/* systemd-stub passes the load options on as kernel command line */
if (filetype_is_linux_efi_image(data->kernel_type) ||
efi_pe_find_section((void *)os_res->start, size, ".linux",
@@ -432,6 +495,18 @@ static int efi_loader_bootm(struct image_data *data)
goto out;
}
+ if (uki) {
+ int n = efi_uki_unpack_overlays((void *)os_res->start, size);
+
+ if (n < 0) {
+ pr_err("cannot unpack the UKI overlays: %pe\n", ERR_PTR(n));
+ ret = n;
+ goto out;
+ }
+
+ globalvar_set("of.overlay.path", n ? EFI_UKI_OVERLAY_DIR : "");
+ }
+
efiret = efi_install_initrd(data, os_res->end + 1);
if(efiret != EFI_SUCCESS)
goto out;
@@ -502,9 +577,18 @@ static int efi_loader_bootm(struct image_data *data)
/* Control is returned to us, disable EFI watchdog */
efi_set_watchdog(0);
+ if (overlay_path) {
+ globalvar_set("of.overlay.path", overlay_path);
+ free(overlay_path);
+ }
+
return -efi_errno(efiret);
out:
+ if (overlay_path) {
+ globalvar_set("of.overlay.path", overlay_path);
+ free(overlay_path);
+ }
efi_initrd_unregister();
efi_install_configuration_table(&efi_fdt_guid, NULL);
efi_free_pool(file_path);
--
2.43.0
^ permalink raw reply [flat|nested] 19+ messages in thread