diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 764fd32ddd5..09de149d42d 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -91,6 +91,15 @@ the same matching procedure. If a match is found, that .dtbauto section will be loaded and will override .dtb if present. + Zero or more .efifw sections for the firmware image. It works + in many ways similar to .dtbauto sections. systemd-stub + will always use the first matching one. The match is performed by first selecting the most appropriate + entry in the .hwids section based on the hardware IDs supplied by SMBIOS (see below). + If a suitable entry is found, the fwid string from that entry will be used to + perform the matching procedure for firmware blobs in .efifw section. The first + matching firmware will be loaded. + + Zero or more .hwids sections with hardware IDs of the machines to match DeviceTrees. systemd-stub will use the SMBIOS data to calculate hardware IDs of the machine (as per + +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',