mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
Merge pull request #34294 from poettering/uki-with-many-core
multi-profile UKIs (systemd-stub hookup)
This commit is contained in:
commit
f2129f1d8c
@ -53,13 +53,14 @@
|
|||||||
<para>The UEFI boot stub looks for various resources for the kernel invocation inside the UEFI PE binary
|
<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
|
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
|
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>
|
<itemizedlist>
|
||||||
<!-- Let's keep this in the canonical order we also measure the sections by, i.e. as in
|
<!-- 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 -->
|
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
|
<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
|
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>
|
signature data in the <literal>.pcrsig</literal> section.</para></listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
|
|
||||||
<para>If UEFI SecureBoot is enabled and the <literal>.cmdline</literal> section is present in the executed
|
<para>Generally, the sections above should appear at most once in a UKI. That said, a concept of
|
||||||
image, any attempts to override the kernel command line by passing one as invocation parameters to the
|
"profiles" is defined, that allows multiple sets of these sections to exist in a single UKI file, of
|
||||||
EFI binary are ignored. Thus, in order to allow overriding the kernel command line, either disable UEFI
|
which one can be selected at boot. For this an additional PE section <literal>.profile</literal> is
|
||||||
SecureBoot, or don't include a kernel command line PE section in the kernel image file. If a command line
|
defined which can be used as separator between multiple sets of these settings. The
|
||||||
is accepted via EFI invocation parameters to the EFI binary it is measured into TPM PCR 12 (if a TPM is
|
<literal>.profile</literal> section itself may contain meta-information about the section, and follows a
|
||||||
present).</para>
|
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
|
<para>If non-zero, the selected numeric profile is measured into PCR 12.</para>
|
||||||
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>When <literal>.pcrsig</literal> and/or <literal>.pcrpkey</literal> sections are present in a
|
<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
|
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>
|
details); in case of the system extension images by using signed Verity images.</para>
|
||||||
</refsect1>
|
</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>
|
<refsect1>
|
||||||
<title>TPM PCR Notes</title>
|
<title>TPM PCR Notes</title>
|
||||||
|
|
||||||
<para>Note that when a unified kernel using <command>systemd-stub</command> is invoked the firmware will
|
<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
|
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
|
<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
|
every type of initrd (of the selected UKI profile) will possibly be measured two or three times: the
|
||||||
measured to PCR 4, PCR 9 and PCR 11; the initrd synthesized from credentials (and the one synthesized
|
initrds embedded in the kernel image will be measured to PCR 4, PCR 9 and PCR 11; the initrd synthesized
|
||||||
from configuration extensions) will be measured to both PCR 9 and PCR 12; the initrd synthesized from
|
from credentials (and the one synthesized from configuration extensions) will be measured to both PCR 9
|
||||||
system extensions will be measured to both PCR 4 and PCR 9. Let's summarize the OS resources and the PCRs
|
and PCR 12; the initrd synthesized from system extensions will be measured to both PCR 4 and PCR 9. Let's
|
||||||
they are measured to:</para>
|
summarize the OS resources and the PCRs they are measured to:</para>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<title>OS Resource PCR Summary</title>
|
<title>OS Resource PCR Summary</title>
|
||||||
@ -324,6 +438,11 @@
|
|||||||
<entry>Configuration Extensions (synthesized initrd from companion files)</entry>
|
<entry>Configuration Extensions (synthesized initrd from companion files)</entry>
|
||||||
<entry>9 + 12</entry>
|
<entry>9 + 12</entry>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>Selected profile unless zero</entry>
|
||||||
|
<entry>12</entry>
|
||||||
|
</row>
|
||||||
</tbody>
|
</tbody>
|
||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
@ -422,6 +541,16 @@
|
|||||||
|
|
||||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||||
</varlistentry>
|
</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>
|
</variablelist>
|
||||||
|
|
||||||
<para>Note that some of the variables above may also be set by the boot loader. The stub will only set
|
<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>
|
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
|
||||||
</varlistentry>
|
</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>
|
</variablelist>
|
||||||
|
|
||||||
<para>Note that all these files are located in the <literal>tmpfs</literal> file system the kernel sets
|
<para>Note that all these files are located in the <literal>tmpfs</literal> file system the kernel sets
|
||||||
|
@ -106,19 +106,6 @@ typedef struct PeFileHeader {
|
|||||||
PeOptionalHeader OptionalHeader;
|
PeOptionalHeader OptionalHeader;
|
||||||
} _packed_ PeFileHeader;
|
} _packed_ PeFileHeader;
|
||||||
|
|
||||||
typedef struct PeSectionHeader {
|
|
||||||
uint8_t Name[8];
|
|
||||||
uint32_t VirtualSize;
|
|
||||||
uint32_t VirtualAddress;
|
|
||||||
uint32_t SizeOfRawData;
|
|
||||||
uint32_t PointerToRawData;
|
|
||||||
uint32_t PointerToRelocations;
|
|
||||||
uint32_t PointerToLinenumbers;
|
|
||||||
uint16_t NumberOfRelocations;
|
|
||||||
uint16_t NumberOfLinenumbers;
|
|
||||||
uint32_t Characteristics;
|
|
||||||
} _packed_ PeSectionHeader;
|
|
||||||
|
|
||||||
#define SECTION_TABLE_BYTES_MAX (16U * 1024U * 1024U)
|
#define SECTION_TABLE_BYTES_MAX (16U * 1024U * 1024U)
|
||||||
|
|
||||||
static bool verify_dos(const DosFileHeader *dos) {
|
static bool verify_dos(const DosFileHeader *dos) {
|
||||||
@ -188,7 +175,7 @@ static void pe_locate_sections(
|
|||||||
|
|
||||||
/* Searches for the sections listed in 'sections[]' within the section table. Validates the resulted
|
/* Searches for the sections listed in 'sections[]' within the section table. Validates the resulted
|
||||||
* data. If 'validate_base' is non-zero also takes base offset when loaded into memory into account for
|
* data. If 'validate_base' is non-zero also takes base offset when loaded into memory into account for
|
||||||
* qchecking for overflows. */
|
* checking for overflows. */
|
||||||
|
|
||||||
for (size_t i = 0; section_names[i]; i++)
|
for (size_t i = 0; section_names[i]; i++)
|
||||||
FOREACH_ARRAY(j, section_table, n_section_table) {
|
FOREACH_ARRAY(j, section_table, n_section_table) {
|
||||||
@ -313,31 +300,49 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address, size_t
|
|||||||
return EFI_SUCCESS;
|
return EFI_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EFI_STATUS pe_section_table_from_base(
|
||||||
|
const void *base,
|
||||||
|
const PeSectionHeader **ret_section_table,
|
||||||
|
size_t *ret_n_section_table) {
|
||||||
|
|
||||||
|
assert(base);
|
||||||
|
assert(ret_section_table);
|
||||||
|
assert(ret_n_section_table);
|
||||||
|
|
||||||
|
const DosFileHeader *dos = (const DosFileHeader*) base;
|
||||||
|
if (!verify_dos(dos))
|
||||||
|
return EFI_LOAD_ERROR;
|
||||||
|
|
||||||
|
const PeFileHeader *pe = (const PeFileHeader*) ((const uint8_t*) base + dos->ExeHeader);
|
||||||
|
if (!verify_pe(dos, pe, /* allow_compatibility= */ false))
|
||||||
|
return EFI_LOAD_ERROR;
|
||||||
|
|
||||||
|
*ret_section_table = (const PeSectionHeader*) ((const uint8_t*) base + section_table_offset(dos, pe));
|
||||||
|
*ret_n_section_table = pe->FileHeader.NumberOfSections;
|
||||||
|
|
||||||
|
return EFI_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
EFI_STATUS pe_memory_locate_sections(
|
EFI_STATUS pe_memory_locate_sections(
|
||||||
const void *base,
|
const void *base,
|
||||||
const char *const section_names[],
|
const char *const section_names[],
|
||||||
PeSectionVector sections[]) {
|
PeSectionVector sections[]) {
|
||||||
|
|
||||||
const DosFileHeader *dos;
|
EFI_STATUS err;
|
||||||
const PeFileHeader *pe;
|
|
||||||
size_t offset;
|
|
||||||
|
|
||||||
assert(base);
|
assert(base);
|
||||||
assert(section_names);
|
assert(section_names);
|
||||||
assert(sections);
|
assert(sections);
|
||||||
|
|
||||||
dos = (const DosFileHeader *) base;
|
const PeSectionHeader *section_table;
|
||||||
if (!verify_dos(dos))
|
size_t n_section_table;
|
||||||
return EFI_LOAD_ERROR;
|
err = pe_section_table_from_base(base, §ion_table, &n_section_table);
|
||||||
|
if (err != EFI_SUCCESS)
|
||||||
|
return err;
|
||||||
|
|
||||||
pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader);
|
|
||||||
if (!verify_pe(dos, pe, /* allow_compatibility= */ false))
|
|
||||||
return EFI_LOAD_ERROR;
|
|
||||||
|
|
||||||
offset = section_table_offset(dos, pe);
|
|
||||||
pe_locate_sections(
|
pe_locate_sections(
|
||||||
(const PeSectionHeader *) ((const uint8_t *) base + offset),
|
section_table,
|
||||||
pe->FileHeader.NumberOfSections,
|
n_section_table,
|
||||||
section_names,
|
section_names,
|
||||||
PTR_TO_SIZE(base),
|
PTR_TO_SIZE(base),
|
||||||
sections);
|
sections);
|
||||||
@ -345,27 +350,19 @@ EFI_STATUS pe_memory_locate_sections(
|
|||||||
return EFI_SUCCESS;
|
return EFI_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
EFI_STATUS pe_file_locate_sections(
|
EFI_STATUS pe_section_table_from_file(
|
||||||
EFI_FILE *dir,
|
EFI_FILE *handle,
|
||||||
const char16_t *path,
|
PeSectionHeader **ret_section_table,
|
||||||
const char *const section_names[],
|
size_t *ret_n_section_table) {
|
||||||
PeSectionVector sections[]) {
|
|
||||||
_cleanup_free_ PeSectionHeader *section_table = NULL;
|
|
||||||
_cleanup_(file_closep) EFI_FILE *handle = NULL;
|
|
||||||
DosFileHeader dos;
|
|
||||||
PeFileHeader pe;
|
|
||||||
size_t len, section_table_len;
|
|
||||||
EFI_STATUS err;
|
EFI_STATUS err;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
assert(dir);
|
assert(handle);
|
||||||
assert(path);
|
assert(ret_section_table);
|
||||||
assert(section_names);
|
assert(ret_n_section_table);
|
||||||
assert(sections);
|
|
||||||
|
|
||||||
err = dir->Open(dir, &handle, (char16_t *) path, EFI_FILE_MODE_READ, 0ULL);
|
|
||||||
if (err != EFI_SUCCESS)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
|
DosFileHeader dos;
|
||||||
len = sizeof(dos);
|
len = sizeof(dos);
|
||||||
err = handle->Read(handle, &len, &dos);
|
err = handle->Read(handle, &len, &dos);
|
||||||
if (err != EFI_SUCCESS)
|
if (err != EFI_SUCCESS)
|
||||||
@ -377,6 +374,7 @@ EFI_STATUS pe_file_locate_sections(
|
|||||||
if (err != EFI_SUCCESS)
|
if (err != EFI_SUCCESS)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
|
PeFileHeader pe;
|
||||||
len = sizeof(pe);
|
len = sizeof(pe);
|
||||||
err = handle->Read(handle, &len, &pe);
|
err = handle->Read(handle, &len, &pe);
|
||||||
if (err != EFI_SUCCESS)
|
if (err != EFI_SUCCESS)
|
||||||
@ -388,10 +386,11 @@ EFI_STATUS pe_file_locate_sections(
|
|||||||
if ((size_t) pe.FileHeader.NumberOfSections > SIZE_MAX / sizeof(PeSectionHeader))
|
if ((size_t) pe.FileHeader.NumberOfSections > SIZE_MAX / sizeof(PeSectionHeader))
|
||||||
return EFI_OUT_OF_RESOURCES;
|
return EFI_OUT_OF_RESOURCES;
|
||||||
REENABLE_WARNING;
|
REENABLE_WARNING;
|
||||||
section_table_len = (size_t) pe.FileHeader.NumberOfSections * sizeof(PeSectionHeader);
|
size_t n_section_table = (size_t) pe.FileHeader.NumberOfSections;
|
||||||
if (section_table_len > SECTION_TABLE_BYTES_MAX)
|
if (n_section_table * sizeof(PeSectionHeader) > SECTION_TABLE_BYTES_MAX)
|
||||||
return EFI_OUT_OF_RESOURCES;
|
return EFI_OUT_OF_RESOURCES;
|
||||||
section_table = xmalloc(section_table_len);
|
|
||||||
|
_cleanup_free_ PeSectionHeader *section_table = xnew(PeSectionHeader, n_section_table);
|
||||||
if (!section_table)
|
if (!section_table)
|
||||||
return EFI_OUT_OF_RESOURCES;
|
return EFI_OUT_OF_RESOURCES;
|
||||||
|
|
||||||
@ -399,19 +398,138 @@ EFI_STATUS pe_file_locate_sections(
|
|||||||
if (err != EFI_SUCCESS)
|
if (err != EFI_SUCCESS)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
len = section_table_len;
|
len = n_section_table * sizeof(PeSectionHeader);
|
||||||
err = handle->Read(handle, &len, section_table);
|
err = handle->Read(handle, &len, section_table);
|
||||||
if (err != EFI_SUCCESS)
|
if (err != EFI_SUCCESS)
|
||||||
return err;
|
return err;
|
||||||
if (len != section_table_len)
|
if (len != n_section_table * sizeof(PeSectionHeader))
|
||||||
return EFI_LOAD_ERROR;
|
return EFI_LOAD_ERROR;
|
||||||
|
|
||||||
|
*ret_section_table = TAKE_PTR(section_table);
|
||||||
|
*ret_n_section_table = n_section_table;
|
||||||
|
return EFI_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
EFI_STATUS pe_file_locate_sections(
|
||||||
|
EFI_FILE *dir,
|
||||||
|
const char16_t *path,
|
||||||
|
const char* const section_names[],
|
||||||
|
PeSectionVector sections[]) {
|
||||||
|
|
||||||
|
_cleanup_free_ PeSectionHeader *section_table = NULL;
|
||||||
|
_cleanup_(file_closep) EFI_FILE *handle = NULL;
|
||||||
|
size_t n_section_table;
|
||||||
|
EFI_STATUS err;
|
||||||
|
|
||||||
|
assert(dir);
|
||||||
|
assert(path);
|
||||||
|
assert(section_names);
|
||||||
|
assert(sections);
|
||||||
|
|
||||||
|
err = dir->Open(dir, &handle, (char16_t *) path, EFI_FILE_MODE_READ, 0ULL);
|
||||||
|
if (err != EFI_SUCCESS)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = pe_section_table_from_file(handle, §ion_table, &n_section_table);
|
||||||
|
if (err != EFI_SUCCESS)
|
||||||
|
return err;
|
||||||
|
|
||||||
pe_locate_sections(
|
pe_locate_sections(
|
||||||
section_table,
|
section_table,
|
||||||
pe.FileHeader.NumberOfSections,
|
n_section_table,
|
||||||
section_names,
|
section_names,
|
||||||
/* validate_base= */ 0, /* don't validate base */
|
/* validate_base= */ 0, /* don't validate base */
|
||||||
sections);
|
sections);
|
||||||
|
|
||||||
return EFI_SUCCESS;
|
return EFI_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const PeSectionHeader* pe_section_table_find_profile_start(
|
||||||
|
const PeSectionHeader *section_table,
|
||||||
|
size_t n_section_table,
|
||||||
|
unsigned profile) {
|
||||||
|
|
||||||
|
assert(section_table || n_section_table == 0);
|
||||||
|
|
||||||
|
if (profile == UINT_MAX) /* base profile? that starts at the beginning */
|
||||||
|
return section_table;
|
||||||
|
|
||||||
|
unsigned current_profile = UINT_MAX;
|
||||||
|
FOREACH_ARRAY(p, section_table, n_section_table) {
|
||||||
|
|
||||||
|
if (!pe_section_name_equal((const char*) p->Name, ".profile"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (current_profile == UINT_MAX)
|
||||||
|
current_profile = 0;
|
||||||
|
else
|
||||||
|
current_profile++;
|
||||||
|
|
||||||
|
if (current_profile == profile) /* Found our profile! */
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We reached the end of the table? Then this section does not exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t pe_section_table_find_profile_length(
|
||||||
|
const PeSectionHeader *section_table,
|
||||||
|
size_t n_section_table,
|
||||||
|
const PeSectionHeader *start,
|
||||||
|
unsigned profile) {
|
||||||
|
|
||||||
|
assert(section_table);
|
||||||
|
assert(n_section_table > 0);
|
||||||
|
assert(start >= section_table);
|
||||||
|
assert(start < section_table + n_section_table);
|
||||||
|
|
||||||
|
/* Look for the next .profile (or the end of the table), this is where the the sections for this
|
||||||
|
* profile end. The base profile does not start with a .profile, the others do, hence conditionally
|
||||||
|
* skip over the first entry. */
|
||||||
|
const PeSectionHeader *e;
|
||||||
|
if (profile == UINT_MAX) /* Base profile */
|
||||||
|
e = start;
|
||||||
|
else {
|
||||||
|
assert(pe_section_name_equal((const char *) start->Name, ".profile"));
|
||||||
|
e = start + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; e < section_table + n_section_table; e++)
|
||||||
|
if (pe_section_name_equal((const char*) e->Name, ".profile"))
|
||||||
|
return e - start;
|
||||||
|
|
||||||
|
return (section_table + n_section_table) - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
EFI_STATUS pe_locate_profile_sections(
|
||||||
|
const PeSectionHeader section_table[],
|
||||||
|
size_t n_section_table,
|
||||||
|
const char* const section_names[],
|
||||||
|
unsigned profile,
|
||||||
|
size_t validate_base,
|
||||||
|
PeSectionVector sections[]) {
|
||||||
|
|
||||||
|
assert(section_table || n_section_table == 0);
|
||||||
|
assert(section_names);
|
||||||
|
assert(sections);
|
||||||
|
|
||||||
|
/* Now scan through the section table until we skipped over the right number of .profile sections */
|
||||||
|
const PeSectionHeader *p = pe_section_table_find_profile_start(section_table, n_section_table, profile);
|
||||||
|
if (!p)
|
||||||
|
return EFI_NOT_FOUND;
|
||||||
|
|
||||||
|
/* Look for the next .profile (or the end of the table), this is where the the sections for this
|
||||||
|
* profile end. */
|
||||||
|
size_t n = pe_section_table_find_profile_length(section_table, n_section_table, p, profile);
|
||||||
|
|
||||||
|
/* And now parse everything between the start and end of our profile */
|
||||||
|
pe_locate_sections(
|
||||||
|
p,
|
||||||
|
n,
|
||||||
|
section_names,
|
||||||
|
validate_base,
|
||||||
|
sections);
|
||||||
|
|
||||||
|
return EFI_SUCCESS;
|
||||||
|
}
|
||||||
|
@ -3,6 +3,20 @@
|
|||||||
|
|
||||||
#include "efi.h"
|
#include "efi.h"
|
||||||
|
|
||||||
|
/* This is the actual PE format of the section header*/
|
||||||
|
typedef struct PeSectionHeader {
|
||||||
|
uint8_t Name[8];
|
||||||
|
uint32_t VirtualSize;
|
||||||
|
uint32_t VirtualAddress;
|
||||||
|
uint32_t SizeOfRawData;
|
||||||
|
uint32_t PointerToRawData;
|
||||||
|
uint32_t PointerToRelocations;
|
||||||
|
uint32_t PointerToLinenumbers;
|
||||||
|
uint16_t NumberOfRelocations;
|
||||||
|
uint16_t NumberOfLinenumbers;
|
||||||
|
uint32_t Characteristics;
|
||||||
|
} _packed_ PeSectionHeader;
|
||||||
|
|
||||||
/* This is a subset of the full PE section header structure, with validated values, and without
|
/* This is a subset of the full PE section header structure, with validated values, and without
|
||||||
* the noise. */
|
* the noise. */
|
||||||
typedef struct PeSectionVector {
|
typedef struct PeSectionVector {
|
||||||
@ -15,6 +29,24 @@ static inline bool PE_SECTION_VECTOR_IS_SET(const PeSectionVector *v) {
|
|||||||
return v && v->size != 0;
|
return v && v->size != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EFI_STATUS pe_section_table_from_base(
|
||||||
|
const void *base,
|
||||||
|
const PeSectionHeader **ret_section_table,
|
||||||
|
size_t *ret_n_section_table);
|
||||||
|
|
||||||
|
EFI_STATUS pe_section_table_from_file(
|
||||||
|
EFI_FILE *handle,
|
||||||
|
PeSectionHeader **ret_section_table,
|
||||||
|
size_t *ret_n_section_table);
|
||||||
|
|
||||||
|
EFI_STATUS pe_locate_profile_sections(
|
||||||
|
const PeSectionHeader section_table[],
|
||||||
|
size_t n_section_table,
|
||||||
|
const char* const section_names[],
|
||||||
|
unsigned profile,
|
||||||
|
size_t validate_base,
|
||||||
|
PeSectionVector sections[]);
|
||||||
|
|
||||||
EFI_STATUS pe_memory_locate_sections(
|
EFI_STATUS pe_memory_locate_sections(
|
||||||
const void *base,
|
const void *base,
|
||||||
const char *const section_names[],
|
const char *const section_names[],
|
||||||
|
@ -38,6 +38,8 @@ enum {
|
|||||||
INITRD_CONFEXT,
|
INITRD_CONFEXT,
|
||||||
INITRD_PCRSIG,
|
INITRD_PCRSIG,
|
||||||
INITRD_PCRPKEY,
|
INITRD_PCRPKEY,
|
||||||
|
INITRD_OSREL,
|
||||||
|
INITRD_PROFILE,
|
||||||
_INITRD_MAX,
|
_INITRD_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -139,7 +141,7 @@ static EFI_STATUS combine_initrds(
|
|||||||
return EFI_SUCCESS;
|
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 =
|
static const uint64_t stub_features =
|
||||||
EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */
|
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_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_ADDONS | /* We pick up .cmdline addons */
|
||||||
EFI_STUB_FEATURE_CMDLINE_SMBIOS | /* We support extending kernel cmdline from SMBIOS Type #11 */
|
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_DEVICETREE_ADDONS | /* We pick up .dtb addons */
|
||||||
|
EFI_STUB_FEATURE_MULTI_PROFILE_UKI | /* We grok the "@1" profile command line argument */
|
||||||
0;
|
0;
|
||||||
|
|
||||||
assert(loaded_image);
|
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_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_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_HANDLE stub_image,
|
||||||
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
|
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
|
||||||
bool have_cmdline,
|
unsigned *ret_profile,
|
||||||
char16_t **ret) {
|
char16_t **ret_cmdline) {
|
||||||
|
|
||||||
assert(stub_image);
|
assert(stub_image);
|
||||||
assert(loaded_image);
|
assert(loaded_image);
|
||||||
assert(ret);
|
assert(ret_profile);
|
||||||
|
assert(ret_cmdline);
|
||||||
/* 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;
|
|
||||||
|
|
||||||
/* The UEFI shell registers EFI_SHELL_PARAMETERS_PROTOCOL onto images it runs. This lets us know that
|
/* 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. */
|
* 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
|
/* 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?). */
|
* 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)
|
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
|
/* Not running from EFI shell, use entire LoadOptions. Note that LoadOptions is a void*, so
|
||||||
* it could be anything! */
|
* it could actually be anything! */
|
||||||
*ret = mangle_stub_cmdline(xstrndup16(loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t)));
|
char16_t *c = xstrndup16(loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t));
|
||||||
return true;
|
parse_profile_from_cmdline(&c, ret_profile);
|
||||||
|
*ret_cmdline = mangle_stub_cmdline(c);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shell->Argc < 2)
|
if (shell->Argc <= 1) /* No arguments were provided? Then we fall back to built-in cmdline. */
|
||||||
/* No arguments were provided? Then we fall back to built-in cmdline. */
|
goto nothing;
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Assemble the command line ourselves without our stub path. */
|
size_t i = 1;
|
||||||
*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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
static EFI_STATUS load_addons_from_dir(
|
||||||
@ -734,43 +804,53 @@ static void generate_embedded_initrds(
|
|||||||
const PeSectionVector sections[static _UNIFIED_SECTION_MAX],
|
const PeSectionVector sections[static _UNIFIED_SECTION_MAX],
|
||||||
struct iovec initrds[static _INITRD_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(loaded_image);
|
||||||
assert(initrds);
|
assert(initrds);
|
||||||
|
|
||||||
/* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it
|
FOREACH_ELEMENT(t, table) {
|
||||||
* to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section
|
if (!PE_SECTION_VECTOR_IS_SET(sections + t->section))
|
||||||
* is not measured, neither as raw section (see above), nor as cpio (here), because it is the
|
continue;
|
||||||
* 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);
|
|
||||||
|
|
||||||
/* 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(
|
(void) pack_cpio_literal(
|
||||||
(const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_PCRPKEY].memory_offset,
|
(const uint8_t*) loaded_image->ImageBase + sections[t->section].memory_offset,
|
||||||
sections[UNIFIED_SECTION_PCRPKEY].size,
|
sections[t->section].size,
|
||||||
".extra",
|
".extra",
|
||||||
u"tpm2-pcr-public-key.pem",
|
t->filename,
|
||||||
/* dir_mode= */ 0555,
|
/* dir_mode= */ 0555,
|
||||||
/* access_mode= */ 0444,
|
/* access_mode= */ 0444,
|
||||||
/* tpm_pcr= */ UINT32_MAX,
|
/* tpm_pcr= */ UINT32_MAX,
|
||||||
/* tpm_description= */ NULL,
|
/* tpm_description= */ NULL,
|
||||||
initrds + INITRD_PCRPKEY,
|
initrds + t->initrd_index,
|
||||||
/* ret_measured= */ NULL);
|
/* ret_measured= */ NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lookup_embedded_initrds(
|
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);
|
graphics_splash((const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_SPLASH].memory_offset, sections[UNIFIED_SECTION_SPLASH].size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void determine_cmdline(
|
static EFI_STATUS find_sections(
|
||||||
EFI_HANDLE image,
|
|
||||||
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
|
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
|
||||||
const PeSectionVector sections[static _UNIFIED_SECTION_MAX],
|
unsigned profile,
|
||||||
char16_t **ret_cmdline,
|
PeSectionVector sections[static _UNIFIED_SECTION_MAX]) {
|
||||||
int *parameters_measured) {
|
|
||||||
|
EFI_STATUS err;
|
||||||
|
|
||||||
assert(loaded_image);
|
assert(loaded_image);
|
||||||
assert(sections);
|
assert(sections);
|
||||||
|
|
||||||
if (use_load_options(image, loaded_image, /* have_cmdline= */ PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE), ret_cmdline)) {
|
const PeSectionHeader *section_table;
|
||||||
/* Let's measure the passed kernel command line into the TPM. Note that this possibly
|
size_t n_section_table;
|
||||||
* duplicates what we already did in the boot menu, if that was already used. However, since
|
err = pe_section_table_from_base(loaded_image->ImageBase, §ion_table, &n_section_table);
|
||||||
* we want the boot menu to support an EFI binary, and want to this stub to be usable from
|
if (err != EFI_SUCCESS)
|
||||||
* any boot menu, let's measure things anyway. */
|
return log_error_status(err, "Unable to locate PE section table: %m");
|
||||||
bool m = false;
|
|
||||||
(void) tpm_log_load_options(*ret_cmdline, &m);
|
/* Get the base sections */
|
||||||
combine_measured_flag(parameters_measured, m);
|
err = pe_locate_profile_sections(
|
||||||
} else
|
section_table,
|
||||||
*ret_cmdline = mangle_stub_cmdline(pe_section_to_str16(loaded_image, sections + UNIFIED_SECTION_CMDLINE));
|
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) {
|
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;
|
size_t n_dt_addons = 0, n_ucode_addons = 0;
|
||||||
_cleanup_free_ struct iovec *all_initrds = NULL;
|
_cleanup_free_ struct iovec *all_initrds = NULL;
|
||||||
size_t n_all_initrds = 0;
|
size_t n_all_initrds = 0;
|
||||||
|
unsigned profile = 0;
|
||||||
EFI_STATUS err;
|
EFI_STATUS err;
|
||||||
|
|
||||||
err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
|
err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
|
||||||
if (err != EFI_SUCCESS)
|
if (err != EFI_SUCCESS)
|
||||||
return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
|
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
|
||||||
if (err != EFI_SUCCESS)
|
* as potential command line to use. */
|
||||||
return log_error_status(err, "Unable to locate embedded PE sections: %m");
|
(void) process_arguments(image, loaded_image, &profile, &cmdline);
|
||||||
if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_LINUX))
|
|
||||||
return log_error_status(EFI_NOT_FOUND, "Image lacks .linux section.");
|
|
||||||
|
|
||||||
|
/* 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);
|
measure_sections(loaded_image, sections, §ions_measured);
|
||||||
|
|
||||||
/* Show splash screen as early as possible, but after measuring it */
|
/* 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);
|
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)
|
/* 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. */
|
* 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);
|
cmdline_append_and_measure_smbios(&cmdline, ¶meters_measured);
|
||||||
|
|
||||||
export_common_variables(loaded_image);
|
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. */
|
/* 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);
|
install_embedded_devicetree(loaded_image, sections, &dt_state);
|
||||||
|
@ -492,3 +492,24 @@ void *xmalloc(size_t size) {
|
|||||||
assert_se(BS->AllocatePool(EfiLoaderData, size, &p) == EFI_SUCCESS);
|
assert_se(BS->AllocatePool(EfiLoaderData, size, &p) == EFI_SUCCESS);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool free_and_xstrdup16(char16_t **p, const char16_t *s) {
|
||||||
|
char16_t *t;
|
||||||
|
|
||||||
|
assert(p);
|
||||||
|
|
||||||
|
/* Replaces a string pointer with a strdup()ed new string,
|
||||||
|
* possibly freeing the old one. */
|
||||||
|
|
||||||
|
if (streq_ptr(*p, s))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (s)
|
||||||
|
t = xstrdup16(s);
|
||||||
|
else
|
||||||
|
t = NULL;
|
||||||
|
|
||||||
|
free(*p);
|
||||||
|
*p = t;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -55,6 +55,8 @@ static inline void* xmemdup(const void *p, size_t l) {
|
|||||||
|
|
||||||
#define xnew(type, n) ((type *) xmalloc_multiply((n), sizeof(type)))
|
#define xnew(type, n) ((type *) xmalloc_multiply((n), sizeof(type)))
|
||||||
|
|
||||||
|
bool free_and_xstrdup16(char16_t **p, const char16_t *s);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
EFI_PHYSICAL_ADDRESS addr;
|
EFI_PHYSICAL_ADDRESS addr;
|
||||||
size_t n_pages;
|
size_t n_pages;
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#define EFI_STUB_FEATURE_CMDLINE_SMBIOS (UINT64_C(1) << 6)
|
#define EFI_STUB_FEATURE_CMDLINE_SMBIOS (UINT64_C(1) << 6)
|
||||||
#define EFI_STUB_FEATURE_DEVICETREE_ADDONS (UINT64_C(1) << 7)
|
#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_PICK_UP_CONFEXTS (UINT64_C(1) << 8)
|
||||||
|
#define EFI_STUB_FEATURE_MULTI_PROFILE_UKI (UINT64_C(1) << 9)
|
||||||
|
|
||||||
typedef enum SecureBootMode {
|
typedef enum SecureBootMode {
|
||||||
SECURE_BOOT_UNSUPPORTED,
|
SECURE_BOOT_UNSUPPORTED,
|
||||||
|
@ -52,3 +52,6 @@ enum {
|
|||||||
|
|
||||||
/* The tag used for EV_EVENT_TAG event log records covering ucode addons (effectively initrds) */
|
/* The tag used for EV_EVENT_TAG event log records covering ucode addons (effectively initrds) */
|
||||||
#define UCODE_ADDON_EVENT_TAG_ID UINT32_C(0xdac08e1a)
|
#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