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:
parent
b66a4c157e
commit
83bf58f39d
@ -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
84
src/boot/efifirmware.c
Normal 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
31
src/boot/efifirmware.h
Normal 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);
|
@ -284,6 +284,7 @@ libefi_sources = files(
|
||||
'console.c',
|
||||
'device-path-util.c',
|
||||
'devicetree.c',
|
||||
'efifirmware.c',
|
||||
'drivers.c',
|
||||
'efi-string.c',
|
||||
'efivars.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,
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -20,6 +20,7 @@ typedef enum UnifiedSection {
|
||||
UNIFIED_SECTION_PROFILE,
|
||||
UNIFIED_SECTION_DTBAUTO,
|
||||
UNIFIED_SECTION_HWIDS,
|
||||
UNIFIED_SECTION_EFIFW,
|
||||
_UNIFIED_SECTION_MAX,
|
||||
} UnifiedSection;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user