<para>The UEFI boot stub looks for various resources for the kernel invocation inside the UEFI PE binary
itself. This allows combining various resources inside a single PE binary image (usually called "Unified
Kernel Image", or "UKI" for short), which may then be signed via UEFI SecureBoot as a whole, covering all
- individual resources at once. Specifically it may include:</para>
+ individual resources at once. Specifically it may include the following PE sections:</para>
<itemizedlist>
<!-- Let's keep this in the canonical order we also measure the sections by, i.e. as in
src/fundamental/uki.h's UnifiedSection enum -->
- <listitem><para>A <literal>.linux</literal> section with the ELF Linux kernel image.</para></listitem>
+ <listitem><para>A <literal>.linux</literal> section with the ELF Linux kernel
+ image. (Required)</para></listitem>
<listitem><para>An <literal>.osrel</literal> section with OS release information, i.e. the contents of
the <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> file
signature data in the <literal>.pcrsig</literal> section.</para></listitem>
</itemizedlist>
- <para>If UEFI SecureBoot is enabled and the <literal>.cmdline</literal> section is present in the executed
- image, any attempts to override the kernel command line by passing one as invocation parameters to the
- EFI binary are ignored. Thus, in order to allow overriding the kernel command line, either disable UEFI
- SecureBoot, or don't include a kernel command line PE section in the kernel image file. If a command line
- is accepted via EFI invocation parameters to the EFI binary it is measured into TPM PCR 12 (if a TPM is
- present).</para>
-
- <para>If a DeviceTree is embedded in the <literal>.dtb</literal> section, it replaces an existing
- DeviceTree in the corresponding EFI configuration table. systemd-stub will ask the firmware via the
- <literal>EFI_DT_FIXUP_PROTOCOL</literal> for hardware specific fixups to the DeviceTree.</para>
-
- <para>The contents of eight of these nine sections are measured into TPM PCR 11. It is otherwise not used
- and thus the result can be pre-calculated without too much effort. The <literal>.pcrsig</literal> section
- is not included in this PCR measurement, since it is supposed to contain signatures for the output of the
- measurement operation, and thus cannot also be input to it.</para>
+ <para>Generally, the sections above should appear at most once in a UKI. That said, a concept of
+ "profiles" is defined, that allows multiple sets of these sections to exist in a single UKI file, of
+ which one can be selected at boot. For this an additional PE section <literal>.profile</literal> is
+ defined which can be used as separator between multiple sets of these settings. The
+ <literal>.profile</literal> section itself may contain meta-information about the section, and follows a
+ similar structure as the contents of the <literal>.osrel</literal> section. For further details about
+ multi-profile UKIs, see below.</para> <para>If UEFI SecureBoot is enabled and the
+ <literal>.cmdline</literal> section is present in the executed image, any attempts to override the kernel
+ command line by passing one as invocation parameters to the EFI binary are ignored. Thus, in order to
+ allow overriding the kernel command line, either disable UEFI SecureBoot, or don't include a kernel
+ command line PE section in the kernel image file. If a command line is accepted via EFI invocation
+ parameters to the EFI binary it is measured into TPM PCR 12 (if a TPM is present).</para> <para>If a
+ DeviceTree is embedded in the <literal>.dtb</literal> section, it replaces an existing DeviceTree in the
+ corresponding EFI configuration table. systemd-stub will ask the firmware via the
+ <literal>EFI_DT_FIXUP_PROTOCOL</literal> for hardware specific fixups to the DeviceTree.</para> <para>The
+ contents of 11 of these 12 sections are measured into TPM PCR 11. It is otherwise not used and thus the
+ result can be pre-calculated without too much effort. The <literal>.pcrsig</literal> section is not
+ included in this PCR measurement, since it is supposed to contain signatures for the output of the
+ measurement operation, and thus cannot also be input to it. If an UKI contains multiple profiles, only
+ the PE sections of the selected profile (and those of the base profile, except if overriden) are
+ measured.</para>
+
+ <para>If non-zero, the selected numeric profile is measured into PCR 12.</para>
<para>When <literal>.pcrsig</literal> and/or <literal>.pcrpkey</literal> sections are present in a
unified kernel image their contents are passed to the booted kernel in an synthetic initrd cpio archive
details); in case of the system extension images by using signed Verity images.</para>
</refsect1>
+ <refsect1>
+ <title>Multi-Profile UKIs</title>
+
+ <para>In many contexts it is useful to allow invocation of a single UKI in multiple different modes (or
+ "profiles") without compromising the cryptographic integrity, measurements and so on of the boot
+ process. For example, a single UKI might provide three distinct profiles: a regular boot one, one that
+ invokes a "factory reset" operation, and one that boots into a storage target mode (as in
+ <citerefentry><refentrytitle>systemd-storagetm.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>). Each
+ profile would then use the same <literal>.linux</literal> and <literal>.initrd</literal> sections, but would
+ have a separate <literal>.cmdline</literal> section. For example the latter two profiles would extend the
+ regular kernel command line with <literal>systemd.unit=factory-reset.target</literal> or
+ <literal>rd.systemd.unit=storagetm.target</literal>.</para>
+
+ <para>A single UKI may support multiple profiles by means of the special <literal>.profile</literal> PE
+ section. This section acts as separator between the PE sections of the individual
+ profiles. <literal>.profile</literal> PE sections hence may appear multiple times in a single UKI, and
+ the other PE sections listed above may appear multiple times too, if <literal>.profile</literal> are
+ used, but only once before the first <literal>.profile</literal> section, once between each subsequent
+ pair, and once after the last appearance of <literal>.profile</literal>. The sections listed before the
+ first <literal>.profile</literal> are considered the "base" profile of the UKI. Each
+ <literal>.profile</literal> section then introduces a new profile, which are numbered starting from
+ zero. The PE sections following each <literal>.profile</literal> are specific to that profile. When
+ booting into a specific profile the base section's profiles are used in combination with the specific
+ profile's sections: if the same section is defined in both, the per-profile section overrides the base
+ profile's version, otherwise the per-profile sections is used together with the base profile
+ sections.</para> <para>A UKI that contains no <literal>.profile</literal> is consider equivalent to one
+ that just contains a single <literal>.profile</literal>, as having only a single profile @0.</para>
+
+ <para>Here's a simple example for a multi-profile UKI's sections, inspired by the setup suggested above:</para>
+
+ <table>
+ <title>Multi-Profile UKI Example</title>
+
+ <tgroup cols='2' align='left' colsep='1' rowsep='1'>
+ <colspec colname="section" />
+ <colspec colname="profile" />
+
+ <thead>
+ <row>
+ <entry>Section</entry>
+ <entry>Profile</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry><literal>.linux</literal></entry>
+ <entry morerows="3" valign="middle">Base profile</entry>
+ </row>
+ <row>
+ <entry><literal>.osrel</literal></entry>
+ </row>
+ <row>
+ <entry><literal>.cmdline</literal></entry>
+ </row>
+ <row>
+ <entry><literal>.initrd</literal></entry>
+ </row>
+ <row>
+ <entry><literal>.profile</literal></entry>
+ <entry>Profile @0</entry>
+ </row>
+ <row>
+ <entry><literal>.profile</literal></entry>
+ <entry morerows="1" valign="middle">Profile @1</entry>
+ </row>
+ <row>
+ <entry><literal>.cmdline</literal></entry>
+ </row>
+ <row>
+ <entry><literal>.profile</literal></entry>
+ <entry morerows="1" valign="middle">Profile @2</entry>
+ </row>
+ <row>
+ <entry><literal>.cmdline</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>The section list above would define three profiles. The first four sections make up the base
+ profile. A <literal>.profile</literal> section then introduces profile @0. It doesn't override any
+ sections (or add any) from the base section, hence it is immediately followed by another
+ <literal>.profile</literal> section that then introduces section @1. This profile overrides the kernel
+ command line. Finally, the last two sections define section @2, again overriding the command line. (Note
+ that in this example the first <literal>.cmdline</literal> could also moved behind the first
+ <literal>.profile</literal> with equivalent effect. To keep things nicely extensible, it's probably a
+ good idea to keep the generic command line in the base section instead of profile 0, in case later added
+ profiles might want to reuse it.)</para>
+
+ <para>The profile to boot may be controlled via the UKI's own command line: if the first argument starts
+ with <literal>@</literal>, followed by a positive integer number in decimal, it selects the profile to
+ boot into. If the first argument is not specified like that, the UKI will automatically boot into profile
+ 0.</para>
+
+ <para>A <literal>.profile</literal> section may contain meta-information about the profile. It follows a
+ similar format as <literal>.osrel</literal> (i.e. an environment-variable-assignment-block-like list of
+ newline separated strings). Currently two fields are defined: <literal>ID=</literal> is supposed to carry
+ a short identifying string that identifies the profile
+ (e.g. <literal>ID=factory-reset</literal>). <literal>TITLE=</literal> should contain a human readable
+ string that may appear in the boot menu entry for this profile (e.g. <literal>TITLE='Factory Reset this
+ Device'</literal>).</para>
+ </refsect1>
+
<refsect1>
<title>TPM PCR Notes</title>
<para>Note that when a unified kernel using <command>systemd-stub</command> is invoked the firmware will
measure it as a whole to TPM PCR 4, covering all embedded resources, such as the stub code itself, the
- core kernel, the embedded initrd and kernel command line (see above for a full list).</para>
+ core kernel, the embedded initrd and kernel command line (see above for a full list), including all UKI
+ profiles.</para>
<para>Also note that the Linux kernel will measure all initrds it receives into TPM PCR 9. This means
- every type of initrd will be measured two or three times: the initrds embedded in the kernel image will be
- measured to PCR 4, PCR 9 and PCR 11; the initrd synthesized from credentials (and the one synthesized
- from configuration extensions) will be measured to both PCR 9 and PCR 12; the initrd synthesized from
- system extensions will be measured to both PCR 4 and PCR 9. Let's summarize the OS resources and the PCRs
- they are measured to:</para>
+ every type of initrd (of the selected UKI profile) will possibly be measured two or three times: the
+ initrds embedded in the kernel image will be measured to PCR 4, PCR 9 and PCR 11; the initrd synthesized
+ from credentials (and the one synthesized from configuration extensions) will be measured to both PCR 9
+ and PCR 12; the initrd synthesized from system extensions will be measured to both PCR 4 and PCR 9. Let's
+ summarize the OS resources and the PCRs they are measured to:</para>
<table>
<title>OS Resource PCR Summary</title>
<entry>Configuration Extensions (synthesized initrd from companion files)</entry>
<entry>9 + 12</entry>
</row>
+
+ <row>
+ <entry>Selected profile unless zero</entry>
+ <entry>12</entry>
+ </row>
</tbody>
</tgroup>
</table>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>StubProfile</varname></term>
+
+ <listitem><para>The numeric index of the selected profile, without the <literal>@</literal>,
+ formatted as decimal string. Set both on single-profile and multi-profile UKIs. (In the former case
+ this variable will be set to <literal>0</literal> unconditionally.)</para>
+
+ <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+ </varlistentry>
</variablelist>
<para>Note that some of the variables above may also be set by the boot loader. The stub will only set
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><filename>/.extra/profile</filename></term>
+ <term><filename>/.extra/os-release</filename></term>
+ <listitem><para>The contents of the <literal>.profile</literal> and <literal>.osrel</literal>
+ sections of the selected profile, if any.</para>
+
+ <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+ </varlistentry>
</variablelist>
<para>Note that all these files are located in the <literal>tmpfs</literal> file system the kernel sets
INITRD_CONFEXT,
INITRD_PCRSIG,
INITRD_PCRPKEY,
+ INITRD_OSREL,
+ INITRD_PROFILE,
_INITRD_MAX,
};
return EFI_SUCCESS;
}
-static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
+static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, unsigned profile) {
static const uint64_t stub_features =
EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */
EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */
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 */
+ EFI_STUB_FEATURE_MULTI_PROFILE_UKI | /* We grok the "@1" profile command line argument */
0;
assert(loaded_image);
(void) efivar_set_str16(MAKE_GUID_PTR(LOADER), u"StubInfo", u"systemd-stub " GIT_VERSION, 0);
(void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"StubFeatures", stub_features, 0);
+
+ (void) efivar_set_uint64_str16(MAKE_GUID_PTR(LOADER), u"StubProfile", profile, 0);
+}
+
+static bool parse_profile_from_cmdline(char16_t **cmdline, unsigned *ret_profile) {
+ assert(cmdline);
+ assert(*cmdline);
+ assert(ret_profile);
+
+ const char16_t *p = *cmdline;
+ if (p[0] != '@')
+ goto nothing;
+
+ uint64_t u;
+ const char16_t *tail;
+ if (!parse_number16(p + 1, &u, &tail))
+ goto nothing;
+ if (u > UINT_MAX)
+ goto nothing;
+ /* Remove exactly one separating space. No further mangling, in order to not disturb measurements –
+ * and thus making prediction harder –, after all we want that people can safely prefix their command
+ * lines with a profile without having to be bothered with additional whitespace the command line
+ * might already contain. */
+ if (tail[0] == u' ')
+ tail++;
+ else if (tail[0] != 0) /* If this is neither a space nor the end of the string, it must be something else */
+ goto nothing;
+
+ /* Drop prefix */
+ free_and_xstrdup16(cmdline, tail);
+ *ret_profile = u;
+ return true;
+
+nothing:
+ *ret_profile = 0;
+ return false;
+}
+
+static bool parse_profile_from_argument(const char16_t *arg, unsigned *ret_profile) {
+ assert(arg);
+ assert(ret_profile);
+
+ if (arg[0] != '@')
+ goto nothing;
+
+ uint64_t u;
+ if (!parse_number16(arg + 1, &u, /* ret_tail= */ NULL))
+ goto nothing;
+
+ if (u > UINT_MAX)
+ goto nothing;
+
+ *ret_profile = u;
+ return true;
+
+nothing:
+ *ret_profile = 0;
+ return false;
}
-static bool use_load_options(
+static void process_arguments(
EFI_HANDLE stub_image,
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
- bool have_cmdline,
- char16_t **ret) {
+ unsigned *ret_profile,
+ char16_t **ret_cmdline) {
assert(stub_image);
assert(loaded_image);
- assert(ret);
-
- /* We only allow custom command lines if we aren't in secure boot or if no cmdline was baked into
- * the stub image.
- * We also don't allow it if we are in confidential vms and secureboot is on. */
- if (secure_boot_enabled() && (have_cmdline || is_confidential_vm()))
- return false;
+ assert(ret_profile);
+ assert(ret_cmdline);
/* The UEFI shell registers EFI_SHELL_PARAMETERS_PROTOCOL onto images it runs. This lets us know that
* LoadOptions starts with the stub binary path which we want to strip off. */
/* We also do a superficial check whether first character of passed command line
* is printable character (for compat with some Dell systems which fill in garbage?). */
if (loaded_image->LoadOptionsSize < sizeof(char16_t) || ((const char16_t *) loaded_image->LoadOptions)[0] <= 0x1F)
- return false;
+ goto nothing;
/* Not running from EFI shell, use entire LoadOptions. Note that LoadOptions is a void*, so
- * it could be anything! */
- *ret = mangle_stub_cmdline(xstrndup16(loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t)));
- return true;
+ * it could actually be anything! */
+ char16_t *c = xstrndup16(loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t));
+ parse_profile_from_cmdline(&c, ret_profile);
+ *ret_cmdline = mangle_stub_cmdline(c);
+ return;
}
- if (shell->Argc < 2)
- /* No arguments were provided? Then we fall back to built-in cmdline. */
- return false;
+ if (shell->Argc <= 1) /* No arguments were provided? Then we fall back to built-in cmdline. */
+ goto nothing;
- /* Assemble the command line ourselves without our stub path. */
- *ret = xstrdup16(shell->Argv[1]);
- for (size_t i = 2; i < shell->Argc; i++) {
- _cleanup_free_ char16_t *old = *ret;
- *ret = xasprintf("%ls %ls", old, shell->Argv[i]);
- }
+ size_t i = 1;
- return true;
+ /* The first argument is possibly an "@5" style profile specifier */
+ i += parse_profile_from_argument(shell->Argv[i], ret_profile);
+
+ if (i < shell->Argc) {
+ /* Assemble the command line ourselves without our stub path. */
+ *ret_cmdline = xstrdup16(shell->Argv[i++]);
+ for (; i < shell->Argc; i++) {
+ _cleanup_free_ char16_t *old = *ret_cmdline;
+ *ret_cmdline = xasprintf("%ls %ls", old, shell->Argv[i]);
+ }
+ } else
+ *ret_cmdline = NULL;
+
+ return;
+
+nothing:
+ *ret_profile = 0;
+ *ret_cmdline = NULL;
+ return;
}
static EFI_STATUS load_addons_from_dir(
const PeSectionVector sections[static _UNIFIED_SECTION_MAX],
struct iovec initrds[static _INITRD_MAX]) {
+ static const struct {
+ UnifiedSection section;
+ size_t initrd_index;
+ const char16_t *filename;
+ } table[] = {
+ /* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it
+ * to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section
+ * is not measured, neither as raw section (see above), nor as cpio (here), because it is the
+ * signature of expected PCR values, i.e. its input are PCR measurements, and hence it shouldn't
+ * itself be input for PCR measurements. */
+ { UNIFIED_SECTION_PCRSIG, INITRD_PCRSIG, u"tpm2-pcr-signature.json" },
+
+ /* If the public key used for the PCR signatures was embedded in the PE image, then let's
+ * wrap it in a cpio and also pass it to the kernel, so that it can be read from
+ * /.extra/tpm2-pcr-public-key.pem. This section is already measured above, hence we won't
+ * measure the cpio. */
+ { UNIFIED_SECTION_PCRPKEY, INITRD_PCRPKEY, u"tpm2-pcr-public-key.pem" },
+
+ /* If we boot a specific profile, let's place the chosen profile in a file that userspace can
+ * make use of this information reasonably. */
+ { UNIFIED_SECTION_PROFILE, INITRD_PROFILE, u"profile" },
+
+ /* Similar, pass the .osrel section too. Userspace should have this information anyway, but
+ * it's so nicely symmetric to the .profile section which we pass around, and who knows,
+ * maybe this is useful to some. */
+ { UNIFIED_SECTION_OSREL, INITRD_OSREL, u"os-release" },
+ };
+
assert(loaded_image);
assert(initrds);
- /* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it
- * to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section
- * is not measured, neither as raw section (see above), nor as cpio (here), because it is the
- * signature of expected PCR values, i.e. its input are PCR measurements, and hence it shouldn't
- * itself be input for PCR measurements. */
- if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_PCRSIG))
- (void) pack_cpio_literal(
- (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_PCRSIG].memory_offset,
- sections[UNIFIED_SECTION_PCRSIG].size,
- ".extra",
- u"tpm2-pcr-signature.json",
- /* dir_mode= */ 0555,
- /* access_mode= */ 0444,
- /* tpm_pcr= */ UINT32_MAX,
- /* tpm_description= */ NULL,
- initrds + INITRD_PCRSIG,
- /* ret_measured= */ NULL);
+ FOREACH_ELEMENT(t, table) {
+ if (!PE_SECTION_VECTOR_IS_SET(sections + t->section))
+ continue;
- /* If the public key used for the PCR signatures was embedded in the PE image, then let's wrap it in
- * a cpio and also pass it to the kernel, so that it can be read from
- * /.extra/tpm2-pcr-public-key.pem. This section is already measure above, hence we won't measure the
- * cpio. */
- if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_PCRPKEY))
(void) pack_cpio_literal(
- (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_PCRPKEY].memory_offset,
- sections[UNIFIED_SECTION_PCRPKEY].size,
+ (const uint8_t*) loaded_image->ImageBase + sections[t->section].memory_offset,
+ sections[t->section].size,
".extra",
- u"tpm2-pcr-public-key.pem",
+ t->filename,
/* dir_mode= */ 0555,
/* access_mode= */ 0444,
/* tpm_pcr= */ UINT32_MAX,
/* tpm_description= */ NULL,
- initrds + INITRD_PCRPKEY,
+ initrds + t->initrd_index,
/* ret_measured= */ NULL);
+ }
}
static void lookup_embedded_initrds(
graphics_splash((const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_SPLASH].memory_offset, sections[UNIFIED_SECTION_SPLASH].size);
}
-static void determine_cmdline(
- EFI_HANDLE image,
+static EFI_STATUS find_sections(
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
+ unsigned profile,
+ PeSectionVector sections[static _UNIFIED_SECTION_MAX]) {
+
+ EFI_STATUS err;
+
+ assert(loaded_image);
+ assert(sections);
+
+ const PeSectionHeader *section_table;
+ size_t n_section_table;
+ err = pe_section_table_from_base(loaded_image->ImageBase, §ion_table, &n_section_table);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Unable to locate PE section table: %m");
+
+ /* Get the base sections */
+ err = pe_locate_profile_sections(
+ section_table,
+ n_section_table,
+ unified_sections,
+ /* profile= */ UINT_MAX,
+ /* validate_base= */ PTR_TO_SIZE(loaded_image->ImageBase),
+ sections);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Unable to locate embedded base PE sections: %m");
+
+ if (profile != UINT_MAX) {
+ /* And then override them with the per-profile sections of the selected profile */
+ err = pe_locate_profile_sections(
+ section_table,
+ n_section_table,
+ unified_sections,
+ profile,
+ /* validate_base= */ PTR_TO_SIZE(loaded_image->ImageBase),
+ sections);
+ if (err != EFI_SUCCESS && !(err == EFI_NOT_FOUND && profile == 0)) /* the first profile is implied if it doesn't exist */
+ return log_error_status(err, "Unable to locate embedded per-profile PE sections: %m");
+ }
+
+ if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_LINUX))
+ return log_error_status(EFI_NOT_FOUND, "Image lacks .linux section.");
+
+ return EFI_SUCCESS;
+}
+
+static void settle_command_line(
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
const PeSectionVector sections[static _UNIFIED_SECTION_MAX],
- char16_t **ret_cmdline,
+ char16_t **cmdline,
int *parameters_measured) {
assert(loaded_image);
assert(sections);
+ assert(cmdline);
- if (use_load_options(image, loaded_image, /* have_cmdline= */ PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE), ret_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
- * we want the boot menu to support an EFI binary, and want to this stub to be usable from
- * any boot menu, let's measure things anyway. */
- bool m = false;
- (void) tpm_log_load_options(*ret_cmdline, &m);
- combine_measured_flag(parameters_measured, m);
- } else
- *ret_cmdline = mangle_stub_cmdline(pe_section_to_str16(loaded_image, sections + UNIFIED_SECTION_CMDLINE));
+ /* This determines which command line to use. On input *cmdline contains the custom passed in cmdline
+ * if there is any.
+ *
+ * We'll suppress the custom cmdline if we are in Secure Boot mode, and if either there is already
+ * a cmdline baked into the UKI or we are in confidential VM mode. */
+
+ if (!isempty(*cmdline)) {
+ if (secure_boot_enabled() && (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE) || is_confidential_vm()))
+ /* Drop the custom cmdline */
+ *cmdline = mfree(*cmdline);
+ else {
+ /* 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 we want the boot menu to support an EFI binary, and want to
+ * this stub to be usable from any boot menu, let's measure things anyway. */
+ bool m = false;
+ (void) tpm_log_load_options(*cmdline, &m);
+ combine_measured_flag(parameters_measured, m);
+ }
+ }
+
+ /* No cmdline specified? Or suppressed? Then let's take the one from the UKI, if there is any. */
+ if (isempty(*cmdline))
+ *cmdline = mangle_stub_cmdline(pe_section_to_str16(loaded_image, sections + UNIFIED_SECTION_CMDLINE));
+}
+
+static void measure_profile(unsigned profile, int *parameters_measured) {
+ if (profile == 0) /* don't measure anything about the default profile */
+ return;
+
+ _cleanup_free_ char16_t *s = xasprintf("%u", profile);
+
+ bool m = false;
+ (void) tpm_log_tagged_event(
+ TPM2_PCR_KERNEL_CONFIG,
+ POINTER_TO_PHYSICAL_ADDRESS(s),
+ strsize16(s),
+ UKI_PROFILE_EVENT_TAG_ID,
+ s,
+ &m);
+ combine_measured_flag(parameters_measured, m);
}
static EFI_STATUS run(EFI_HANDLE image) {
size_t n_dt_addons = 0, n_ucode_addons = 0;
_cleanup_free_ struct iovec *all_initrds = NULL;
size_t n_all_initrds = 0;
+ unsigned profile = 0;
EFI_STATUS err;
err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
- err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, sections);
+ /* Pick up the arguments passed to us, split out the prefixing profile parameter, and return the rest
+ * as potential command line to use. */
+ (void) process_arguments(image, loaded_image, &profile, &cmdline);
+
+ /* Find the sections we want to operate on, both the basic ones, and the one appropriate for the
+ * selected profile. */
+ err = find_sections(loaded_image, profile, sections);
if (err != EFI_SUCCESS)
- return log_error_status(err, "Unable to locate embedded PE sections: %m");
- if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_LINUX))
- return log_error_status(EFI_NOT_FOUND, "Image lacks .linux section.");
+ return err;
+ measure_profile(profile, ¶meters_measured);
measure_sections(loaded_image, sections, §ions_measured);
/* Show splash screen as early as possible, but after measuring it */
uname = pe_section_to_str8(loaded_image, sections + UNIFIED_SECTION_UNAME);
- determine_cmdline(image, loaded_image, sections, &cmdline, ¶meters_measured);
+ /* Let's now check if we actually want to use the command line, measure it if it was passed in. */
+ settle_command_line(loaded_image, sections, &cmdline, ¶meters_measured);
/* 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. */
cmdline_append_and_measure_smbios(&cmdline, ¶meters_measured);
export_common_variables(loaded_image);
- export_stub_variables(loaded_image);
+ export_stub_variables(loaded_image, profile);
/* First load the base device tree, then fix it up using addons - global first, then per-UKI. */
install_embedded_devicetree(loaded_image, sections, &dt_state);