1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-21 02:50:18 +03:00

uki: introduce support for a .efifw section

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.
This commit is contained in:
Ani Sinha 2024-11-08 12:01:51 +05:30 committed by Lennart Poettering
parent b66a4c157e
commit 83bf58f39d
9 changed files with 233 additions and 3 deletions

View File

@ -91,6 +91,15 @@
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

84
src/boot/efifirmware.c Normal file
View File

@ -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;
}

31
src/boot/efifirmware.h Normal file
View File

@ -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);

View 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',

View 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,

View 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,
};

View File

@ -20,6 +20,7 @@ typedef enum UnifiedSection {
UNIFIED_SECTION_PROFILE,
UNIFIED_SECTION_DTBAUTO,
UNIFIED_SECTION_HWIDS,
UNIFIED_SECTION_EFIFW,
_UNIFIED_SECTION_MAX,
} UnifiedSection;

View 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,

View File

@ -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',