the same matching procedure. If a match is found, that <literal>.dtbauto</literal> section will be
loaded and will override <varname>.dtb</varname> if present.</para></listitem>
+ <listitem><para>Zero or more <literal>.efifw</literal> sections for the firmware image. It works
+ in many ways similar to <literal>.dtbauto</literal> sections. <filename>systemd-stub</filename>
+ will always use the first matching one. The match is performed by first selecting the most appropriate
+ entry in the <varname>.hwids</varname> section based on the hardware IDs supplied by SMBIOS (see below).
+ If a suitable entry is found, the <varname>fwid</varname> string from that entry will be used to
+ perform the matching procedure for firmware blobs in <varname>.efifw</varname> section. The first
+ matching firmware will be loaded.
+ </para></listitem>
+
<listitem><para>Zero or more <literal>.hwids</literal> sections with hardware IDs of the machines to
match DeviceTrees. <filename>systemd-stub</filename> will use the SMBIOS data to calculate hardware IDs
of the machine (as per <ulink
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "efifirmware.h"
+#include "util.h"
+#include <endian.h>
+
+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;
+}
--- /dev/null
+/* 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);
'console.c',
'device-path-util.c',
'devicetree.c',
+ 'efifirmware.c',
'drivers.c',
'efi-string.c',
'efivars.c',
#include "chid.h"
#include "devicetree.h"
+#include "efifirmware.h"
#include "pe.h"
#include "util.h"
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,
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,
[UNIFIED_SECTION_PROFILE] = ".profile",
[UNIFIED_SECTION_DTBAUTO] = ".dtbauto",
[UNIFIED_SECTION_HWIDS] = ".hwids",
+ [UNIFIED_SECTION_EFIFW] = ".efifw",
NULL,
};
UNIFIED_SECTION_PROFILE,
UNIFIED_SECTION_DTBAUTO,
UNIFIED_SECTION_HWIDS,
+ UNIFIED_SECTION_EFIFW,
_UNIFIED_SECTION_MAX,
} UnifiedSection;
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,
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']]
'.dtb': 'binary',
'.dtbauto': 'binary',
'.hwids': 'binary',
+ '.efifw': 'binary',
'.cmdline': 'text',
'.osrel': 'text',
'.uname': 'text',
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]
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)
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)
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('<p', magic)
+ efifw_header_blob += struct.pack('<I', EFIFW_HEADER_SIZE)
+ efifw_header_blob += struct.pack('<I', fwid_len)
+ efifw_header_blob += struct.pack('<I', payload_len)
+
+ efifw_blob = b''
+ efifw_blob += efifw_header_blob + fwid + payload_blob
+
+ return efifw_blob
+
+
STUB_SBAT = """\
sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
uki,1,UKI,uki,1,https://uapi-group.org/specifications/specs/unified_kernel_image/
('.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
'.osrel',
'.cmdline',
'.initrd',
+ '.efifw',
'.ucode',
'.splash',
'.dtb',
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',