<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>
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
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,
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);
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
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;
}
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];
- mangle_stub_cmdline(buffer);
+ 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]);
- if (!isempty(buffer)) {
- _cleanup_free_ char16_t *tmp = TAKE_PTR(*cmdline_append);
- bool m = false;
+ 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;
+ }
+ }
- (void) tpm_log_load_options(buffer, &m);
- *ret_parameters_measured = m;
+ if (ret_cmdline && !isempty(cmdline))
+ *ret_cmdline = TAKE_PTR(cmdline);
- *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;
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;
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. */
* 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
&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)
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. */
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);