<para><filename>/usr/lib/systemd/boot/efi/linuxx64.efi.stub</filename></para>
<para><filename>/usr/lib/systemd/boot/efi/linuxia32.efi.stub</filename></para>
<para><filename>/usr/lib/systemd/boot/efi/linuxaa64.efi.stub</filename></para>
+ <para><filename><replaceable>ESP</replaceable>/.../<replaceable>foo</replaceable>.efi.extra.d/*.addon.efi</filename></para>
<para><filename><replaceable>ESP</replaceable>/.../<replaceable>foo</replaceable>.efi.extra.d/*.cred</filename></para>
<para><filename><replaceable>ESP</replaceable>/.../<replaceable>foo</replaceable>.efi.extra.d/*.raw</filename></para>
+ <para><filename><replaceable>ESP</replaceable>/loader/addons/*.addon.efi</filename></para>
<para><filename><replaceable>ESP</replaceable>/loader/credentials/*.cred</filename></para>
</refsynopsisdiv>
details on system extension images. The generated <command>cpio</command> archive containing these
system extension images is measured into TPM PCR 13 (if a TPM is present).</para></listitem>
+ <listitem><para>Similarly, files
+ <filename><replaceable>foo</replaceable>.efi.extra.d/*.addon.efi</filename>
+ are loaded and verified as PE binaries, and a <literal>.cmdline</literal> section is parsed from them.
+ In case Secure Boot is enabled, these files will be validated using keys in UEFI DB, Shim's DB or
+ Shim's MOK, and will be rejected otherwise. Additionally, if the both the addon and the UKI contain a
+ a <literal>.uname</literal> section, the addon will be rejected if they do not exactly match. It is
+ recommended to always add a <literal>.sbat</literal> section to all signed addons, so that they may be
+ revoked with a SBAT policy update, without requiring blocklisting via DBX/MOKX. The
+ <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>
+
<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>
directory of the initrd file hierarchy. This is supposed to be used to pass additional credentials to
the initrd, regardless of the kernel being booted. The generated <command>cpio</command> archive is
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>
</itemizedlist>
<para>These mechanisms may be used to parameterize and extend trusted (i.e. signed), immutable initrd
#include "proto/shell-parameters.h"
#include "random-seed.h"
#include "secure-boot.h"
+#include "shim.h"
#include "splash.h"
#include "tpm-pcr.h"
#include "util.h"
return true;
}
+static EFI_STATUS load_addons_from_dir(
+ EFI_FILE *root,
+ const char16_t *prefix,
+ char16_t ***items,
+ size_t *n_items,
+ size_t *n_allocated) {
+
+ _cleanup_(file_closep) EFI_FILE *extra_dir = NULL;
+ _cleanup_free_ EFI_FILE_INFO *dirent = NULL;
+ size_t dirent_size = 0;
+ EFI_STATUS err;
+
+ assert(root);
+ assert(prefix);
+ assert(items);
+ assert(n_items);
+ assert(n_allocated);
+
+ err = open_directory(root, prefix, &extra_dir);
+ if (err == EFI_NOT_FOUND)
+ /* No extra subdir, that's totally OK */
+ return EFI_SUCCESS;
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to open addons directory '%ls': %m", prefix);
+
+ for (;;) {
+ _cleanup_free_ char16_t *d = NULL;
+
+ err = readdir(extra_dir, &dirent, &dirent_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to read addons directory of loaded image: %m");
+ if (!dirent) /* End of directory */
+ break;
+
+ if (dirent->FileName[0] == '.')
+ continue;
+ if (FLAGS_SET(dirent->Attribute, EFI_FILE_DIRECTORY))
+ continue;
+ if (!is_ascii(dirent->FileName))
+ continue;
+ if (strlen16(dirent->FileName) > 255) /* Max filename size on Linux */
+ continue;
+ if (!endswith_no_case(dirent->FileName, u".addon.efi"))
+ continue;
+
+ d = xstrdup16(dirent->FileName);
+
+ if (*n_items + 2 > *n_allocated) {
+ /* We allocate 16 entries at a time, as a matter of optimization */
+ if (*n_items > (SIZE_MAX / sizeof(uint16_t)) - 16) /* Overflow check, just in case */
+ return log_oom();
+
+ size_t m = *n_items + 16;
+ *items = xrealloc(*items, *n_allocated * sizeof(uint16_t *), m * sizeof(uint16_t *));
+ *n_allocated = m;
+ }
+
+ (*items)[(*n_items)++] = TAKE_PTR(d);
+ (*items)[*n_items] = NULL; /* Let's always NUL terminate, to make freeing via strv_free() easy */
+ }
+
+ return EFI_SUCCESS;
+
+}
+
+static EFI_STATUS cmdline_append_and_measure_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) {
+
+ _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;
+ EFI_STATUS err;
+
+ assert(stub_image);
+ assert(loaded_image);
+ assert(prefix);
+ assert(ret_parameters_measured);
+ assert(cmdline_append);
+
+ if (!loaded_image->DeviceHandle)
+ return EFI_SUCCESS;
+
+ 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
+ * its file handles. */
+ return EFI_SUCCESS;
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Unable to open root directory: %m");
+
+ err = load_addons_from_dir(root, prefix, &items, &n_items, &n_allocated);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (n_items == 0)
+ return EFI_SUCCESS; /* Empty directory */
+
+ /* Now, sort the files we found, to make this uniform and stable (and to ensure the TPM measurements
+ * are not dependent on read order) */
+ sort_pointer_array((void**) items, n_items, (compare_pointer_func_t) strcmp16);
+
+ for (size_t i = 0; i < n_items; i++) {
+ size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
+ _cleanup_free_ EFI_DEVICE_PATH *addon_path = NULL;
+ _cleanup_(unload_imagep) EFI_HANDLE addon = NULL;
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_addon = NULL;
+ _cleanup_free_ char16_t *addon_spath = NULL;
+
+ addon_spath = xasprintf("%ls\\%ls", prefix, items[i]);
+ err = make_file_device_path(loaded_image->DeviceHandle, addon_spath, &addon_path);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error making device path for %ls: %m", addon_spath);
+
+ /* By using shim_load_image, we cover both the case where the PE files are signed with MoK
+ * and with DB, and running with or without shim. */
+ err = shim_load_image(stub_image, addon_path, &addon);
+ if (err != EFI_SUCCESS) {
+ log_error_status(err,
+ "Failed to read '%ls' from '%ls', ignoring: %m",
+ items[i],
+ addon_spath);
+ continue;
+ }
+
+ err = BS->HandleProtocol(addon,
+ MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL),
+ (void **) &loaded_addon);
+ if (err != EFI_SUCCESS)
+ 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)
+ err = EFI_NOT_FOUND;
+ log_error_status(err,
+ "Unable to locate embedded .cmdline section in %ls, ignoring: %m",
+ items[i]);
+ continue;
+ }
+
+ /* We want to enforce that addons are not UKIs, i.e.: they must not embed a kernel. */
+ if (szs[UNIFIED_SECTION_LINUX] > 0) {
+ log_error_status(EFI_INVALID_PARAMETER, "%ls is a UKI, not an addon, ignoring: %m", items[i]);
+ continue;
+ }
+
+ /* Also enforce that, in case it is specified, .uname matches as a quick way to allow
+ * enforcing compatibility with a specific UKI only */
+ if (uname && szs[UNIFIED_SECTION_UNAME] > 0 &&
+ !strneq8(uname,
+ (char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_UNAME],
+ szs[UNIFIED_SECTION_UNAME])) {
+ log_error(".uname mismatch between %ls and UKI, ignoring", items[i]);
+ 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);
+ }
+
+ mangle_stub_cmdline(buffer);
+
+ 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);
+ }
+
+ 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 addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
_cleanup_free_ char16_t *cmdline = NULL;
int sections_measured = -1, parameters_measured = -1;
+ _cleanup_free_ char *uname = NULL;
bool sysext_measured = false, m;
uint64_t loader_features = 0;
EFI_STATUS err;
/* Show splash screen as early as possible */
graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]);
+ if (szs[UNIFIED_SECTION_UNAME] > 0)
+ uname = xstrndup8((char *)loaded_image->ImageBase + addrs[UNIFIED_SECTION_UNAME],
+ szs[UNIFIED_SECTION_UNAME]);
+
if (use_load_options(image, loaded_image, szs[UNIFIED_SECTION_CMDLINE] > 0, &cmdline)) {
/* Let's measure the passed kernel command line into the TPM. Note that this possibly
* duplicates what we already did in the boot menu, if that was already used. However, since
mangle_stub_cmdline(cmdline);
}
+ /* If we have any extra command line to add via PE addons, load them now and append, and
+ * 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");
+ 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");
+ parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
+
const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra");
if (extra) {
_cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline), *extra16 = xstr8_to_16(extra);