mirror of
https://github.com/systemd/systemd
synced 2024-10-07 08:40:44 +00:00
Merge pull request #27358 from bluca/pe_mule
stub: allow loading and verifying kernel command line addons
This commit is contained in:
commit
be2e63159f
6
TODO
6
TODO
|
@ -467,12 +467,6 @@ Features:
|
|||
parametrization, if needed. This matches our usual rule that admin config
|
||||
should win over vendor defaults.
|
||||
|
||||
* sd-stub: optionally allow users to configure manual kernel command line even
|
||||
in SecureBoot by authenticating it via shim's APIs, integrating with MOK and
|
||||
similar: instead of authenticating just PE code shim should be capable of
|
||||
authenticating any kind of data for us, including files containing kernel
|
||||
command lines.
|
||||
|
||||
* write a "search path" spec, that documents the prefixes to search in
|
||||
(i.e. the usual /etc/, /run/, /usr/lib/ dance, potentially /usr/etc/), how to
|
||||
sort found entries, how masking works and overriding.
|
||||
|
|
|
@ -28,8 +28,10 @@
|
|||
<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>
|
||||
|
||||
|
@ -57,12 +59,18 @@
|
|||
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> file of
|
||||
the OS the kernel belongs to, in the <literal>.osrel</literal> PE section.</para></listitem>
|
||||
|
||||
<listitem><para>Kernel version information, i.e. the output of <command>uname -r</command> for the
|
||||
kernel included in the UKI, in the <literal>.uname</literal> PE section.</para></listitem>
|
||||
|
||||
<listitem><para>The initrd will be loaded from the <literal>.initrd</literal> PE section.
|
||||
</para></listitem>
|
||||
|
||||
<listitem><para>A compiled binary DeviceTree will be looked for in the <literal>.dtb</literal> PE
|
||||
section.</para></listitem>
|
||||
|
||||
<listitem><para>Kernel version information, i.e. the output of <command>uname -r</command> for the
|
||||
kernel included in the UKI, in the <literal>.uname</literal> PE section.</para></listitem>
|
||||
|
||||
<listitem><para>The kernel command line to pass to the invoked kernel will be looked for in the
|
||||
<literal>.cmdline</literal> PE section.</para></listitem>
|
||||
|
||||
|
@ -142,11 +150,41 @@
|
|||
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
|
||||
|
@ -391,7 +429,7 @@
|
|||
<varlistentry>
|
||||
<term><varname>io.systemd.stub.kernel-cmdline-extra</varname></term>
|
||||
<listitem><para>If set, the value of this string is added to the list of kernel command line
|
||||
arguments that are passed to the kernel.</para></listitem>
|
||||
arguments that are measured in PCR12 and passed to the kernel.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
<varname>Splash=</varname>/<option>--splash=</option>,
|
||||
<varname>PCRPKey=</varname>/<option>--pcrpkey=</option>,
|
||||
<varname>Uname=</varname>/<option>--uname=</option>,
|
||||
<varname>SBAT=</varname>/<option>--sbat=</option>,
|
||||
and <option>--section=</option>
|
||||
below.</para>
|
||||
|
||||
|
@ -369,6 +370,27 @@
|
|||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>[Addon:<replaceable>NAME</replaceable>] section</title>
|
||||
|
||||
<para>Currently, these options only apply when building PE addons.</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>SBAT=<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></varname></term>
|
||||
<term><option>--sbat=<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></option></term>
|
||||
|
||||
<listitem><para>SBAT metadata associated with the addon. SBAT policies are useful to revoke whole
|
||||
groups of addons with a single, static policy update that does not take space in DBX/MOKX. If not
|
||||
specified manually, a default metadata entry consisting of
|
||||
<literal>uki.addon.systemd,1,UKI Addon,uki.addon.systemd,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html</literal>
|
||||
will be used, to ensure it is always possible to revoke addons. For more information on SBAT see
|
||||
<ulink url="https://github.com/rhboot/shim/blob/main/SBAT.md">Shim's documentation.</ulink></para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
@ -464,11 +486,13 @@ Phases=enter-initrd:leave-initrd
|
|||
--secureboot-private-key=sb.key \
|
||||
--secureboot-certificate=sb.cert \
|
||||
--cmdline='debug' \
|
||||
--sbat='sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
|
||||
uki.addon.author,1,UKI Addon for System,uki.addon.author,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html'
|
||||
--output=debug.cmdline
|
||||
</programlisting>
|
||||
|
||||
<para>This creates a signed PE binary that contains the additional kernel command line parameter
|
||||
<literal>debug</literal>.</para>
|
||||
<literal>debug</literal> with SBAT metadata referring to the owner of the addon.</para>
|
||||
</example>
|
||||
</refsect1>
|
||||
|
||||
|
|
|
@ -193,3 +193,15 @@ if [ "$WITH_TESTS" = 1 ]; then
|
|||
fi
|
||||
|
||||
( set -x; meson install -C "$BUILDDIR" --quiet --no-rebuild --only-changed )
|
||||
|
||||
# Ensure that side-loaded PE addons are loaded if signed, and ignored if not
|
||||
if [ -d "${DESTDIR}/boot/loader" ]; then
|
||||
addons_dir="${DESTDIR}/boot/loader/addons"
|
||||
elif [ -d "${DESTDIR}/efi/loader" ]; then
|
||||
addons_dir="${DESTDIR}/efi/loader/addons"
|
||||
fi
|
||||
if [ -n "${addons_dir}" ]; then
|
||||
mkdir -p "${addons_dir}"
|
||||
ukify --secureboot-private-key mkosi.secure-boot.key --secureboot-certificate mkosi.secure-boot.crt --cmdline this_should_be_here -o "${addons_dir}/good.addon.efi"
|
||||
ukify --cmdline this_should_not_be_here -o "${addons_dir}/bad.addon.efi"
|
||||
fi
|
||||
|
|
|
@ -7,6 +7,8 @@ systemctl --failed --no-legend | tee /failed-services
|
|||
if [[ -d /sys/firmware/efi/efivars/ ]]; then
|
||||
cmp /sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c <(printf '\6\0\0\0\1')
|
||||
cmp /sys/firmware/efi/efivars/SetupMode-8be4df61-93ca-11d2-aa0d-00e098032b8c <(printf '\6\0\0\0\0')
|
||||
grep -q this_should_be_here /proc/cmdline
|
||||
grep -q this_should_not_be_here /proc/cmdline && exit 1
|
||||
fi
|
||||
|
||||
# Exit with non-zero EC if the /failed-services file is not empty (we have -e set)
|
||||
|
|
15
src/boot/efi/addon.c
Normal file
15
src/boot/efi/addon.c
Normal file
|
@ -0,0 +1,15 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "efi.h"
|
||||
#include "version.h"
|
||||
|
||||
/* Magic string for recognizing our own binaries */
|
||||
_used_ _section_(".sdmagic") static const char magic[] =
|
||||
"#### LoaderInfo: systemd-addon " GIT_VERSION " ####";
|
||||
|
||||
/* This is intended to carry data, not to be executed */
|
||||
|
||||
EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table);
|
||||
EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) {
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
|
@ -2641,6 +2641,10 @@ static EFI_STATUS run(EFI_HANDLE image) {
|
|||
|
||||
init_usec = time_usec();
|
||||
|
||||
/* Ask Shim to leave its protocol around, so that the stub can use it to validate PEs.
|
||||
* By default, Shim uninstalls its protocol when calling StartImage(). */
|
||||
shim_retain_protocol();
|
||||
|
||||
err = BS->OpenProtocol(
|
||||
image,
|
||||
MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL),
|
||||
|
|
|
@ -301,29 +301,6 @@ static EFI_STATUS pack_cpio_trailer(
|
|||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static char16_t *get_dropin_dir(const EFI_DEVICE_PATH *file_path) {
|
||||
if (!file_path)
|
||||
return NULL;
|
||||
|
||||
/* A device path is allowed to have more than one file path node. If that is the case they are
|
||||
* supposed to be concatenated. Unfortunately, the device path to text protocol simply converts the
|
||||
* nodes individually and then combines those with the usual '/' for device path nodes. But this does
|
||||
* not create a legal EFI file path that the file protocol can use. */
|
||||
|
||||
/* Make sure we really only got file paths. */
|
||||
for (const EFI_DEVICE_PATH *node = file_path; !device_path_is_end(node);
|
||||
node = device_path_next_node(node))
|
||||
if (node->Type != MEDIA_DEVICE_PATH || node->SubType != MEDIA_FILEPATH_DP)
|
||||
return NULL;
|
||||
|
||||
_cleanup_free_ char16_t *file_path_str = NULL;
|
||||
if (device_path_to_str(file_path, &file_path_str) != EFI_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
convert_efi_path(file_path_str);
|
||||
return xasprintf("%ls.extra.d", file_path_str);
|
||||
}
|
||||
|
||||
EFI_STATUS pack_cpio(
|
||||
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
|
||||
const char16_t *dropin_dir,
|
||||
|
@ -363,7 +340,7 @@ EFI_STATUS pack_cpio(
|
|||
return log_error_status(err, "Unable to open root directory: %m");
|
||||
|
||||
if (!dropin_dir)
|
||||
dropin_dir = rel_dropin_dir = get_dropin_dir(loaded_image->FilePath);
|
||||
dropin_dir = rel_dropin_dir = get_extra_dir(loaded_image->FilePath);
|
||||
|
||||
err = open_directory(root, dropin_dir, &extra_dir);
|
||||
if (err == EFI_NOT_FOUND)
|
||||
|
|
|
@ -242,6 +242,7 @@ libefi_sources = files(
|
|||
'pe.c',
|
||||
'random-seed.c',
|
||||
'secure-boot.c',
|
||||
'shim.c',
|
||||
'ticks.c',
|
||||
'util.c',
|
||||
'vmm.c',
|
||||
|
@ -249,7 +250,6 @@ libefi_sources = files(
|
|||
|
||||
systemd_boot_sources = files(
|
||||
'boot.c',
|
||||
'shim.c',
|
||||
)
|
||||
|
||||
stub_sources = files(
|
||||
|
@ -259,6 +259,10 @@ stub_sources = files(
|
|||
'stub.c',
|
||||
)
|
||||
|
||||
addon_sources = files(
|
||||
'addon.c',
|
||||
)
|
||||
|
||||
if get_option('b_sanitize') == 'undefined'
|
||||
libefi_sources += files('ubsan.c')
|
||||
endif
|
||||
|
@ -328,12 +332,28 @@ foreach archspec : efi_archspecs
|
|||
override_options : efi_override_options,
|
||||
name_suffix : 'elf.stub',
|
||||
pie : true)
|
||||
|
||||
efi_elf_binaries += executable(
|
||||
'addon' + archspec['arch'],
|
||||
addon_sources,
|
||||
include_directories : efi_includes,
|
||||
c_args : archspec['c_args'],
|
||||
link_args : archspec['link_args'],
|
||||
link_depends : elf2efi_lds,
|
||||
dependencies : versiondep,
|
||||
gnu_symbol_visibility : 'hidden',
|
||||
override_options : efi_override_options,
|
||||
name_suffix : 'elf.stub',
|
||||
pie : true)
|
||||
endforeach
|
||||
|
||||
foreach efi_elf_binary : efi_elf_binaries
|
||||
# FIXME: Use build_tgt.name() with meson >= 0.54.0
|
||||
name = fs.name(efi_elf_binary.full_path()).split('.')[0]
|
||||
name += name.startswith('linux') ? '.efi.stub' : '.efi'
|
||||
name += name.startswith('systemd-boot') ? '.efi' : '.efi.stub'
|
||||
# For the addon, given it's empty, we need to explicitly reserve space in the header to account for
|
||||
# the sections that ukify will add.
|
||||
minimum_sections = name.startswith('addon') ? '7' : '0'
|
||||
exe = custom_target(
|
||||
name,
|
||||
output : name,
|
||||
|
@ -348,6 +368,7 @@ foreach efi_elf_binary : efi_elf_binaries
|
|||
'--efi-major=1',
|
||||
'--efi-minor=1',
|
||||
'--subsystem=10',
|
||||
'--minimum-sections=' + minimum_sections,
|
||||
'@INPUT@',
|
||||
'@OUTPUT@',
|
||||
])
|
||||
|
|
|
@ -97,3 +97,12 @@ EFI_STATUS shim_load_image(EFI_HANDLE parent, const EFI_DEVICE_PATH *device_path
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void shim_retain_protocol(void) {
|
||||
uint8_t value = 1;
|
||||
|
||||
/* Ask Shim to avoid uninstalling its security protocol, so that we can use it from sd-stub to
|
||||
* validate PE addons. By default, Shim uninstalls its protocol when calling StartImage().
|
||||
* Requires Shim 15.8. */
|
||||
(void) efivar_set_raw(MAKE_GUID_PTR(SHIM_LOCK), u"ShimRetainProtocol", &value, sizeof(value), 0);
|
||||
}
|
||||
|
|
|
@ -13,3 +13,4 @@
|
|||
|
||||
bool shim_loaded(void);
|
||||
EFI_STATUS shim_load_image(EFI_HANDLE parent, const EFI_DEVICE_PATH *device_path, EFI_HANDLE *ret_image);
|
||||
void shim_retain_protocol(void);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#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"
|
||||
|
@ -180,6 +181,189 @@ static bool use_load_options(
|
|||
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;
|
||||
|
@ -190,6 +374,7 @@ static EFI_STATUS run(EFI_HANDLE image) {
|
|||
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;
|
||||
|
@ -262,6 +447,10 @@ static EFI_STATUS run(EFI_HANDLE image) {
|
|||
/* 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
|
||||
|
@ -277,11 +466,44 @@ static EFI_STATUS run(EFI_HANDLE image) {
|
|||
mangle_stub_cmdline(cmdline);
|
||||
}
|
||||
|
||||
/* SMBIOS strings are measured in PCR1, so we do not re-measure these command line extensions. */
|
||||
/* 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);
|
||||
cmdline = xasprintf("%ls %ls", tmp, extra16);
|
||||
|
||||
/* SMBIOS strings are measured in PCR1, but we also want to measure them in our specific
|
||||
* PCR12, as firmware-owned PCRs are very difficult to use as they'll contain unpredictable
|
||||
* measurements that are not under control of the machine owner. */
|
||||
m = false;
|
||||
(void) tpm_log_load_options(extra16, &m);
|
||||
parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
|
||||
}
|
||||
|
||||
export_variables(loaded_image);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "device-path-util.h"
|
||||
#include "proto/device-path.h"
|
||||
#include "proto/simple-text-io.h"
|
||||
#include "ticks.h"
|
||||
|
@ -267,6 +268,9 @@ char16_t *xstr8_to_path(const char *str8) {
|
|||
void mangle_stub_cmdline(char16_t *cmdline) {
|
||||
char16_t *p = cmdline;
|
||||
|
||||
if (!cmdline)
|
||||
return;
|
||||
|
||||
for (; *cmdline != '\0'; cmdline++)
|
||||
/* Convert ASCII control characters to spaces. */
|
||||
if (*cmdline <= 0x1F)
|
||||
|
@ -665,3 +669,26 @@ void *find_configuration_table(const EFI_GUID *guid) {
|
|||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path) {
|
||||
if (!file_path)
|
||||
return NULL;
|
||||
|
||||
/* A device path is allowed to have more than one file path node. If that is the case they are
|
||||
* supposed to be concatenated. Unfortunately, the device path to text protocol simply converts the
|
||||
* nodes individually and then combines those with the usual '/' for device path nodes. But this does
|
||||
* not create a legal EFI file path that the file protocol can use. */
|
||||
|
||||
/* Make sure we really only got file paths. */
|
||||
for (const EFI_DEVICE_PATH *node = file_path; !device_path_is_end(node);
|
||||
node = device_path_next_node(node))
|
||||
if (node->Type != MEDIA_DEVICE_PATH || node->SubType != MEDIA_FILEPATH_DP)
|
||||
return NULL;
|
||||
|
||||
_cleanup_free_ char16_t *file_path_str = NULL;
|
||||
if (device_path_to_str(file_path, &file_path_str) != EFI_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
convert_efi_path(file_path_str);
|
||||
return xasprintf("%ls.extra.d", file_path_str);
|
||||
}
|
||||
|
|
|
@ -212,3 +212,5 @@ static inline bool efi_guid_equal(const EFI_GUID *a, const EFI_GUID *b) {
|
|||
}
|
||||
|
||||
void *find_configuration_table(const EFI_GUID *guid);
|
||||
|
||||
char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path);
|
||||
|
|
|
@ -83,6 +83,7 @@ static int help(int argc, char *argv[], void *userdata) {
|
|||
" --initrd=PATH Path to initrd image file %7$s .initrd\n"
|
||||
" --splash=PATH Path to splash bitmap file %7$s .splash\n"
|
||||
" --dtb=PATH Path to Devicetree file %7$s .dtb\n"
|
||||
" --uname=PATH Path to 'uname -r' file %7$s .uname\n"
|
||||
" --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
program_invocation_short_name,
|
||||
|
@ -122,6 +123,7 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
ARG_INITRD,
|
||||
ARG_SPLASH,
|
||||
ARG_DTB,
|
||||
ARG_UNAME,
|
||||
_ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */
|
||||
_ARG_SECTION_LAST,
|
||||
ARG_PCRPKEY = _ARG_SECTION_LAST,
|
||||
|
@ -144,6 +146,7 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
{ "initrd", required_argument, NULL, ARG_INITRD },
|
||||
{ "splash", required_argument, NULL, ARG_SPLASH },
|
||||
{ "dtb", required_argument, NULL, ARG_DTB },
|
||||
{ "uname", required_argument, NULL, ARG_UNAME },
|
||||
{ "pcrpkey", required_argument, NULL, ARG_PCRPKEY },
|
||||
{ "current", no_argument, NULL, 'c' },
|
||||
{ "bank", required_argument, NULL, ARG_BANK },
|
||||
|
|
|
@ -11,6 +11,7 @@ const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = {
|
|||
[UNIFIED_SECTION_INITRD] = ".initrd",
|
||||
[UNIFIED_SECTION_SPLASH] = ".splash",
|
||||
[UNIFIED_SECTION_DTB] = ".dtb",
|
||||
[UNIFIED_SECTION_UNAME] = ".uname",
|
||||
[UNIFIED_SECTION_PCRSIG] = ".pcrsig",
|
||||
[UNIFIED_SECTION_PCRPKEY] = ".pcrpkey",
|
||||
NULL,
|
||||
|
|
|
@ -29,6 +29,7 @@ typedef enum UnifiedSection {
|
|||
UNIFIED_SECTION_INITRD,
|
||||
UNIFIED_SECTION_SPLASH,
|
||||
UNIFIED_SECTION_DTB,
|
||||
UNIFIED_SECTION_UNAME,
|
||||
UNIFIED_SECTION_PCRSIG,
|
||||
UNIFIED_SECTION_PCRPKEY,
|
||||
_UNIFIED_SECTION_MAX,
|
||||
|
|
|
@ -658,10 +658,10 @@ def make_uki(opts):
|
|||
('.osrel', opts.os_release, True ),
|
||||
('.cmdline', opts.cmdline, True ),
|
||||
('.dtb', opts.devicetree, True ),
|
||||
('.uname', opts.uname, True ),
|
||||
('.splash', opts.splash, True ),
|
||||
('.pcrpkey', pcrpkey, True ),
|
||||
('.initrd', initrd, True ),
|
||||
('.uname', opts.uname, False),
|
||||
|
||||
# linux shall be last to leave breathing room for decompression.
|
||||
# We'll add it later.
|
||||
|
@ -679,10 +679,12 @@ def make_uki(opts):
|
|||
|
||||
call_systemd_measure(uki, linux, opts=opts)
|
||||
|
||||
# UKI creation
|
||||
# UKI or addon creation - addons don't use the stub so we add SBAT manually
|
||||
|
||||
if linux is not None:
|
||||
uki.add_section(Section.create('.linux', linux, measure=True))
|
||||
elif opts.sbat:
|
||||
uki.add_section(Section.create('.sbat', opts.sbat, measure=False))
|
||||
|
||||
if sign_args_present:
|
||||
unsigned = tempfile.NamedTemporaryFile(prefix='uki')
|
||||
|
@ -927,6 +929,16 @@ CONFIG_ITEMS = [
|
|||
config_key = 'UKI/Stub',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--sbat',
|
||||
metavar = 'TEXT|@PATH',
|
||||
help = 'SBAT policy [.sbat section] for addons',
|
||||
default = """sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
|
||||
uki.addon,1,UKI Addon,uki.addon,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html
|
||||
""",
|
||||
config_key = 'Addon/SBAT',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--section',
|
||||
dest = 'sections',
|
||||
|
@ -1141,7 +1153,10 @@ def finalize_options(opts):
|
|||
opts.efi_arch = guess_efi_arch()
|
||||
|
||||
if opts.stub is None:
|
||||
opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/linux{opts.efi_arch}.efi.stub')
|
||||
if opts.linux is not None:
|
||||
opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/linux{opts.efi_arch}.efi.stub')
|
||||
else:
|
||||
opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/addon{opts.efi_arch}.efi.stub')
|
||||
|
||||
if opts.signing_engine is None:
|
||||
if opts.sb_key:
|
||||
|
|
|
@ -6,6 +6,13 @@ SECTIONS {
|
|||
.text ALIGN(CONSTANT(MAXPAGESIZE)) : {
|
||||
*(.text .text.*)
|
||||
}
|
||||
|
||||
/* When linking a minimal addon stub, the linker can merge .text and .dynsym, creating a RWE
|
||||
* segment, and then rejects it. Ensure there's a gap so that we end up with two separate segments.
|
||||
* The alignments for the next sections are only applied if the section exists, so they are not
|
||||
* enough, and we need to have this unconditional one. */
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
|
||||
.rodata ALIGN(CONSTANT(MAXPAGESIZE)) : {
|
||||
*(.rodata .rodata.*)
|
||||
*(.srodata .srodata.*)
|
||||
|
|
|
@ -512,10 +512,11 @@ def elf2efi(args: argparse.Namespace):
|
|||
opt.SizeOfImage = align_to(
|
||||
sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT
|
||||
)
|
||||
|
||||
opt.SizeOfHeaders = align_to(
|
||||
PE_OFFSET
|
||||
+ coff.SizeOfOptionalHeader
|
||||
+ sizeof(PeSection) * coff.NumberOfSections,
|
||||
+ sizeof(PeSection) * max(coff.NumberOfSections, args.minimum_sections),
|
||||
FILE_ALIGNMENT,
|
||||
)
|
||||
# DYNAMIC_BASE|NX_COMPAT|HIGH_ENTROPY_VA or DYNAMIC_BASE|NX_COMPAT
|
||||
|
@ -578,6 +579,12 @@ def main():
|
|||
type=argparse.FileType("wb"),
|
||||
help="Output PE/EFI file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--minimum-sections",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Minimum number of sections to leave space for",
|
||||
)
|
||||
|
||||
elf2efi(parser.parse_args())
|
||||
|
||||
|
|
Loading…
Reference in a new issue