stub: add support for dtb addons

Same as kernel command line addons.
This commit is contained in:
Luca Boccassi 2023-08-07 01:05:18 +01:00
parent 3b66a6764e
commit 68f85761e2
5 changed files with 236 additions and 69 deletions

View file

@ -90,6 +90,16 @@ UTF-16.
**Measured hash** covers the literal kernel command line in UTF-16 (without any
trailing NUL bytes).
### PCR 12, `EV_EVENT_TAG`, "Devicetrees"
Devicetree addons are measured individually as a tagged event.
**Event Tag** `0x6c46f751`
**Description** the addon filename.
**Measured hash** covers the content of the Devicetree.
### PCR 12, `EV_IPL`, "Per-UKI Credentials initrd"
**Description** in the event log record is the constant string "Credentials

View file

@ -167,19 +167,20 @@
<citerefentry><refentrytitle>ukify</refentrytitle><manvolnum>1</manvolnum></citerefentry> tool will
add a SBAT policy by default if none is passed when building addons. For more information on SBAT see
<ulink url="https://github.com/rhboot/shim/blob/main/SBAT.md">Shim's documentation</ulink>.
Addons are supposed to be used to pass additional kernel command line parameters, regardless of the
kernel image being booted, for example to allow platform vendors to ship platform-specific
configuration. The loaded command line addon files are sorted, loaded, measured into TPM PCR 12 (if a
TPM is present) and appended to the kernel command line. UKI command line options are listed first,
then options from addons in <filename>/loader/addons/*.addon.efi</filename> are appended next, and
finally UKI-specific addons are appended last. Addons are always loaded in the same order based on the
filename, so that, given the same set of addons, the same set of measurements can be expected in
PCR12, however note that the filename is not protected by the PE signature, and as such an attacker
with write access to the ESP could potentially rename these files to change the order in which they
are loaded, in a way that could alter the functionality of the kernel, as some options might be order
dependent. If you sign such addons, you should pay attention to the PCR12 values and make use of an
attestation service so that improper use of your signed addons can be detected and dealt with using
one of the aforementioned revocation mechanisms.</para></listitem>
Addons are supposed to be used to pass additional kernel command line parameters or Devicetree blobs,
regardless of the kernel image being booted, for example to allow platform vendors to ship
platform-specific configuration. The loaded command line addon files are sorted, loaded, and measured
into TPM PCR 12 (if a TPM is present) and appended to the kernel command line. UKI command line options
are listed first, then options from addons in <filename>/loader/addons/*.addon.efi</filename>, and
finally UKI-specific addons. Device tree blobs are loaded and measured following the same algorithm.
Addons are always loaded in the same order based on the filename, so that, given the same set of
addons, the same set of measurements can be expected in PCR12. However, note that the filename is not
protected by the PE signature, and as such an attacker with write access to the ESP could potentially
rename these files to change the order in which they are loaded, in a way that could alter the
functionality of the kernel, as some options might be order dependent. If you sign such addons, you
should pay attention to the PCR12 values and make use of an attestation service so that improper use
of your signed addons can be detected and dealt with using one of the aforementioned revocation
mechanisms.</para></listitem>
<listitem><para>Files <filename>/loader/credentials/*.cred</filename> are packed up in a
<command>cpio</command> archive and placed in the <filename>/.extra/global_credentials/</filename>
@ -188,9 +189,9 @@
measured into TPM PCR 12 (if a TPM is present).</para></listitem>
<listitem><para>Additionally, files <filename>/loader/addons/*.addon.efi</filename> are loaded and
verified as PE binaries, and a <literal>.cmdline</literal> section is parsed from them. This is
supposed to be used to pass additional command line parameters to the kernel, regardless of the kernel
being booted.</para></listitem>
verified as PE binaries, and <literal>.cmdline</literal> and/or <literal>.dtb</literal> sections are
parsed from them. This is supposed to be used to pass additional command line parameters or Devicetree
blobs to the kernel, regardless of the kernel being booted.</para></listitem>
</itemizedlist>
<para>These mechanisms may be used to parameterize and extend trusted (i.e. signed), immutable initrd

View file

@ -384,6 +384,7 @@ int verb_status(int argc, char *argv[], void *userdata) {
{ EFI_STUB_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" },
{ EFI_STUB_FEATURE_CMDLINE_ADDONS, "Pick up .cmdline from addons" },
{ EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" },
{ EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" },
};
_cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL;
sd_id128_t loader_part_uuid = SD_ID128_NULL;

View file

@ -26,6 +26,8 @@ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION
DECLARE_SBAT(SBAT_STUB_SECTION_TEXT);
#define ADDON_FILENAME_EVENT_TAG_ID UINT32_C(0x6c46f751)
static EFI_STATUS combine_initrd(
EFI_PHYSICAL_ADDRESS initrd_base, size_t initrd_size,
const void * const extra_initrds[], const size_t extra_initrd_sizes[], size_t n_extra_initrds,
@ -95,6 +97,7 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
EFI_STUB_FEATURE_RANDOM_SEED | /* We pass a random seed to the kernel */
EFI_STUB_FEATURE_CMDLINE_ADDONS | /* We pick up .cmdline addons */
EFI_STUB_FEATURE_CMDLINE_SMBIOS | /* We support extending kernel cmdline from SMBIOS Type #11 */
EFI_STUB_FEATURE_DEVICETREE_ADDONS | /* We pick up .dtb addons */
0;
assert(loaded_image);
@ -253,29 +256,123 @@ static EFI_STATUS load_addons_from_dir(
return EFI_SUCCESS;
}
static EFI_STATUS cmdline_append_and_measure_addons(
static void cmdline_append_and_measure_addons(
char16_t *cmdline,
char16_t **cmdline_append,
bool *ret_parameters_measured) {
_cleanup_free_ char16_t *tmp = NULL;
bool m = false;
assert(cmdline_append);
assert(ret_parameters_measured);
mangle_stub_cmdline(cmdline);
if (isempty(cmdline))
return;
(void) tpm_log_load_options(cmdline, &m);
*ret_parameters_measured = m;
tmp = TAKE_PTR(*cmdline_append);
*cmdline_append = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", cmdline);
}
static void dtb_install_addons(
struct devicetree_state *dt_state,
void **dt_bases,
size_t *dt_sizes,
char16_t **dt_filenames,
size_t n_dts,
bool *ret_parameters_measured) {
int parameters_measured = -1;
EFI_STATUS err;
assert(dt_state);
assert(n_dts == 0 || (dt_bases && dt_sizes && dt_filenames));
assert(ret_parameters_measured);
for (size_t i = 0; i < n_dts; ++i) {
err = devicetree_install_from_memory(dt_state, dt_bases[i], dt_sizes[i]);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading addon devicetree, ignoring: %m");
else {
bool m = false;
err = tpm_log_tagged_event(
TPM2_PCR_KERNEL_CONFIG,
POINTER_TO_PHYSICAL_ADDRESS(dt_bases[i]),
dt_sizes[i],
ADDON_FILENAME_EVENT_TAG_ID,
dt_filenames[i],
&m);
if (err != EFI_SUCCESS)
return (void) log_error_status(
err,
"Unable to add measurement of DTB addon #%zu to PCR %i: %m",
i,
TPM2_PCR_KERNEL_CONFIG);
parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
}
}
*ret_parameters_measured = parameters_measured;
}
static void dt_bases_free(void **dt_bases, size_t n_dt) {
assert(dt_bases || n_dt == 0);
for (size_t i = 0; i < n_dt; ++i)
free(dt_bases[i]);
free(dt_bases);
}
static void dt_filenames_free(char16_t **dt_filenames, size_t n_dt) {
assert(dt_filenames || n_dt == 0);
for (size_t i = 0; i < n_dt; ++i)
free(dt_filenames[i]);
free(dt_filenames);
}
static EFI_STATUS load_addons(
EFI_HANDLE stub_image,
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
const char16_t *prefix,
const char *uname,
bool *ret_parameters_measured,
char16_t **cmdline_append) {
char16_t **ret_cmdline,
void ***ret_dt_bases,
size_t **ret_dt_sizes,
char16_t ***ret_dt_filenames,
size_t *ret_n_dt) {
_cleanup_free_ size_t *dt_sizes = NULL;
_cleanup_(strv_freep) char16_t **items = NULL;
_cleanup_(file_closep) EFI_FILE *root = NULL;
_cleanup_free_ char16_t *buffer = NULL;
size_t n_items = 0, n_allocated = 0;
_cleanup_free_ char16_t *cmdline = NULL;
size_t n_items = 0, n_allocated = 0, n_dt = 0;
char16_t **dt_filenames = NULL;
void **dt_bases = NULL;
EFI_STATUS err;
assert(stub_image);
assert(loaded_image);
assert(prefix);
assert(ret_parameters_measured);
assert(cmdline_append);
assert(!!ret_dt_bases == !!ret_dt_sizes);
assert(!!ret_dt_bases == !!ret_n_dt);
assert(!!ret_dt_filenames == !!ret_n_dt);
if (!loaded_image->DeviceHandle)
return EFI_SUCCESS;
CLEANUP_ARRAY(dt_bases, n_dt, dt_bases_free);
CLEANUP_ARRAY(dt_filenames, n_dt, dt_filenames_free);
err = open_volume(loaded_image->DeviceHandle, &root);
if (err == EFI_UNSUPPORTED)
/* Error will be unsupported if the bootloader doesn't implement the file system protocol on
@ -325,11 +422,12 @@ static EFI_STATUS cmdline_append_and_measure_addons(
return log_error_status(err, "Failed to find protocol in %ls: %m", items[i]);
err = pe_memory_locate_sections(loaded_addon->ImageBase, unified_sections, addrs, szs);
if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_CMDLINE] == 0) {
if (err != EFI_SUCCESS ||
(szs[UNIFIED_SECTION_CMDLINE] == 0 && szs[UNIFIED_SECTION_DTB] == 0)) {
if (err == EFI_SUCCESS)
err = EFI_NOT_FOUND;
log_error_status(err,
"Unable to locate embedded .cmdline section in %ls, ignoring: %m",
"Unable to locate embedded .cmdline/.dtb sections in %ls, ignoring: %m",
items[i]);
continue;
}
@ -350,22 +448,42 @@ static EFI_STATUS cmdline_append_and_measure_addons(
continue;
}
_cleanup_free_ char16_t *tmp = TAKE_PTR(buffer),
*extra16 = xstrn8_to_16((char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_CMDLINE],
szs[UNIFIED_SECTION_CMDLINE]);
buffer = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", extra16);
if (ret_cmdline && szs[UNIFIED_SECTION_CMDLINE] > 0) {
_cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline),
*extra16 = xstrn8_to_16((char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_CMDLINE],
szs[UNIFIED_SECTION_CMDLINE]);
cmdline = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", extra16);
}
if (ret_dt_bases && szs[UNIFIED_SECTION_DTB] > 0) {
dt_sizes = xrealloc(dt_sizes,
n_dt * sizeof(size_t),
(n_dt + 1) * sizeof(size_t));
dt_sizes[n_dt] = szs[UNIFIED_SECTION_DTB];
dt_bases = xrealloc(dt_bases,
n_dt * sizeof(void *),
(n_dt + 1) * sizeof(void *));
dt_bases[n_dt] = xmemdup((uint8_t*)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_DTB],
dt_sizes[n_dt]);
dt_filenames = xrealloc(dt_filenames,
n_dt * sizeof(char16_t *),
(n_dt + 1) * sizeof(char16_t *));
dt_filenames[n_dt] = xstrdup16(items[i]);
++n_dt;
}
}
mangle_stub_cmdline(buffer);
if (ret_cmdline && !isempty(cmdline))
*ret_cmdline = TAKE_PTR(cmdline);
if (!isempty(buffer)) {
_cleanup_free_ char16_t *tmp = TAKE_PTR(*cmdline_append);
bool m = false;
(void) tpm_log_load_options(buffer, &m);
*ret_parameters_measured = m;
*cmdline_append = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", buffer);
if (ret_n_dt && n_dt > 0) {
*ret_dt_filenames = TAKE_PTR(dt_filenames);
*ret_dt_bases = TAKE_PTR(dt_bases);
*ret_dt_sizes = TAKE_PTR(dt_sizes);
*ret_n_dt = n_dt;
}
return EFI_SUCCESS;
@ -374,12 +492,15 @@ static EFI_STATUS cmdline_append_and_measure_addons(
static EFI_STATUS run(EFI_HANDLE image) {
_cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL;
size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0;
size_t linux_size, initrd_size, dt_size;
void **dt_bases_addons_global = NULL, **dt_bases_addons_uki = NULL;
char16_t **dt_filenames_addons_global = NULL, **dt_filenames_addons_uki = NULL;
_cleanup_free_ size_t *dt_sizes_addons_global = NULL, *dt_sizes_addons_uki = NULL;
size_t linux_size, initrd_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0;
EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base;
_cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
_cleanup_free_ char16_t *cmdline = NULL;
_cleanup_free_ char16_t *cmdline = NULL, *cmdline_addons_global = NULL, *cmdline_addons_uki = NULL;
int sections_measured = -1, parameters_measured = -1;
_cleanup_free_ char *uname = NULL;
bool sysext_measured = false, m;
@ -406,6 +527,40 @@ static EFI_STATUS run(EFI_HANDLE image) {
return log_error_status(err, "Unable to locate embedded .linux section: %m");
}
CLEANUP_ARRAY(dt_bases_addons_global, n_dts_addons_global, dt_bases_free);
CLEANUP_ARRAY(dt_bases_addons_uki, n_dts_addons_uki, dt_bases_free);
CLEANUP_ARRAY(dt_filenames_addons_global, n_dts_addons_global, dt_filenames_free);
CLEANUP_ARRAY(dt_filenames_addons_uki, n_dts_addons_uki, dt_filenames_free);
/* Now that we have the UKI sections loaded, also load global first and then local (per-UKI)
* addons. The data is loaded at once, and then used later. */
err = load_addons(
image,
loaded_image,
u"\\loader\\addons",
uname,
&cmdline_addons_global,
&dt_bases_addons_global,
&dt_sizes_addons_global,
&dt_filenames_addons_global,
&n_dts_addons_global);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading global addons, ignoring: %m");
_cleanup_free_ char16_t *dropin_dir = get_extra_dir(loaded_image->FilePath);
err = load_addons(
image,
loaded_image,
dropin_dir,
uname,
&cmdline_addons_uki,
&dt_bases_addons_uki,
&dt_sizes_addons_uki,
&dt_filenames_addons_uki,
&n_dts_addons_uki);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading UKI-specific addons, ignoring: %m");
/* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written
* into so far), so that we have one PCR that we can nicely write policies against because it
* contains all static data of this image, and thus can be easily be pre-calculated. */
@ -471,27 +626,10 @@ static EFI_STATUS run(EFI_HANDLE image) {
* measure the additions separately, after the embedded options, but before the smbios ones,
* so that the order is reversed from "most hardcoded" to "most dynamic". The global addons are
* loaded first, and the image-specific ones later, for the same reason. */
err = cmdline_append_and_measure_addons(
image,
loaded_image,
u"\\loader\\addons",
uname,
&m,
&cmdline);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading global addons, ignoring: %m");
cmdline_append_and_measure_addons(cmdline_addons_global, &cmdline, &m);
parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
_cleanup_free_ char16_t *dropin_dir = get_extra_dir(loaded_image->FilePath);
err = cmdline_append_and_measure_addons(
image,
loaded_image,
dropin_dir,
uname,
&m,
&cmdline);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading UKI-specific addons, ignoring: %m");
cmdline_append_and_measure_addons(cmdline_addons_uki, &cmdline, &m);
parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
/* SMBIOS OEM Strings data is controlled by the host admin and not covered
@ -552,6 +690,32 @@ static EFI_STATUS run(EFI_HANDLE image) {
&m) == EFI_SUCCESS)
sysext_measured = m;
dt_size = szs[UNIFIED_SECTION_DTB];
dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0;
/* First load the base device tree, then fix it up using addons - global first, then per-UKI. */
if (dt_size > 0) {
err = devicetree_install_from_memory(
&dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading embedded devicetree: %m");
}
dtb_install_addons(&dt_state,
dt_bases_addons_global,
dt_sizes_addons_global,
dt_filenames_addons_global,
n_dts_addons_global,
&m);
parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
dtb_install_addons(&dt_state,
dt_bases_addons_uki,
dt_sizes_addons_uki,
dt_filenames_addons_uki,
n_dts_addons_uki,
&m);
parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
if (parameters_measured > 0)
(void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM2_PCR_KERNEL_CONFIG, 0);
if (sysext_measured)
@ -600,9 +764,6 @@ static EFI_STATUS run(EFI_HANDLE image) {
initrd_size = szs[UNIFIED_SECTION_INITRD];
initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0;
dt_size = szs[UNIFIED_SECTION_DTB];
dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0;
_cleanup_pages_ Pages initrd_pages = {};
if (credential_initrd || global_credential_initrd || sysext_initrd || pcrsig_initrd || pcrpkey_initrd) {
/* If we have generated initrds dynamically, let's combine them with the built-in initrd. */
@ -637,13 +798,6 @@ static EFI_STATUS run(EFI_HANDLE image) {
pcrpkey_initrd = mfree(pcrpkey_initrd);
}
if (dt_size > 0) {
err = devicetree_install_from_memory(
&dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading embedded devicetree: %m");
}
err = linux_exec(image, cmdline,
PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size,
PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);

View file

@ -31,6 +31,7 @@
#define EFI_STUB_FEATURE_RANDOM_SEED (UINT64_C(1) << 4)
#define EFI_STUB_FEATURE_CMDLINE_ADDONS (UINT64_C(1) << 5)
#define EFI_STUB_FEATURE_CMDLINE_SMBIOS (UINT64_C(1) << 6)
#define EFI_STUB_FEATURE_DEVICETREE_ADDONS (UINT64_C(1) << 7)
typedef enum SecureBootMode {
SECURE_BOOT_UNSUPPORTED,