+
+static bool efifw_validate_header(
+ const void *blob,
+ size_t blob_len,
+ const char **ret_fwid,
+ const char **ret_payload) {
+
+ if ((uintptr_t) blob % alignof(EfiFwHeader) != 0)
+ return false;
+
+ size_t base_sz = offsetof(EfiFwHeader, payload);
+
+ /* at least the base size of the header must be in memory */
+ if (blob_len < base_sz)
+ return false;
+
+ const EfiFwHeader *fw_header = ASSERT_PTR(blob);
+
+ if (fw_header->magic != FWHEADERMAGIC)
+ return false;
+
+ uint32_t header_len = fw_header->header_len;
+
+ /* header_len must not be malformed */
+ if (header_len < base_sz)
+ return false;
+
+ uint32_t fwid_len = fw_header->fwid_len;
+ uint32_t payload_len = fw_header->payload_len;
+ size_t total_computed_size;
+
+ /* check for unusually large values of payload_len, header_len or fwid_len */
+ if (!ADD_SAFE(&total_computed_size, header_len, fwid_len) ||
+ !ADD_SAFE(&total_computed_size, total_computed_size, payload_len))
+ return false;
+
+ /* see if entire size of the base header is present in memory */
+ if (blob_len < total_computed_size)
+ return false;
+
+ const char *fwid = (const char*)blob + header_len;
+ const char *payload = fwid + fwid_len;
+
+ /* check that fwid points to a NUL terminated string */
+ if (memchr(fwid, 0, fwid_len) != fwid + fwid_len - 1)
+ return false;
+
+ if (ret_fwid)
+ *ret_fwid = fwid;
+
+ if (ret_payload)
+ *ret_payload = payload;
+ return true;
+}
+
+static const char* efifw_get_fwid(
+ const void *efifwblob,
+ size_t efifwblob_len) {
+
+ const char* fwid;
+ if (!efifw_validate_header(efifwblob, efifwblob_len, &fwid, NULL))
+ return NULL;
+
+ return fwid;
+}
+
+EFI_STATUS efifirmware_match_by_fwid(
+ const void *uki_efifw,
+ size_t uki_efifw_len,
+ const char *fwid) {
+
+ assert(fwid);
+
+ const char *fwblob_fwid = efifw_get_fwid(uki_efifw, uki_efifw_len);
+ if (!fwblob_fwid)
+ return EFI_INVALID_PARAMETER;
+
+ return streq8(fwblob_fwid, fwid) ? EFI_SUCCESS : EFI_NOT_FOUND;
+}
diff --git a/src/boot/efifirmware.h b/src/boot/efifirmware.h
new file mode 100644
index 00000000000..888de35a983
--- /dev/null
+++ b/src/boot/efifirmware.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define FWHEADERMAGIC (UINT32_C(0xfeeddead))
+
+/* The structure of the efifw UKI blob is the following:
+ * ---------------------------------------------------------
+ * EfiFw header|fwid|payload| reserved for future attributes
+ * ---------------------------------------------------------
+ * The base header defines the length of full header, fwid and payload.
+ * The fwid is a NUL terminated string.
+ * The payload contains the actual efi firmware.
+ */
+typedef struct EfiFwHeader {
+ uint32_t magic; /* magic number that defines Efifw */
+ uint32_t header_len; /* total length of header including all attributes */
+ uint32_t fwid_len; /* length including the NUL terminator */
+ uint32_t payload_len; /* actual length of the efi firmware binary image */
+
+ /* The header might be extended in the future to add additional
+ * parameters. header_len will increase to indicate presence of these
+ * additional attributes.
+ */
+
+ /* next comes payload which is fwid and efi firmware binary blob */
+ uint8_t payload[] _alignas_(uint64_t);
+} EfiFwHeader;
+
+EFI_STATUS efifirmware_match_by_fwid(const void *uki_efifw, size_t uki_efifw_length, const char *fwid);
diff --git a/src/boot/meson.build b/src/boot/meson.build
index f7269962334..31f36ea46d4 100644
--- a/src/boot/meson.build
+++ b/src/boot/meson.build
@@ -284,6 +284,7 @@ libefi_sources = files(
'console.c',
'device-path-util.c',
'devicetree.c',
+ 'efifirmware.c',
'drivers.c',
'efi-string.c',
'efivars.c',
diff --git a/src/boot/pe.c b/src/boot/pe.c
index 97ea8d7220a..348296f0016 100644
--- a/src/boot/pe.c
+++ b/src/boot/pe.c
@@ -2,6 +2,7 @@
#include "chid.h"
#include "devicetree.h"
+#include "efifirmware.h"
#include "pe.h"
#include "util.h"
@@ -195,6 +196,33 @@ static bool pe_use_this_dtb(
return false;
}
+static bool pe_use_this_firmware(
+ const void *efifw,
+ size_t efifw_size,
+ const void *base,
+ const Device *device,
+ size_t section_nb) {
+
+ assert(efifw);
+
+ EFI_STATUS err;
+
+ /* if there is no hwids section, there is nothing much we can do */
+ if (!device || !base)
+ return false;
+
+ const char *fwid = device_get_fwid(base, device);
+ if (!fwid)
+ return false;
+
+ err = efifirmware_match_by_fwid(efifw, efifw_size, fwid);
+ if (err == EFI_SUCCESS)
+ return true;
+ if (err == EFI_INVALID_PARAMETER)
+ log_error_status(err, "Found bad efifw blob in PE section %zu", section_nb);
+ return false;
+}
+
static void pe_locate_sections_internal(
const PeSectionHeader section_table[],
size_t n_section_table,
@@ -255,6 +283,20 @@ static void pe_locate_sections_internal(
continue;
}
+ /* handle efifw section which works very much like .dtbauto */
+ if (pe_section_name_equal(section_names[i], ".efifw")) {
+ /* can't match without validate_base */
+ if (!validate_base)
+ break;
+ if (!pe_use_this_firmware(
+ (const uint8_t *) SIZE_TO_PTR(validate_base) + j->VirtualAddress,
+ j->VirtualSize,
+ device_table,
+ device,
+ (PTR_TO_SIZE(j) - PTR_TO_SIZE(section_table)) / sizeof(*j)))
+ continue;
+ }
+
/* At this time, the sizes and offsets have been validated. Store them away */
sections[i] = (PeSectionVector) {
.memory_size = j->VirtualSize,
diff --git a/src/fundamental/uki.c b/src/fundamental/uki.c
index 441d466a97e..06361cc5271 100644
--- a/src/fundamental/uki.c
+++ b/src/fundamental/uki.c
@@ -23,5 +23,6 @@ const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = {
[UNIFIED_SECTION_PROFILE] = ".profile",
[UNIFIED_SECTION_DTBAUTO] = ".dtbauto",
[UNIFIED_SECTION_HWIDS] = ".hwids",
+ [UNIFIED_SECTION_EFIFW] = ".efifw",
NULL,
};
diff --git a/src/fundamental/uki.h b/src/fundamental/uki.h
index 4b6195f9b70..6752b2ae77c 100644
--- a/src/fundamental/uki.h
+++ b/src/fundamental/uki.h
@@ -20,6 +20,7 @@ typedef enum UnifiedSection {
UNIFIED_SECTION_PROFILE,
UNIFIED_SECTION_DTBAUTO,
UNIFIED_SECTION_HWIDS,
+ UNIFIED_SECTION_EFIFW,
_UNIFIED_SECTION_MAX,
} UnifiedSection;
diff --git a/src/measure/measure.c b/src/measure/measure.c
index dcae8528e07..c8e4d59688e 100644
--- a/src/measure/measure.c
+++ b/src/measure/measure.c
@@ -160,8 +160,9 @@ static int parse_argv(int argc, char *argv[]) {
ARG_PCRPKEY,
ARG_PROFILE,
ARG_DTBAUTO,
+ ARG_HWIDS,
_ARG_SECTION_LAST,
- ARG_HWIDS = _ARG_SECTION_LAST,
+ ARG_EFIFW = _ARG_SECTION_LAST,
ARG_BANK,
ARG_PRIVATE_KEY,
ARG_PRIVATE_KEY_SOURCE,
diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py
index 5f821297c12..3bf0f458573 100755
--- a/src/ukify/ukify.py
+++ b/src/ukify/ukify.py
@@ -262,6 +262,7 @@ class UkifyConfig:
efi_arch: str
hwids: Path
initrd: list[Path]
+ efifw: list[Path]
join_profiles: list[Path]
sign_profiles: list[str]
json: Union[Literal['pretty'], Literal['short'], Literal['off']]
@@ -391,6 +392,7 @@ DEFAULT_SECTIONS_TO_SHOW = {
'.dtb': 'binary',
'.dtbauto': 'binary',
'.hwids': 'binary',
+ '.efifw': 'binary',
'.cmdline': 'text',
'.osrel': 'text',
'.uname': 'text',
@@ -473,7 +475,10 @@ class UKI:
if s.name == '.profile':
start = i + 1
- if any(section.name == s.name for s in self.sections[start:] if s.name != '.dtbauto'):
+ multiple_allowed_sections = ['.dtbauto', '.efifw']
+ if any(
+ section.name == s.name for s in self.sections[start:] if s.name not in multiple_allowed_sections
+ ):
raise ValueError(f'Duplicate section {section.name}')
self.sections += [section]
@@ -665,7 +670,10 @@ def check_inputs(opts: UkifyConfig) -> None:
elif isinstance(value, list):
for item in value:
if isinstance(item, Path):
- item.open().close()
+ if item.is_dir():
+ item.iterdir()
+ else:
+ item.open().close()
check_splash(opts.splash)
@@ -1049,6 +1057,10 @@ NULL_DEVICE = b'\0' * DEVICE_STRUCT_SIZE
DEVICE_TYPE_DEVICETREE = 1
DEVICE_TYPE_UEFI_FW = 2
+# Keep in sync with efifirmware.h
+FWHEADERMAGIC = 'feeddead'
+EFIFW_HEADER_SIZE = 4 + 4 + 4 + 4
+
def device_make_descriptor(device_type: int, size: int) -> int:
return (size) | (device_type << 28)
@@ -1139,6 +1151,42 @@ def parse_hwid_dir(path: Path) -> bytes:
return devices_blob + strings_blob
+def parse_efifw_dir(path: Path) -> bytes:
+ if not path.is_dir():
+ raise ValueError(f'{path} is not a directory or it does not exist.')
+
+ # only one firmware image must be present in the directory
+ # to uniquely identify that firmware with its ID.
+ if len(list(path.glob('*'))) != 1:
+ raise ValueError(f'{path} must contain exactly one firmware image file.')
+
+ payload_blob = b''
+ for fw in path.iterdir():
+ payload_blob += fw.read_bytes()
+
+ payload_len = len(payload_blob)
+ if payload_len == 0:
+ raise ValueError(f'{fw} is a zero byte file!')
+
+ dirname = path.parts[-1]
+ # firmware id is the name of the directory the firmware bundle is in,
+ # terminated by NULL.
+ fwid = b'' + dirname.encode() + b'\0'
+ fwid_len = len(fwid)
+ magic = bytes.fromhex(FWHEADERMAGIC)
+
+ efifw_header_blob = b''
+ efifw_header_blob += struct.pack(' None:
('.splash', opts.splash, True),
('.pcrpkey', pcrpkey, True),
('.initrd', initrd, True),
+ *(('.efifw', parse_efifw_dir(fw), False) for fw in opts.efifw),
('.ucode', opts.microcode, True),
] # fmt: skip
@@ -1281,6 +1330,7 @@ def make_uki(opts: UkifyConfig) -> None:
'.osrel',
'.cmdline',
'.initrd',
+ '.efifw',
'.ucode',
'.splash',
'.dtb',
@@ -1756,6 +1806,16 @@ CONFIG_ITEMS = [
config_key='UKI/Initrd',
config_push=ConfigItem.config_list_prepend,
),
+ ConfigItem(
+ '--efifw',
+ metavar='DIR',
+ type=Path,
+ action='append',
+ default=[],
+ help='Directory with efi firmware binary file [.efifw section]',
+ config_key='UKI/Firmware',
+ config_push=ConfigItem.config_list_prepend,
+ ),
ConfigItem(
'--microcode',
metavar='UCODE',