efi: from the stub measure the ELF kernel + built-in initrd and so on into PCR 11

Here we grab a new – on Linux so far unused (by my Googling skills, that
is) – and measure all static components of the PE kernel image into.
This is useful since for the first time we'll have a PCR that contains
only a PCR of the booted kernel, nothing else. That allows putting
together TPM policies that bind to a specific kernel (+ builtin initrd),
without having to have booted that kernel first. PCRs can be
pre-calculated. Yay!

You might wonder, why we measure just the discovered PE sections we are
about to use, instead of the whole PE image. That's because of the next
step I have in mind: PE images should also be able to carry an
additional section that contains a signature for its own expected,
pre-calculated PCR values. This signature data should then be passed
into the booted kernel and can be used there in TPM policies. Benefit:
TPM policies can now be bound to *signatures* of PCRs, instead of the
raw hash values themselves. This makes update management a *lot* easier,
as policies don't need to be updated whenever a kernel is updated, as
long as the signature is available. Now, if the PCR signature is
embedded in the kernel PE image it cannot be of a PCR hash of the kernel
PE image itself, because that would be a chicken-and-egg problem. Hence,
by only measuring the relavent payload sections (and that means
excluding the future section that will contain the PCR hash signature)
we avoid this problem, naturally.
This commit is contained in:
Lennart Poettering 2022-07-25 17:44:24 +02:00
parent 599fe002a1
commit 72c97c19c3
4 changed files with 72 additions and 10 deletions

View file

@ -296,6 +296,11 @@
<entry>The IMA project measures its runtime state into this PCR.</entry>
</row>
<row>
<entry>11</entry>
<entry><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures the ELF kernel image, embedded initrd and other payload of the PE image it is placed in into this PCR. Unlike PCR 4 (where the same data should be measured into), this PCR value should be easy to pre-calculate, as this only contains static parts of the PE binary. Use this PCR to bind TPM policies to a specific kernel image, possibly with an embedded initial RAM disk (initrd).</entry>
</row>
<row>
<entry>12</entry>
<entry><citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any specified kernel command line into this PCR. <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. (Note that if <command>systemd-boot</command> and <command>systemd-stub</command> are used in combination the command line might be measured twice!)</entry>

View file

@ -53,6 +53,10 @@
<listitem><para>The ELF Linux kernel images will be looked for in the <literal>.linux</literal> PE
section of the executed image.</para></listitem>
<listitem><para>OS release information, i.e. the
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> file of
the OS the kernel belongs to, in the <literal>.osrel</literal> PE section.</para></listitem>
<listitem><para>The initial RAM disk (initrd) will be looked for in the <literal>.initrd</literal> PE
section.</para></listitem>
@ -76,6 +80,9 @@
<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 these six PE sections are measured into TPM PCR 11, that is otherwise not
used. Thus, it can be pre-calculated without too much effort.</para>
</refsect1>
<refsect1>
@ -133,10 +140,10 @@
core kernel, the embedded initrd and kernel command line (see above for a full list).</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 twice: the initrd embedded in the kernel image will be measured to
both PCR 4 and PCR 9; the initrd synthesized from credentials will be measured to both PCR 12 and PCR 9;
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 will be measured two or three times: the initrd embedded in the kernel image will be
measured to PCR 4, PCR 9 and PCR 11; the initrd synthesized from credentials 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>
@ -160,22 +167,22 @@
<row>
<entry>Boot splash (embedded in the unified PE binary)</entry>
<entry>4</entry>
<entry>4 + 11</entry>
</row>
<row>
<entry>Core kernel code (embedded in unified PE binary)</entry>
<entry>4</entry>
<entry>4 + 11</entry>
</row>
<row>
<entry>Main initrd (embedded in unified PE binary)</entry>
<entry>4 + 9</entry>
<entry>4 + 9 + 11</entry>
</row>
<row>
<entry>Default kernel command line (embedded in unified PE binary)</entry>
<entry>4</entry>
<entry>4 + 11</entry>
</row>
<row>
@ -240,6 +247,15 @@
this data.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>StubPcrKernelImage</varname></term>
<listitem><para>The PCR register index the ELF kernel image/initial RAM disk image/boot
splash/devicetree database/embedded command line are measured into, formatted as decimal ASCII string
(i.e. <literal>11</literal>). This variable is set if a measurement was successfully completed, and
remains unset otherwise.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>StubPcrKernelParameters</varname></term>

View file

@ -5,6 +5,12 @@
#include <stdbool.h>
#include <uchar.h>
/* This TPM PCR is where we extend the sd-stub "payloads" into, before using them. i.e. the kernel ELF image,
* embedded initrd, and so on. In contrast to PCR 4 (which also contains this data, given the whole
* surrounding PE image is measured into it) this should be reasonably pre-calculatable, because it *only*
* consists of static data from the kernel PE image. */
#define TPM_PCR_INDEX_KERNEL_IMAGE 11U
/* This TPM PCR is where sd-stub extends the kernel command line and any passed credentials into. */
#define TPM_PCR_INDEX_KERNEL_PARAMETERS 12U

View file

@ -149,7 +149,7 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
enum {
enum Section {
SECTION_CMDLINE,
SECTION_LINUX,
SECTION_INITRD,
@ -178,7 +178,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
UINTN szs[_SECTION_MAX] = {};
char *cmdline = NULL;
_cleanup_free_ char *cmdline_owned = NULL;
int parameters_measured = -1;
int sections_measured = -1, parameters_measured = -1;
EFI_STATUS err;
bool m;
@ -204,6 +204,41 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
return log_error_status_stall(err, L"Unable to locate embedded .linux section: %r", err);
}
/* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written
* into so far), so that we have one PCR that we can nicely write policies against because it
* contains all static data of this image, and thus can be easily be pre-calculated. */
for (enum Section section = 0; section < _SECTION_MAX; section++) {
m = false;
if (szs[section] == 0) /* not found */
continue;
/* First measure the name of the section */
(void) tpm_log_event_ascii(
TPM_PCR_INDEX_KERNEL_IMAGE,
POINTER_TO_PHYSICAL_ADDRESS(sections[section]),
strsize8(sections[section]), /* including NUL byte */
sections[section],
&m);
sections_measured = sections_measured < 0 ? m : (sections_measured && m);
/* Then measure the data of the section */
(void) tpm_log_event_ascii(
TPM_PCR_INDEX_KERNEL_IMAGE,
POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section],
szs[section],
sections[section],
&m);
sections_measured = sections_measured < 0 ? m : (sections_measured && m);
}
/* After we are done, set an EFI variable that tells userspace this was done successfully, and encode
* in it which PCR was used. */
if (sections_measured > 0)
(void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelImage", TPM_PCR_INDEX_KERNEL_IMAGE, 0);
/* Show splash screen as early as possible */
graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[SECTION_SPLASH], szs[SECTION_SPLASH], NULL);