]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
uki: introduce support for a .efifw section
authorAni Sinha <anisinha@redhat.com>
Fri, 8 Nov 2024 06:31:51 +0000 (12:01 +0530)
committerLennart Poettering <lennart@poettering.net>
Fri, 31 Jan 2025 09:05:00 +0000 (10:05 +0100)
UKIs can be used to bundle uefi firmwares that can be measured and
used on a confidential computing environment. There can be more than one
firmware blob bundle, each one for a specific platform. Also firmware images
can themselves be containers like IGVM files that can in turn bundle the
actual firmware blob. This change is specifically for uefi firmwares, not
IGVM container files.

This change adds support to introduce a .efifw section in UKI that can be
used for firmware blobs/images. There can be multiple such sections and each
section can contain a single firmware image.

The matching .hwids entry for a specific platform can be used to select the
most appropriate firmware blob.

ukify tool has been also changed to support addition of a firmware image
in UKI.

Since firmware gets measured automatically, we do not need to measure it
separately as a part of the UKI.

man/systemd-stub.xml
src/boot/efifirmware.c [new file with mode: 0644]
src/boot/efifirmware.h [new file with mode: 0644]
src/boot/meson.build
src/boot/pe.c
src/fundamental/uki.c
src/fundamental/uki.h
src/measure/measure.c
src/ukify/ukify.py

index 764fd32ddd5edec2d46741f7e97c2314489616b5..09de149d42d76f627153b30eee8a60fc54606478 100644 (file)
       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
diff --git a/src/boot/efifirmware.c b/src/boot/efifirmware.c
new file mode 100644 (file)
index 0000000..8cf8e62
--- /dev/null
@@ -0,0 +1,84 @@
+/* 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;
+}
diff --git a/src/boot/efifirmware.h b/src/boot/efifirmware.h
new file mode 100644 (file)
index 0000000..888de35
--- /dev/null
@@ -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);
index f7269962334222487c2824d8004171fa99771912..31f36ea46d45b8762da5826dccb47fd4f315422f 100644 (file)
@@ -284,6 +284,7 @@ libefi_sources = files(
         'console.c',
         'device-path-util.c',
         'devicetree.c',
+        'efifirmware.c',
         'drivers.c',
         'efi-string.c',
         'efivars.c',
index 97ea8d7220a643ada29e474cda910760ed8828c2..348296f001685b3efe3ac39ef48c18b36f70fa16 100644 (file)
@@ -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,
index 441d466a97ef077d4c6caaac9278724c8d89cac3..06361cc527108f0fb893ae84e55859d92d9691e0 100644 (file)
@@ -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,
 };
index 4b6195f9b70ca273a959cbea5d81960edb64d04a..6752b2ae77cc7f979a76391643c1264a0f73b29c 100644 (file)
@@ -20,6 +20,7 @@ typedef enum UnifiedSection {
         UNIFIED_SECTION_PROFILE,
         UNIFIED_SECTION_DTBAUTO,
         UNIFIED_SECTION_HWIDS,
+        UNIFIED_SECTION_EFIFW,
         _UNIFIED_SECTION_MAX,
 } UnifiedSection;
 
index dcae8528e073efa54f47357ab327a596eff70937..c8e4d59688eb5696b1f8d2eeb47566c74f758c27 100644 (file)
@@ -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,
index 5f821297c128516d2764d7d286a90a037668a1d8..3bf0f458573c6359a4774a7061bcc0c0e0924860 100755 (executable)
@@ -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('<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/
@@ -1222,6 +1270,7 @@ def make_uki(opts: UkifyConfig) -> 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',