mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
stub: add ability to place multiple alternative PE sections of a specific type in the same UKI ("Multi-Profile UKIs")
This adds a ability to add alternative sections of a specific type in the same UKI. The primary usecase is for supporting multiple different kernel cmdlines that are baked into a UKI. The mechanism is relatively simple (I think), in order to make it robust. 1. A new PE section ".profile" is introduced, that is a lot like ".osrel", but contains information about a specific "profile" to boot. The ".profile" section can appear multiple times in the same PE, and acts as delimiter indicating where a new profile starts. Everything before the first ".profile" is called the "base profile", and is shared among all other profiles, which can then override or add addition PE sections on top. 2. An UKI's command line can be prefixed with an argument such as "@0" or "@1" or "@2" which indicates the "profile" to boot. If no argument is specified the default is profile 0. Also, a UKI that lacks any .profile section is treated like one with only a profile 0, but with no data in that profile section. 3. The stub will first search for its usual set of PE sections (hereafter called "base sections"), and stop at the first .profile PE section if any. It will then find the .profile matching the selected profile by its index, and any sections found as part of that profile on top of the base sections. And that's already it. Example: let's say a distro wants to provide a single UKI that can be invoked in one of three ways: 1. The regular profile that just boots the system 2. A profile that boots into storagetm 3. A profile that initiates factory reset and reboots. For this it would define a classic UKI with sections .linux, .initrd, .cmdline, and whatever else it needs. The .cmdline section would contain the kernel command line for the regular profile. It would then insert one ".profile" section, with a contents like the following: ID=regular This is the profile for profile 0. It would immediately afterwards add another ".profile" section: ID=storagetm TITLE=Boot into Storage Target Mode This would then followed with a .cmdline section that is just like the basic one, but with "rd.systemd.unit=storage-target-mode.target" suffixed. Then, another .profile section would be added: ID=factory-reset TITLE=Factory Reset Which is then followed by one last PE section: a .cmdline one with "systemd.unit=factory-reset.target" suffixed to te regular command line. i.e. expressed in tabular form the above would be: The base profile: .linux .initrd .cmdline .osrel The regular boot profile: .profile The storagetm profile: .profile .cmdline The factory reset profile: .profile .cmdline You might wonder why the first .cmdline in the list above is placed in the base profile rather than in the regular boot profile, given that it is overriden in all other profiles anyway. And you are right. The only reason I'd place it in the base profile is that it makes the UKI more nicely extensible if later profiles are added that want to replace something else instead of the .cmdline, for example .ucode or so. But it really doesn't matter much. While the primary usecase is of course multiple alternative command lines, the concept is more powerful than that: for various usecases it might be valuable to offer multiple choices of devicetree, ucode or initrds. The .profile contents is also passed to the invoked kernel as a file in /.extra/profile (via a synthetic initrd). Thus, this functionality can even be useful without overriding any section at all, simply by means of reading that file from userspace. Design choices: 1. On purposes I used a special command line marker (i.e. the "@" thing, which maybe we should call the "profile selector"), that doesn't look like a regular kernel command line option. This is because this is really not a regular kernel command line option – we process it in the stub, then remove it as prefix, and measure the unprefixed command line only after that. The kernel will not see the profile selector either. I think these special semantics are best communicated by making it look substantially different from regular options. 2. This moves around measurements a bit. Previously we measured our UKI sections right after finding them. Now we first parse the profile number from the command line, then search for the profile's sections, and only then measure the sections we actually end up using for this profile. I think that this logic makes most sense: measure what we are using, not what we are overriding. Or in other words, if you boot profile @3, then we'll measure .cmdline (assuming it exists) of profile 3, and *not* measure .cmdline of the base profile. Also note that if the user passes in a custom kernel command line via command line arguments we'll strip off the profile selector (i.e. the initial "@X" thing) before we pass it on. 3. The .profile stuff is supposed to be generic and extensible. For example we could use it in future to mark "dangerous" options such as factory reset, so that boot menus can ask for confirmation before booting into it. Or we could introduce match expressions against SMBIOS or other system identifiers, to filter out profiles on specific hw. Note btw, that PE allows defining multiple sections that point to the same offsets in the file. This allows sharing payload under different names. For example, if profile @4 and @7 shall carry the same .ucode section, they can define .ucode in each profile and then make it point to the same offset. Also note that that one can even "mask" a base section in a profile, by inserting an empty section. For example, if the base .dtb section should not be used for profile @4, then add a section .dtb right after the fourth .profile with a zero size to the UKI, and you will get your wish fulfilled. This code only contains changes to sd-stub. A follow-up commit will teach sd-boot to also find this profile PE sections to synthesize additional menu entries from a single UKI. A later commit will add support for gnerating this via ukify. Fixes: #24539
This commit is contained in:
parent
f4e081051d
commit
a632d8dd9f
@ -53,13 +53,14 @@
|
||||
<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
|
||||
@ -95,21 +96,29 @@
|
||||
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>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 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>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
|
||||
@ -231,19 +240,124 @@
|
||||
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>
|
||||
@ -324,6 +438,11 @@
|
||||
<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>
|
||||
@ -422,6 +541,16 @@
|
||||
|
||||
<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
|
||||
@ -500,6 +629,15 @@
|
||||
|
||||
<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
|
||||
|
@ -38,6 +38,8 @@ enum {
|
||||
INITRD_CONFEXT,
|
||||
INITRD_PCRSIG,
|
||||
INITRD_PCRPKEY,
|
||||
INITRD_OSREL,
|
||||
INITRD_PROFILE,
|
||||
_INITRD_MAX,
|
||||
};
|
||||
|
||||
@ -139,7 +141,7 @@ static EFI_STATUS combine_initrds(
|
||||
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 */
|
||||
@ -150,6 +152,7 @@ static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
|
||||
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);
|
||||
@ -159,23 +162,76 @@ static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *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 use_load_options(
|
||||
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 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. */
|
||||
@ -185,26 +241,40 @@ static bool use_load_options(
|
||||
/* 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(
|
||||
@ -734,43 +804,53 @@ static void generate_embedded_initrds(
|
||||
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(
|
||||
@ -898,26 +978,103 @@ static void display_splash(
|
||||
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,
|
||||
const PeSectionVector sections[static _UNIFIED_SECTION_MAX],
|
||||
char16_t **ret_cmdline,
|
||||
int *parameters_measured) {
|
||||
unsigned profile,
|
||||
PeSectionVector sections[static _UNIFIED_SECTION_MAX]) {
|
||||
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(loaded_image);
|
||||
assert(sections);
|
||||
|
||||
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));
|
||||
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 **cmdline,
|
||||
int *parameters_measured) {
|
||||
|
||||
assert(loaded_image);
|
||||
assert(sections);
|
||||
assert(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) {
|
||||
@ -932,18 +1089,24 @@ 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);
|
||||
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.");
|
||||
/* 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 err;
|
||||
|
||||
measure_profile(profile, ¶meters_measured);
|
||||
measure_sections(loaded_image, sections, §ions_measured);
|
||||
|
||||
/* Show splash screen as early as possible, but after measuring it */
|
||||
@ -953,7 +1116,8 @@ static EFI_STATUS run(EFI_HANDLE image) {
|
||||
|
||||
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. */
|
||||
@ -969,7 +1133,7 @@ static EFI_STATUS run(EFI_HANDLE image) {
|
||||
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);
|
||||
|
@ -34,6 +34,7 @@
|
||||
#define EFI_STUB_FEATURE_CMDLINE_SMBIOS (UINT64_C(1) << 6)
|
||||
#define EFI_STUB_FEATURE_DEVICETREE_ADDONS (UINT64_C(1) << 7)
|
||||
#define EFI_STUB_FEATURE_PICK_UP_CONFEXTS (UINT64_C(1) << 8)
|
||||
#define EFI_STUB_FEATURE_MULTI_PROFILE_UKI (UINT64_C(1) << 9)
|
||||
|
||||
typedef enum SecureBootMode {
|
||||
SECURE_BOOT_UNSUPPORTED,
|
||||
|
@ -52,3 +52,6 @@ enum {
|
||||
|
||||
/* The tag used for EV_EVENT_TAG event log records covering ucode addons (effectively initrds) */
|
||||
#define UCODE_ADDON_EVENT_TAG_ID UINT32_C(0xdac08e1a)
|
||||
|
||||
/* The tag used for EV_EVENT_TAG event log records covering the selected UKI profile */
|
||||
#define UKI_PROFILE_EVENT_TAG_ID UINT32_C(0x13aed6db)
|
||||
|
Loading…
Reference in New Issue
Block a user