Merge pull request #24146 from poettering/efi-stub-measure-payload

stub: measure kernel/initrd/parameters into clean PCRs 11/12/13, and add "systemd-measure" tool to be able to pre-calculate values
This commit is contained in:
Luca Boccassi 2022-08-02 14:42:33 +01:00 committed by GitHub
commit 675a644de2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1239 additions and 175 deletions

27
TODO
View file

@ -119,6 +119,14 @@ Features:
on other disks. Always boot into them via NextBoot EFI variable, to not
affect PCR values.
* systemd-measure tool:
- pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11
- sign pre-calculated hashes in a way compatible with TPM2 PCR hash signature
policies, in a way they can be included in unified PE kernel images, and
made available to userspace. There, this should be consumed by
systemd-cryptsetup to implement PCR signature based TPM volume unlock
policies.
* in sd-boot: load EFI drivers from a new PE section. That way, one can have a
"supercharged" sd-boot binary, that could carry ext4 drivers built-in.
@ -385,12 +393,6 @@ Features:
case the same wd is reused multiple times before we start processing
IN_IGNORED again)
* sd-stub: set efi var indicating stub features, i.e. whether they pick up
creds, sysexts and so on. similar to existing variable of sd-boot
* sd-stub: set efi vars declaring TPM PCRs we measured creds/cmdline + sysext
into (even if we hardcode them)
* systemd-fstab-generator: support addition mount specifications via kernel
cmdline. Usecase: invoke a VM, and mount a host homedir into it via
virtio-fs.
@ -413,10 +415,6 @@ Features:
- sd-stub: automatically pick up microcode from ESP (/loader/microcode/*)
and synthesize initrd from it, and measure it. Signing is not necessary, as
microcode does that on its own. Pass as first initrd to kernel.
- sd-stub should measure the kernel/initrd/… into a separate PCR, so that we
have one PCR we can bind the encrypted creds to that is not effected by
anything else but what we drop in via kernel-install, i.e. by earlier EFI
code running (i.e. like PCR 4)
* Add a new service type very similar to Type=notify, that goes one step
further and extends the protocol to cover reloads. Specifically, SIGHUP will
@ -660,7 +658,7 @@ Features:
dep in the base OS image)
* sysext: automatically activate sysext images dropped in via new sd-stub
sysext pickup logic.
sysext pickup logic. (must insist on verity + signature on those though)
* add concept for "exitrd" as inverse of "initrd", that we can transition to at
shutdown, and has similar security semantics. This should then take the place
@ -708,9 +706,9 @@ Features:
what must be read-only, what requires encryption, and what requires
authentication.
* in uefi stub: query firmware regarding which PCRs are being used, store that
in EFI var. then use this when enrolling TPM2 in cryptsetup to verify that
the selected PCRs actually are used by firmware.
* in uefi stub: query firmware regarding which PCR banks are being used, store
that in EFI var. then use this when enrolling TPM2 in cryptsetup to verify
that the selected PCRs actually are used by firmware.
* rework recursive read-only remount to use new mount API
@ -1606,7 +1604,6 @@ Features:
- show whether UEFI audit mode is available
- teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation
- teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host
- make it operate on loopback files, dissecting enough to find ESP to operate on
- bootspec: properly support boot attempt counters when parsing entry file names
* kernel-install:

View file

@ -953,6 +953,7 @@ manpages = [
'systemd-makefs',
'systemd-mkswap@.service'],
''],
['systemd-measure', '1', [], 'HAVE_GNU_EFI'],
['systemd-modules-load.service', '8', ['systemd-modules-load'], 'HAVE_KMOD'],
['systemd-mount', '1', ['systemd-umount'], ''],
['systemd-network-generator.service', '8', ['systemd-network-generator'], ''],

View file

@ -296,11 +296,21 @@
<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>
</row>
<row>
<entry>13</entry>
<entry><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> measures any <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry> images it loads and passed to the booted kernel into this PCR.</entry>
</row>
<row>
<entry>14</entry>
<entry>The shim project measures its "MOK" certificates and hashes into this PCR.</entry>

154
man/systemd-measure.xml Normal file
View file

@ -0,0 +1,154 @@
<?xml version="1.0"?>
<!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-measure" xmlns:xi="http://www.w3.org/2001/XInclude" conditional='HAVE_GNU_EFI'>
<refentryinfo>
<title>systemd-measure</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-measure</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-measure</refname>
<refpurpose>Pre-calculate expected TPM2 PCR values for booted unified kernel images</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>/usr/lib/systemd/systemd-measure <arg choice="opt" rep="repeat">OPTIONS</arg></command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>Note: this command is experimental for now. While it is likely to become a regular component of
systemd, it might still change in behaviour and interface.</para>
<para><command>systemd-measure</command> is a tool that may be used to pre-calculate the expected TPM2
PCR 11 values that should be seen when a unified Linux kernel image based on
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> is
booted up. It accepts paths to the ELF kernel image file, initial ram disk image file, devicetree file,
kernel command line file,
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> file, and
boot splash file that make up the unified kernel image, and determines the PCR values expected to be in
place after booting the image. Calculation starts with a zero-initialized PCR 11, and is executed in a
fashion compatible with what <filename>systemd-stub</filename> does at boot.</para>
</refsect1>
<refsect1>
<title>Commands</title>
<para>The following commands are understood:</para>
<variablelist>
<varlistentry>
<term><command>status</command></term>
<listitem><para>This is the default command if none is specified. This queries the local system's
TPM2 PCR 11+12+13 values and displays them. The data is written in a similar format as the
<command>calculate</command> command below, and may be used to quickly compare expectation with
reality.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>calculate</command></term>
<listitem><para>Pre-calculate the expected value seen in PCR register 11 after boot-up of a unified
kernel image consisting of the components specified with <option>--linux=</option>,
<option>--osrel=</option>, <option>--cmdline=</option>, <option>--initrd=</option>,
<option>--splash=</option>, <option>--dtb=</option>, see below. Only <option>--linux=</option> is
mandatory.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>--linux=PATH</option></term>
<term><option>--osrel=PATH</option></term>
<term><option>--cmdline=PATH</option></term>
<term><option>--initrd=PATH</option></term>
<term><option>--splash=PATH</option></term>
<term><option>--dtb=PATH</option></term>
<listitem><para>When used with the <command>calculate</command> verb, configures the files to read
the unified kernel image components from. Each option corresponds with the equally named section in
the unified kernel PE file. The <option>--linux=</option> switch expects the path to the ELF kernel
file that the unified PE kernel will wrap. All switches except <option>--linux=</option> are
optional. Each option may be used at most once.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--bank=DIGEST</option></term>
<listitem><para>Controls the PCR banks to pre-calculate the PCR values for in case
<command>calculate</command> is invoked , or the banks to show in the <command>status</command>
output. May be used more then once to specify multiple banks. If not specified, defaults to the four
banks <literal>sha1</literal>, <literal>sha256</literal>, <literal>sha384</literal>,
<literal>sha512</literal>.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<example>
<title>Generate a unified kernel image, and calculate the expected TPM PCR 11 value</title>
<programlisting># objcopy \
--add-section .linux=vmlinux --change-section-vma .linux=0x2000000 \
--add-section .osrel=os-release.txt --change-section-vma .osrel=0x20000 \
--add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x30000 \
--add-section .initrd=initrd.cpio --change-section-vma .initrd=0x3000000 \
--add-section .splash=splash.bmp --change-section-vma .splash=0x100000 \
--add-section .dtb=devicetree.dtb --change-section-vma .dtb=0x40000 \
/usr/lib/systemd/boot/efi/linuxx64.efi.stub \
foo.efi
# systemd-measure calculate \
--linux=vmlinux \
--osrel=os-release \
--cmdline=cmdline.txt \
--initrd=initrd.cpio \
--splash=splash.bmp \
--dtb=devicetree.dtb
11:sha1=d775a7b4482450ac77e03ee19bda90bd792d6ec7
11:sha256=bc6170f9ce28eb051ab465cd62be8cf63985276766cf9faf527ffefb66f45651
11:sha384=1cf67dff4757e61e5a73d2a21a6694d668629bbc3761747d493f7f49ad720be02fd07263e1f93061243aec599d1ee4b4
11:sha512=8e79acd3ddbbc8282e98091849c3530f996303c8ac8e87a3b2378b71c8b3a6e86d5c4f41ecea9e1517090c3e8ec0c714821032038f525f744960bcd082d937da
</programlisting>
</example>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

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>
@ -108,7 +115,7 @@
images to the initrd. See
<citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry> for
details on system extension images. The generated <command>cpio</command> archive containing these
system extension images is measured into TPM PCR 4 (if a TPM is present).</para></listitem>
system extension images is measured into TPM PCR 13 (if a TPM is present).</para></listitem>
<listitem><para>Files <filename>/loader/credentials/*.cred</filename> are packed up in a
<command>cpio</command> archive and placed in the <filename>/.extra/global_credentials/</filename>
@ -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>
@ -185,12 +192,12 @@
<row>
<entry>Credentials (synthesized initrd from companion files)</entry>
<entry>12 + 9</entry>
<entry>9 + 12</entry>
</row>
<row>
<entry>System Extensions (synthesized initrd from companion files)</entry>
<entry>4 + 9</entry>
<entry>9 + 13</entry>
</row>
</tbody>
</tgroup>
@ -239,6 +246,32 @@
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> to view
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>
<listitem><para>The PCR register index the kernel command line and credentials are measured into,
formatted as decimal ASCII string (i.e. <literal>12</literal>). This variable is set if a measurement
was successfully completed, and remains unset otherwise.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>StubPcrInitRDSysExts</varname></term>
<listitem><para>The PCR register index the systemd extensions for the initial RAM disk image, which
are picked up from the file system the kernel image is located on. Formatted as decimal ASCII string
(i.e. <literal>13</literal>). This variable is set if a measurement was successfully completed, and
remains unset otherwise.</para></listitem>
</varlistentry>
</variablelist>
<para>Note that some of the variables above may also be set by the boot loader. The stub will only set

View file

@ -310,6 +310,8 @@ conf.set_quoted('STATUS_UNIT_FORMAT_DEFAULT_STR', status_unit_format
conf.set10('FIRST_BOOT_FULL_PRESET', get_option('first-boot-full-preset'))
conf.set10('EFI_TPM_PCR_COMPAT', get_option('efi-tpm-pcr-compat'))
#####################################################################
cc = meson.get_compiler('c')
@ -2539,6 +2541,18 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1
install_rpath : rootpkglibdir,
install : true,
install_dir : systemgeneratordir)
if conf.get('HAVE_OPENSSL') == 1
executable(
'systemd-measure',
'src/boot/measure.c',
include_directories : includes,
link_with : [libshared],
dependencies : [libopenssl],
install_rpath : rootpkglibdir,
install : true,
install_dir : rootlibexecdir)
endif
endif
executable(

View file

@ -456,13 +456,21 @@ static const char *get_efi_arch(void) {
return EFI_MACHINE_TYPE_NAME;
}
static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) {
static int enumerate_binaries(
const char *esp_path,
const char *path,
const char *prefix,
char **previous,
bool *is_first) {
_cleanup_closedir_ DIR *d = NULL;
const char *p;
int c = 0, r;
assert(esp_path);
assert(path);
assert(previous);
assert(is_first);
p = prefix_roota(esp_path, path);
d = opendir(p);
@ -490,10 +498,24 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char
r = get_file_version(fd, &v);
if (r < 0)
return r;
if (*previous) { /* let's output the previous entry now, since now we know that there will be one more, and can draw the tree glyph properly */
printf(" %s %s%s\n",
*is_first ? "File:" : " ",
special_glyph(SPECIAL_GLYPH_TREE_BRANCH), *previous);
*is_first = false;
*previous = mfree(*previous);
}
/* Do not output this entry immediately, but store what should be printed in a state
* variable, because we only will know the tree glyph to print (branch or final edge) once we
* read one more entry */
if (r > 0)
printf(" File: %s/%s/%s (%s%s%s)\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name, ansi_highlight(), v, ansi_normal());
r = asprintf(previous, "/%s/%s (%s%s%s)", path, de->d_name, ansi_highlight(), v, ansi_normal());
else
printf(" File: %s/%s/%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name);
r = asprintf(previous, "/%s/%s", path, de->d_name);
if (r < 0)
return log_oom();
c++;
}
@ -502,9 +524,11 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char
}
static int status_binaries(const char *esp_path, sd_id128_t partition) {
int r;
_cleanup_free_ char *last = NULL;
bool is_first = true;
int r, k;
printf("Available Boot Loaders on ESP:\n");
printf("%sAvailable Boot Loaders on ESP:%s\n", ansi_underline(), ansi_normal());
if (!esp_path) {
printf(" ESP: Cannot find or access mount point of ESP.\n\n");
@ -516,42 +540,56 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) {
printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", SD_ID128_FORMAT_VAL(partition));
printf("\n");
r = enumerate_binaries(esp_path, "EFI/systemd", NULL);
if (r < 0)
goto finish;
r = enumerate_binaries(esp_path, "EFI/systemd", NULL, &last, &is_first);
if (r < 0) {
printf("\n");
return r;
}
k = enumerate_binaries(esp_path, "EFI/BOOT", "boot", &last, &is_first);
if (k < 0) {
printf("\n");
return k;
}
if (last) /* let's output the last entry now, since now we know that there will be no more, and can draw the tree glyph properly */
printf(" %s %s%s\n",
is_first ? "File:" : " ",
special_glyph(SPECIAL_GLYPH_TREE_RIGHT), last);
if (r == 0 && !arg_quiet)
log_info("systemd-boot not installed in ESP.");
r = enumerate_binaries(esp_path, "EFI/BOOT", "boot");
if (r < 0)
goto finish;
if (r == 0 && !arg_quiet)
if (k == 0 && !arg_quiet)
log_info("No default/fallback boot loader installed in ESP.");
r = 0;
finish:
printf("\n");
return r;
return 0;
}
static int print_efi_option(uint16_t id, bool in_order) {
static int print_efi_option(uint16_t id, int *n_printed, bool in_order) {
_cleanup_free_ char *title = NULL;
_cleanup_free_ char *path = NULL;
sd_id128_t partition;
bool active;
int r;
assert(n_printed);
r = efi_get_boot_option(id, &title, &partition, &path, &active);
if (r < 0)
return r;
return log_error_errno(r, "Failed to read boot option %u: %m", id);
/* print only configured entries with partition information */
if (!path || sd_id128_is_null(partition))
if (!path || sd_id128_is_null(partition)) {
log_debug("Ignoring boot entry %u without partition information.", id);
return 0;
}
efi_tilt_backslashes(path);
if (*n_printed == 0) /* Print section title before first entry */
printf("%sBoot Loaders Listed in EFI Variables:%s\n", ansi_underline(), ansi_normal());
printf(" Title: %s%s%s\n", ansi_highlight(), strna(title), ansi_normal());
printf(" ID: 0x%04X\n", id);
printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : "");
@ -560,12 +598,13 @@ static int print_efi_option(uint16_t id, bool in_order) {
printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path);
printf("\n");
return 0;
(*n_printed)++;
return 1;
}
static int status_variables(void) {
_cleanup_free_ uint16_t *options = NULL, *order = NULL;
int n_options, n_order;
int n_options, n_order, n_printed = 0;
n_options = efi_get_boot_options(&options);
if (n_options == -ENOENT)
@ -582,9 +621,8 @@ static int status_variables(void) {
return log_error_errno(n_order, "Failed to read EFI boot order: %m");
/* print entries in BootOrder first */
printf("Boot Loaders Listed in EFI Variables:\n");
for (int i = 0; i < n_order; i++)
print_efi_option(order[i], true);
(void) print_efi_option(order[i], &n_printed, /* in_order= */ true);
/* print remaining entries */
for (int i = 0; i < n_options; i++) {
@ -592,12 +630,15 @@ static int status_variables(void) {
if (options[i] == order[j])
goto next_option;
print_efi_option(options[i], false);
(void) print_efi_option(options[i], &n_printed, /* in_order= */ false);
next_option:
continue;
}
if (n_printed == 0)
printf("No boot loaders listed in EFI Variables.\n\n");
return 0;
}
@ -655,8 +696,8 @@ static int status_entries(
dollar_boot_partition_uuid = esp_partition_uuid;
}
printf("Boot Loader Entries:\n"
" $BOOT: %s", dollar_boot_path);
printf("%sBoot Loader Entries:%s\n"
" $BOOT: %s", ansi_underline(), ansi_normal(), dollar_boot_path);
if (!sd_id128_is_null(dollar_boot_partition_uuid))
printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")",
SD_ID128_FORMAT_VAL(dollar_boot_partition_uuid));
@ -665,7 +706,7 @@ static int status_entries(
if (config->default_entry < 0)
printf("%zu entries, no entry could be determined as default.\n", config->n_entries);
else {
printf("Default Boot Loader Entry:\n");
printf("%sDefault Boot Loader Entry:%s\n", ansi_underline(), ansi_normal());
r = show_boot_entry(
boot_config_default_entry(config),
@ -1714,7 +1755,7 @@ static int verb_status(int argc, char *argv[], void *userdata) {
static const struct {
uint64_t flag;
const char *name;
} flags[] = {
} loader_flags[] = {
{ EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" },
{ EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" },
{ EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" },
@ -1723,10 +1764,22 @@ static int verb_status(int argc, char *argv[], void *userdata) {
{ EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" },
{ EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" },
{ EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" },
{ EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" },
{ EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" },
{ EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" },
};
static const struct {
uint64_t flag;
const char *name;
} stub_flags[] = {
{ EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets ESP information" },
{ EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" },
{ EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" },
{ EFI_STUB_FEATURE_THREE_PCRS, "Measures kernel+command line+sysexts" },
};
_cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL;
sd_id128_t loader_part_uuid = SD_ID128_NULL;
uint64_t loader_features = 0;
uint64_t loader_features = 0, stub_features = 0;
Tpm2Support s;
int have;
@ -1736,6 +1789,7 @@ static int verb_status(int argc, char *argv[], void *userdata) {
read_efi_var(EFI_LOADER_VARIABLE(StubInfo), &stub);
read_efi_var(EFI_LOADER_VARIABLE(LoaderImageIdentifier), &loader_path);
(void) efi_loader_get_features(&loader_features);
(void) efi_stub_get_features(&stub_features);
if (loader_path)
efi_tilt_backslashes(loader_path);
@ -1745,7 +1799,7 @@ static int verb_status(int argc, char *argv[], void *userdata) {
r = log_warning_errno(k, "Failed to read EFI variable LoaderDevicePartUUID: %m");
SecureBootMode secure = efi_get_secure_boot_mode();
printf("System:\n");
printf("%sSystem:%s\n", ansi_underline(), ansi_normal());
printf(" Firmware: %s%s (%s)%s\n", ansi_highlight(), strna(fw_type), strna(fw_info), ansi_normal());
printf(" Firmware Arch: %s\n", get_efi_arch());
printf(" Secure Boot: %sd (%s)\n",
@ -1774,11 +1828,11 @@ static int verb_status(int argc, char *argv[], void *userdata) {
}
printf("\n");
printf("Current Boot Loader:\n");
printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal());
printf(" Product: %s%s%s\n", ansi_highlight(), strna(loader), ansi_normal());
for (size_t i = 0; i < ELEMENTSOF(flags); i++)
print_yes_no_line(i == 0, FLAGS_SET(loader_features, flags[i].flag), flags[i].name);
for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++)
print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name);
sd_id128_t bootloader_esp_uuid;
bool have_bootloader_esp_uuid = efi_loader_get_device_part_uuid(&bootloader_esp_uuid) >= 0;
@ -1787,8 +1841,11 @@ static int verb_status(int argc, char *argv[], void *userdata) {
if (have_bootloader_esp_uuid && !sd_id128_equal(esp_uuid, bootloader_esp_uuid))
printf("WARNING: The boot loader reports a different ESP UUID than detected!\n");
if (stub)
if (stub) {
printf(" Stub: %s\n", stub);
for (size_t i = 0; i < ELEMENTSOF(stub_flags); i++)
print_yes_no_line(i == 0, FLAGS_SET(stub_features, stub_flags[i].flag), stub_flags[i].name);
}
if (!sd_id128_is_null(loader_part_uuid))
printf(" ESP: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n",
SD_ID128_FORMAT_VAL(loader_part_uuid));
@ -1797,7 +1854,7 @@ static int verb_status(int argc, char *argv[], void *userdata) {
printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), strna(loader_path));
printf("\n");
printf("Random Seed:\n");
printf("%sRandom Seed:%s\n", ansi_underline(), ansi_normal());
have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderRandomSeed)), F_OK) >= 0;
printf(" Passed to OS: %s\n", yes_no(have));
have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), F_OK) >= 0;
@ -1816,7 +1873,9 @@ static int verb_status(int argc, char *argv[], void *userdata) {
printf("\n");
} else
printf("System:\n Not booted with EFI\n\n");
printf("%sSystem:%s\n"
"Not booted with EFI\n\n",
ansi_underline(), ansi_normal());
if (arg_esp_path) {
k = status_binaries(arg_esp_path, esp_uuid);

View file

@ -2339,7 +2339,7 @@ static EFI_STATUS image_start(
loaded_image->LoadOptionsSize = strsize16(options);
/* Try to log any options to the TPM, especially to catch manually edited options */
(void) tpm_log_load_options(options);
(void) tpm_log_load_options(options, NULL);
}
efivar_set_time_usec(LOADER_GUID, L"LoaderTimeExecUSec", 0);
@ -2443,6 +2443,9 @@ static void export_variables(
EFI_LOADER_FEATURE_XBOOTLDR |
EFI_LOADER_FEATURE_RANDOM_SEED |
EFI_LOADER_FEATURE_LOAD_DRIVER |
EFI_LOADER_FEATURE_SORT_KEY |
EFI_LOADER_FEATURE_SAVED_ENTRY |
EFI_LOADER_FEATURE_DEVICETREE |
0;
_cleanup_free_ char16_t *infostr = NULL, *typestr = NULL;

View file

@ -315,7 +315,8 @@ EFI_STATUS pack_cpio(
UINTN n_tpm_pcr,
const char16_t *tpm_description,
void **ret_buffer,
UINTN *ret_buffer_size) {
UINTN *ret_buffer_size,
bool *ret_measured) {
_cleanup_(file_closep) EFI_FILE *root = NULL, *extra_dir = NULL;
UINTN dirent_size = 0, buffer_size = 0, n_items = 0, n_allocated = 0;
@ -324,6 +325,7 @@ EFI_STATUS pack_cpio(
_cleanup_(strv_freep) char16_t **items = NULL;
_cleanup_free_ void *buffer = NULL;
uint32_t inode = 1; /* inode counter, so that each item gets a new inode */
int measured = -1;
EFI_STATUS err;
assert(loaded_image);
@ -432,23 +434,40 @@ EFI_STATUS pack_cpio(
return log_error_status_stall(err, L"Failed to pack cpio trailer: %r");
for (UINTN i = 0; i < n_tpm_pcr; i++) {
bool m;
if (tpm_pcr[i] == UINT32_MAX) /* Disabled */
continue;
err = tpm_log_event(
tpm_pcr[i],
POINTER_TO_PHYSICAL_ADDRESS(buffer),
buffer_size,
tpm_description);
if (err != EFI_SUCCESS)
tpm_description,
&m);
if (err != EFI_SUCCESS) {
log_error_stall(L"Unable to add initrd TPM measurement for PCR %u (%s), ignoring: %r", tpm_pcr[i], tpm_description, err);
measured = false;
continue;
}
measured = measured < 0 ? m : (measured && m);
}
*ret_buffer = TAKE_PTR(buffer);
*ret_buffer_size = buffer_size;
if (ret_measured)
*ret_measured = measured;
return EFI_SUCCESS;
nothing:
*ret_buffer = NULL;
*ret_buffer_size = 0;
if (ret_measured)
*ret_measured = true;
return EFI_SUCCESS;
}

View file

@ -2,6 +2,7 @@
#pragma once
#include <efi.h>
#include <stdbool.h>
#include <uchar.h>
EFI_STATUS pack_cpio(
@ -15,4 +16,5 @@ EFI_STATUS pack_cpio(
UINTN n_tpm_pcr,
const char16_t *tpm_description,
void **ret_buffer,
UINTN *ret_buffer_size);
UINTN *ret_buffer_size,
bool *ret_measured);

View file

@ -5,6 +5,7 @@
#include <efi.h>
#include <efilib.h>
#include "tpm-pcr.h"
#include "macro-fundamental.h"
#include "measure.h"
#include "missing_efi.h"
@ -141,43 +142,79 @@ bool tpm_present(void) {
return tcg2_interface_check() || tcg1_interface_check();
}
EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description) {
EFI_TCG *tpm1;
EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description, bool *ret_measured) {
EFI_TCG2 *tpm2;
EFI_STATUS err;
assert(description);
/* PCR disabled */
if (pcrindex == UINT32_MAX)
/* If EFI_SUCCESS is returned, will initialize ret_measured to true if we actually measured
* something, or false if measurement was turned off. */
if (pcrindex == UINT32_MAX) { /* PCR disabled? */
if (ret_measured)
*ret_measured = false;
return EFI_SUCCESS;
}
tpm2 = tcg2_interface_check();
if (tpm2)
return tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description);
err = tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description);
else {
EFI_TCG *tpm1;
tpm1 = tcg1_interface_check();
if (tpm1)
return tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description);
tpm1 = tcg1_interface_check();
if (tpm1)
err = tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description);
else {
/* No active TPM found, so don't return an error */
/* No active TPM found, so don't return an error */
return EFI_SUCCESS;
if (ret_measured)
*ret_measured = false;
return EFI_SUCCESS;
}
}
if (err == EFI_SUCCESS && ret_measured)
*ret_measured = true;
return err;
}
EFI_STATUS tpm_log_load_options(const char16_t *load_options) {
EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char *description, bool *ret_measured) {
_cleanup_free_ char16_t *c = NULL;
if (description)
c = xstra_to_str(description);
return tpm_log_event(pcrindex, buffer, buffer_size, c, ret_measured);
}
EFI_STATUS tpm_log_load_options(const char16_t *load_options, bool *ret_measured) {
int measured = -1;
EFI_STATUS err;
/* Measures a load options string into the TPM2, i.e. the kernel command line */
for (UINTN i = 0; i < 2; i++) {
uint32_t pcr = i == 0 ? TPM_PCR_INDEX_KERNEL_PARAMETERS : TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT;
bool m;
err = tpm_log_event(pcr,
POINTER_TO_PHYSICAL_ADDRESS(load_options),
strsize16(load_options), load_options);
if (pcr == UINT32_MAX) /* Skip this one, if it's invalid, so that our 'measured' return value is not corrupted by it */
continue;
err = tpm_log_event(pcr, POINTER_TO_PHYSICAL_ADDRESS(load_options), strsize16(load_options), load_options, &m);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Unable to add load options (i.e. kernel command) line measurement to PCR %u: %r", pcr, err);
measured = measured < 0 ? m : (measured && m);
}
if (ret_measured)
*ret_measured = measured < 0 ? false : measured;
return EFI_SUCCESS;
}

View file

@ -5,25 +5,12 @@
#include <stdbool.h>
#include <uchar.h>
/* This TPM PCR is where sd-stub extends the kernel command line and any passed credentials into. */
#define TPM_PCR_INDEX_KERNEL_PARAMETERS 12U
/* sd-stub used to write the kernel command line/credentials into PCR 8, in systemd <= 250. Let's provide for
* some compatibility. (Remove in 2023!) */
#if EFI_TPM_PCR_COMPAT
#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT 8U
#else
#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT UINT32_MAX
#endif
/* This TPM PCR is where most Linux infrastructure extends the initrd binary images into, and so do we. */
#define TPM_PCR_INDEX_INITRD 4U
#if ENABLE_TPM
bool tpm_present(void);
EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description);
EFI_STATUS tpm_log_load_options(const char16_t *cmdline);
EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description, bool *ret_measured);
EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char *description, bool *ret_measured);
EFI_STATUS tpm_log_load_options(const char16_t *cmdline, bool *ret_measured);
#else
@ -31,11 +18,21 @@ static inline bool tpm_present(void) {
return false;
}
static inline EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description) {
static inline EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description, bool *ret_measured) {
if (ret_measured)
*ret_measured = false;
return EFI_SUCCESS;
}
static inline EFI_STATUS tpm_log_load_options(const char16_t *cmdline) {
static inline EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char *description, bool *ret_measured) {
if (ret_measured)
*ret_measured = false;
return EFI_SUCCESS;
}
static inline EFI_STATUS tpm_log_load_options(const char16_t *cmdline, bool *ret_measured) {
if (ret_measured)
*ret_measured = false;
return EFI_SUCCESS;
}

View file

@ -12,6 +12,7 @@
#include "pe.h"
#include "secure-boot.h"
#include "splash.h"
#include "tpm-pcr.h"
#include "util.h"
/* magic string to find in the binary image */
@ -102,6 +103,13 @@ static EFI_STATUS combine_initrd(
}
static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
static const uint64_t stub_features =
EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */
EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */
EFI_STUB_FEATURE_PICK_UP_SYSEXTS | /* We pick up system extensions from the boot partition */
EFI_STUB_FEATURE_THREE_PCRS | /* We can measure kernel image, parameters and sysext */
0;
char16_t uuid[37];
assert(loaded_image);
@ -142,31 +150,15 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
efivar_set(LOADER_GUID, L"LoaderFirmwareType", s, 0);
}
/* add StubInfo (this is one is owned by the stub, hence we unconditionally override this with our
* own data) */
(void) efivar_set(LOADER_GUID, L"StubInfo", L"systemd-stub " GIT_VERSION, 0);
(void) efivar_set_uint64_le(LOADER_GUID, L"StubFeatures", stub_features, 0);
}
EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
enum {
SECTION_CMDLINE,
SECTION_LINUX,
SECTION_INITRD,
SECTION_SPLASH,
SECTION_DTB,
_SECTION_MAX,
};
static const char * const sections[_SECTION_MAX + 1] = {
[SECTION_CMDLINE] = ".cmdline",
[SECTION_LINUX] = ".linux",
[SECTION_INITRD] = ".initrd",
[SECTION_SPLASH] = ".splash",
[SECTION_DTB] = ".dtb",
NULL,
};
UINTN cmdline_len = 0, linux_size, initrd_size, dt_size;
UINTN credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0;
_cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL;
@ -174,10 +166,11 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base;
_cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
UINTN addrs[_SECTION_MAX] = {};
UINTN szs[_SECTION_MAX] = {};
UINTN addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
char *cmdline = NULL;
_cleanup_free_ char *cmdline_owned = NULL;
int sections_measured = -1, parameters_measured = -1;
bool sysext_measured = false, m;
EFI_STATUS err;
InitializeLib(image, sys_table);
@ -195,83 +188,136 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Error getting a LoadedImageProtocol handle: %r", err);
err = pe_memory_locate_sections(loaded_image->ImageBase, sections, addrs, szs);
if (err != EFI_SUCCESS || szs[SECTION_LINUX] == 0) {
err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, addrs, szs);
if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_LINUX] == 0) {
if (err == EFI_SUCCESS)
err = EFI_NOT_FOUND;
return log_error_status_stall(err, L"Unable to locate embedded .linux section: %r", err);
}
/* Show splash screen as early as possible */
graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[SECTION_SPLASH], szs[SECTION_SPLASH], NULL);
/* 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 (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) {
m = false;
if (szs[SECTION_CMDLINE] > 0) {
cmdline = (char *) loaded_image->ImageBase + addrs[SECTION_CMDLINE];
cmdline_len = szs[SECTION_CMDLINE];
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(unified_sections[section]),
strsize8(unified_sections[section]), /* including NUL byte */
unified_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],
unified_sections[section],
&m);
sections_measured = sections_measured < 0 ? m : (sections_measured && m);
}
/* if we are not in secure boot mode, or none was provided, accept a custom command line and replace the built-in one */
if ((!secure_boot_enabled() || cmdline_len == 0) && loaded_image->LoadOptionsSize > 0 &&
*(char16_t *) loaded_image->LoadOptions > 0x1F) {
cmdline_len = (loaded_image->LoadOptionsSize / sizeof(char16_t)) * sizeof(char);
cmdline = cmdline_owned = xmalloc(cmdline_len);
/* 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);
for (UINTN i = 0; i < cmdline_len; i++)
cmdline[i] = ((char16_t *) loaded_image->LoadOptions)[i];
/* Show splash screen as early as possible */
graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH], NULL);
if (szs[UNIFIED_SECTION_CMDLINE] > 0) {
cmdline = (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE];
cmdline_len = szs[UNIFIED_SECTION_CMDLINE];
}
/* if we are not in secure boot mode, or none was provided, accept a custom command line and replace
* the built-in one. We also do a superficial check whether first chararacter of passed command line
* is printable character (for compat with some Dell systems which fill in garbage?). */
if ((!secure_boot_enabled() || cmdline_len == 0) &&
loaded_image->LoadOptionsSize > 0 &&
((char16_t *) loaded_image->LoadOptions)[0] > 0x1F) {
cmdline_len = (loaded_image->LoadOptionsSize / sizeof(char16_t)) * sizeof(char);
cmdline = cmdline_owned = xnew(char, cmdline_len);
for (UINTN i = 0; i < cmdline_len; i++) {
char16_t c = ((char16_t *) loaded_image->LoadOptions)[i];
cmdline[i] = c > 0x1F && c < 0x7F ? c : ' '; /* convert non-printable and non_ASCII characters to spaces. */
}
/* 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. */
(void) tpm_log_load_options(loaded_image->LoadOptions);
m = false;
(void) tpm_log_load_options(loaded_image->LoadOptions, &m);
parameters_measured = m;
}
export_variables(loaded_image);
(void) pack_cpio(loaded_image,
NULL,
L".cred",
".extra/credentials",
/* dir_mode= */ 0500,
/* access_mode= */ 0400,
/* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT },
/* n_tpm_pcr= */ 2,
L"Credentials initrd",
&credential_initrd,
&credential_initrd_size);
if (pack_cpio(loaded_image,
NULL,
L".cred",
".extra/credentials",
/* dir_mode= */ 0500,
/* access_mode= */ 0400,
/* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT },
/* n_tpm_pcr= */ 2,
L"Credentials initrd",
&credential_initrd,
&credential_initrd_size,
&m) == EFI_SUCCESS)
parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
(void) pack_cpio(loaded_image,
L"\\loader\\credentials",
L".cred",
".extra/global_credentials",
/* dir_mode= */ 0500,
/* access_mode= */ 0400,
/* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT },
/* n_tpm_pcr= */ 2,
L"Global credentials initrd",
&global_credential_initrd,
&global_credential_initrd_size);
if (pack_cpio(loaded_image,
L"\\loader\\credentials",
L".cred",
".extra/global_credentials",
/* dir_mode= */ 0500,
/* access_mode= */ 0400,
/* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT },
/* n_tpm_pcr= */ 2,
L"Global credentials initrd",
&global_credential_initrd,
&global_credential_initrd_size,
&m) == EFI_SUCCESS)
parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
(void) pack_cpio(loaded_image,
NULL,
L".raw",
".extra/sysext",
/* dir_mode= */ 0555,
/* access_mode= */ 0444,
/* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_INITRD },
/* n_tpm_pcr= */ 1,
L"System extension initrd",
&sysext_initrd,
&sysext_initrd_size);
if (pack_cpio(loaded_image,
NULL,
L".raw",
".extra/sysext",
/* dir_mode= */ 0555,
/* access_mode= */ 0444,
/* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_INITRD_SYSEXTS },
/* n_tpm_pcr= */ 1,
L"System extension initrd",
&sysext_initrd,
&sysext_initrd_size,
&m) == EFI_SUCCESS)
sysext_measured = m;
linux_size = szs[SECTION_LINUX];
linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_LINUX];
if (parameters_measured > 0)
(void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelParameters", TPM_PCR_INDEX_KERNEL_PARAMETERS, 0);
if (sysext_measured)
(void) efivar_set_uint_string(LOADER_GUID, L"StubPcrInitRDSysExts", TPM_PCR_INDEX_INITRD_SYSEXTS, 0);
initrd_size = szs[SECTION_INITRD];
initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_INITRD] : 0;
linux_size = szs[UNIFIED_SECTION_LINUX];
linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX];
dt_size = szs[SECTION_DTB];
dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_DTB] : 0;
initrd_size = szs[UNIFIED_SECTION_INITRD];
initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0;
dt_size = szs[UNIFIED_SECTION_DTB];
dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0;
if (credential_initrd || global_credential_initrd || sysext_initrd) {
/* If we have generated initrds dynamically, let's combine them with the built-in initrd. */

View file

@ -637,6 +637,31 @@ __attribute__((noinline)) void debug_break(void) {
}
#endif
#ifdef EFI_DEBUG
void hexdump(const char16_t *prefix, const void *data, UINTN size) {
static const char hex[16] = "0123456789abcdef";
_cleanup_free_ char16_t *buf = NULL;
const uint8_t *d = data;
assert(prefix);
assert(data || size == 0);
/* Debugging helper — please keep this around, even if not used */
buf = xnew(char16_t, size*2+1);
for (UINTN i = 0; i < size; i++) {
buf[i*2] = hex[d[i] >> 4];
buf[i*2+1] = hex[d[i] & 0x0F];
}
buf[size*2] = 0;
log_error_stall(L"%s[%" PRIuN "]: %s", prefix, size, buf);
}
#endif
#if defined(__i386__) || defined(__x86_64__)
static inline uint8_t inb(uint16_t port) {
uint8_t value;

View file

@ -167,6 +167,10 @@ extern uint8_t _text, _data;
# define debug_hook(identity)
#endif
#ifdef EFI_DEBUG
void hexdump(const char16_t *prefix, const void *data, UINTN size);
#endif
#if defined(__i386__) || defined(__x86_64__)
void beep(UINTN beep_count);
#else

533
src/boot/measure.c Normal file
View file

@ -0,0 +1,533 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include <unistd.h>
#include "alloc-util.h"
#include "efi-loader.h"
#include "fd-util.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "main-func.h"
#include "openssl-util.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "terminal-util.h"
#include "tpm-pcr.h"
#include "tpm2-util.h"
#include "verbs.h"
/* Tool for pre-calculating expected TPM PCR values based on measured resources. This is intended to be used
* to pre-calculate suitable values for PCR 11, the way sd-stub measures into it. */
static char *arg_sections[_UNIFIED_SECTION_MAX] = {};
static char **arg_banks = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
static inline void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) {
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++)
free((*sections)[c]);
}
STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections);
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("systemd-measure", "1", &link);
if (r < 0)
return log_oom();
printf("%1$s [OPTIONS...] COMMAND ...\n"
"\n%5$sPre-calculate PCR hash for kernel image.%6$s\n"
"\n%3$sCommands:%4$s\n"
" status Show current PCR values\n"
" calculate Calculate expected PCR values\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Print version\n"
" --linux=PATH Path Linux kernel ELF image\n"
" --osrel=PATH Path to os-release file\n"
" --cmdline=PATH Path to file with kernel command line\n"
" --initrd=PATH Path to initrd image\n"
" --splash=PATH Path to splash bitmap\n"
" --dtb=PATH Path to Devicetree file\n"
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ansi_underline(),
ansi_normal(),
ansi_highlight(),
ansi_normal());
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
_ARG_SECTION_FIRST,
ARG_LINUX = _ARG_SECTION_FIRST,
ARG_OSREL,
ARG_CMDLINE,
ARG_INITRD,
ARG_SPLASH,
_ARG_SECTION_LAST,
ARG_DTB = _ARG_SECTION_LAST,
ARG_BANK,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "linux", required_argument, NULL, ARG_LINUX },
{ "osrel", required_argument, NULL, ARG_OSREL },
{ "cmdline", required_argument, NULL, ARG_CMDLINE },
{ "initrd", required_argument, NULL, ARG_INITRD },
{ "splash", required_argument, NULL, ARG_SPLASH },
{ "dtb", required_argument, NULL, ARG_DTB },
{ "bank", required_argument, NULL, ARG_BANK },
{}
};
int c, r;
assert(argc >= 0);
assert(argv);
/* Make sure the arguments list and the section list, stays in sync */
assert_cc(_ARG_SECTION_FIRST + _UNIFIED_SECTION_MAX == _ARG_SECTION_LAST + 1);
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
switch (c) {
case 'h':
help(0, NULL, NULL);
return 0;
case ARG_VERSION:
return version();
case _ARG_SECTION_FIRST..._ARG_SECTION_LAST: {
UnifiedSection section = c - _ARG_SECTION_FIRST;
r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section);
if (r < 0)
return r;
break;
}
case ARG_BANK: {
const EVP_MD *implementation;
implementation = EVP_get_digestbyname(optarg);
if (!implementation)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg);
if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0)
return log_oom();
break;
}
case '?':
return -EINVAL;
default:
assert_not_reached();
}
if (strv_isempty(arg_banks)) {
/* If no banks are specifically selected, pick all known banks */
arg_banks = strv_new("SHA1", "SHA256", "SHA384", "SHA512");
if (!arg_banks)
return log_oom();
}
strv_sort(arg_banks);
strv_uniq(arg_banks);
return 1;
}
typedef struct PcrState {
const EVP_MD *md;
void *value;
size_t value_size;
} PcrState;
static void pcr_state_free_all(PcrState **pcr_state) {
assert(pcr_state);
if (!*pcr_state)
return;
for (size_t i = 0; (*pcr_state)[i].value; i++)
free((*pcr_state)[i].value);
*pcr_state = mfree(*pcr_state);
}
static void evp_md_ctx_free_all(EVP_MD_CTX **md[]) {
assert(md);
if (!*md)
return;
for (size_t i = 0; (*md)[i]; i++)
EVP_MD_CTX_free((*md)[i]);
*md = mfree(*md);
}
static int pcr_state_extend(PcrState *pcr_state, const void *data, size_t sz) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mc = NULL;
unsigned value_size;
assert(pcr_state);
assert(data || sz == 0);
assert(pcr_state->md);
assert(pcr_state->value);
assert(pcr_state->value_size > 0);
/* Extends a (virtual) PCR by the given data */
mc = EVP_MD_CTX_new();
if (!mc)
return log_oom();
if (EVP_DigestInit_ex(mc, pcr_state->md, NULL) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", EVP_MD_name(pcr_state->md));
/* First thing we do, is hash the old PCR value */
if (EVP_DigestUpdate(mc, pcr_state->value, pcr_state->value_size) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest.");
/* Then, we hash the new data */
if (EVP_DigestUpdate(mc, data, sz) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest.");
if (EVP_DigestFinal_ex(mc, pcr_state->value, &value_size) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context.");
assert(value_size == pcr_state->value_size);
return 0;
}
#define BUFFER_SIZE (16U * 1024U)
static int measure_pcr(PcrState *pcr_states, size_t n) {
_cleanup_free_ void *buffer = NULL;
int r;
assert(n > 0);
assert(pcr_states);
buffer = malloc(BUFFER_SIZE);
if (!buffer)
return log_oom();
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) {
_cleanup_(evp_md_ctx_free_all) EVP_MD_CTX **mdctx = NULL;
_cleanup_close_ int fd = -1;
uint64_t m = 0;
if (!arg_sections[c])
continue;
fd = open(arg_sections[c], O_RDONLY|O_CLOEXEC);
if (fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", arg_sections[c]);
/* Allocate one message digest context per bank (NULL terminated) */
mdctx = new0(EVP_MD_CTX*, n + 1);
if (!mdctx)
return log_oom();
for (size_t i = 0; i < n; i++) {
mdctx[i] = EVP_MD_CTX_new();
if (!mdctx[i])
return log_oom();
if (EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize data %s context.", EVP_MD_name(pcr_states[i].md));
}
for (;;) {
ssize_t sz;
sz = read(fd, buffer, BUFFER_SIZE);
if (sz < 0)
return log_error_errno(errno, "Failed to read '%s': %m", arg_sections[c]);
if (sz == 0) /* EOF */
break;
for (size_t i = 0; i < n; i++)
if (EVP_DigestUpdate(mdctx[i], buffer, sz) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest.");
m += sz;
}
fd = safe_close(fd);
if (m == 0) /* We skip over empty files, the stub does so too */
continue;
for (size_t i = 0; i < n; i++) {
_cleanup_free_ void *data_hash = NULL;
unsigned data_hash_size;
data_hash = malloc(pcr_states[i].value_size);
if (!data_hash)
return log_oom();
/* Measure name of section */
if (EVP_Digest(unified_sections[c], strlen(unified_sections[c]) + 1, data_hash, &data_hash_size, pcr_states[i].md, NULL) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash section name with %s.", EVP_MD_name(pcr_states[i].md));
assert(data_hash_size == (unsigned) pcr_states[i].value_size);
r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size);
if (r < 0)
return r;
/* Retrieve hash of data an measure it*/
if (EVP_DigestFinal_ex(mdctx[i], data_hash, &data_hash_size) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context.");
assert(data_hash_size == (unsigned) pcr_states[i].value_size);
r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size);
if (r < 0)
return r;
}
}
return 0;
}
static int verb_calculate(int argc, char *argv[], void *userdata) {
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
size_t n = 0;
int r;
if (!arg_sections[UNIFIED_SECTION_LINUX])
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--linux= switch must be specified, refusing.");
pcr_states = new0(PcrState, strv_length(arg_banks) + 1);
if (!pcr_states)
return log_oom();
/* Allocate a PCR state structure, one for each bank */
STRV_FOREACH(d, arg_banks) {
const EVP_MD *implementation;
_cleanup_free_ void *v = NULL;
int sz;
assert_se(implementation = EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */
sz = EVP_MD_size(implementation);
if (sz <= 0 || sz >= INT_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected digest size: %i", sz);
v = malloc0(sz); /* initial PCR state is all zeroes */
if (!v)
return log_oom();
pcr_states[n++] = (struct PcrState) {
.md = implementation,
.value = TAKE_PTR(v),
.value_size = sz,
};
}
r = measure_pcr(pcr_states, n);
if (r < 0)
return r;
for (size_t i = 0; i < n; i++) {
_cleanup_free_ char *hd = NULL, *b = NULL;
hd = hexmem(pcr_states[i].value, pcr_states[i].value_size);
if (!hd)
return log_oom();
b = strdup(EVP_MD_name(pcr_states[i].md));
if (!b)
return log_oom();
printf("%" PRIu32 ":%s=%s\n", TPM_PCR_INDEX_KERNEL_IMAGE, ascii_strlower(b), hd);
}
return 0;
}
static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) {
_cleanup_free_ char *s = NULL;
uint32_t v;
int r;
r = efi_get_variable_string(varname, &s);
if (r == -ENOENT)
return 0;
if (r < 0)
return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname);
r = safe_atou32(s, &v);
if (r < 0)
return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s);
if (pcr != v)
log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n"
"The measurements are likely inconsistent.", description, v, pcr);
return 0;
}
static int validate_stub(void) {
uint64_t features;
bool found = false;
int r;
if (tpm2_support() != TPM2_SUPPORT_FULL)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support.");
r = efi_stub_get_features(&features);
if (r < 0)
return log_error_errno(r, "Unable to get stub features: %m");
if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS))
log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n"
"The PCR measurements seen are unlikely to be valid.");
r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_IMAGE, EFI_LOADER_VARIABLE("StubPcrKernelImage"), "kernel image");
if (r < 0)
return r;
r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_PARAMETERS, EFI_LOADER_VARIABLE("StubPcrKernelParameters"), "kernel parameters");
if (r < 0)
return r;
r = compare_reported_pcr_nr(TPM_PCR_INDEX_INITRD_SYSEXTS, EFI_LOADER_VARIABLE("StubPcrInitRDSysExts"), "initrd system extension images");
if (r < 0)
return r;
STRV_FOREACH(bank, arg_banks) {
_cleanup_free_ char *b = NULL, *p = NULL;
b = strdup(*bank);
if (!b)
return log_oom();
if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0)
return log_oom();
if (access(p, F_OK) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b);
} else
found = true;
}
if (!found)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist.");
return 0;
}
static int verb_status(int argc, char *argv[], void *userdata) {
static const struct {
uint32_t nr;
const char *description;
} relevant_pcrs[] = {
{ TPM_PCR_INDEX_KERNEL_IMAGE, "Unified Kernel Image" },
{ TPM_PCR_INDEX_KERNEL_PARAMETERS, "Kernel Parameters" },
{ TPM_PCR_INDEX_INITRD_SYSEXTS, "initrd System Extensions" },
};
int r;
r = validate_stub();
if (r < 0)
return r;
for (size_t i = 0; i < ELEMENTSOF(relevant_pcrs); i++) {
STRV_FOREACH(bank, arg_banks) {
_cleanup_free_ char *b = NULL, *p = NULL, *s = NULL, *f = NULL;
_cleanup_free_ void *h = NULL;
size_t l;
b = strdup(*bank);
if (!b)
return log_oom();
if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), relevant_pcrs[i].nr) < 0)
return log_oom();
r = read_virtual_file(p, 4096, &s, NULL);
if (r == -ENOENT)
continue;
if (r < 0)
return log_error_errno(r, "Failed to read '%s': %m", p);
r = unhexmem(strstrip(s), SIZE_MAX, &h, &l);
if (r < 0)
return log_error_errno(r, "Failed to decode PCR value '%s': %m", s);
f = hexmem(h, l);
if (!h)
return log_oom();
if (bank == arg_banks) {
/* before the first line for each PCR, write a short descriptive text to
* stderr, and leave the primary content on stdout */
fflush(stdout);
fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n",
ansi_grey(),
relevant_pcrs[i].nr,
relevant_pcrs[i].description,
memeqzero(h, l) ? " (NOT SET!)" : "",
ansi_normal());
fflush(stderr);
}
printf("%" PRIu32 ":%s=%s\n", relevant_pcrs[i].nr, b, f);
}
}
return 0;
}
static int measure_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
{ "calculate", VERB_ANY, 1, 0, verb_calculate },
{}
};
return dispatch_verb(argc, argv, verbs, NULL);
}
static int run(int argc, char *argv[]) {
int r;
log_show_color(true);
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
return measure_main(argc, argv);
}
DEFINE_MAIN_FUNCTION(run);

View file

@ -4,6 +4,7 @@
#include <errno.h>
#include "string-util-fundamental.h"
/* Features of the loader, i.e. systemd-boot */
#define EFI_LOADER_FEATURE_CONFIG_TIMEOUT (UINT64_C(1) << 0)
#define EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT (UINT64_C(1) << 1)
#define EFI_LOADER_FEATURE_ENTRY_DEFAULT (UINT64_C(1) << 2)
@ -12,6 +13,15 @@
#define EFI_LOADER_FEATURE_XBOOTLDR (UINT64_C(1) << 5)
#define EFI_LOADER_FEATURE_RANDOM_SEED (UINT64_C(1) << 6)
#define EFI_LOADER_FEATURE_LOAD_DRIVER (UINT64_C(1) << 7)
#define EFI_LOADER_FEATURE_SORT_KEY (UINT64_C(1) << 8)
#define EFI_LOADER_FEATURE_SAVED_ENTRY (UINT64_C(1) << 9)
#define EFI_LOADER_FEATURE_DEVICETREE (UINT64_C(1) << 10)
/* Features of the stub, i.e. systemd-stub */
#define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0)
#define EFI_STUB_FEATURE_PICK_UP_CREDENTIALS (UINT64_C(1) << 1)
#define EFI_STUB_FEATURE_PICK_UP_SYSEXTS (UINT64_C(1) << 2)
#define EFI_STUB_FEATURE_THREE_PCRS (UINT64_C(1) << 3)
typedef enum SecureBootMode {
SECURE_BOOT_UNSUPPORTED,

View file

@ -8,6 +8,7 @@ fundamental_headers = files(
'macro-fundamental.h',
'sha256.h',
'string-util-fundamental.h',
'tpm-pcr.h',
)
# for sd-boot
@ -16,6 +17,7 @@ fundamental_source_paths = files(
'efivars-fundamental.c',
'sha256.c',
'string-util-fundamental.c',
'tpm-pcr.c',
)
# for libbasic

15
src/fundamental/tpm-pcr.c Normal file
View file

@ -0,0 +1,15 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stddef.h>
#include "tpm-pcr.h"
const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = {
[UNIFIED_SECTION_LINUX] = ".linux",
[UNIFIED_SECTION_OSREL] = ".osrel",
[UNIFIED_SECTION_CMDLINE] = ".cmdline",
[UNIFIED_SECTION_INITRD] = ".initrd",
[UNIFIED_SECTION_SPLASH] = ".splash",
[UNIFIED_SECTION_DTB] = ".dtb",
NULL,
};

38
src/fundamental/tpm-pcr.h Normal file
View file

@ -0,0 +1,38 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
/* The various TPM PCRs we measure into from sd-stub and sd-boot. */
/* 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
/* sd-stub used to write the kernel command line/credentials into PCR 8, in systemd <= 250. Let's provide for
* some compatibility. (Remove in 2023!) */
#if EFI_TPM_PCR_COMPAT
#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT 8U
#else
#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT UINT32_MAX
#endif
/* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */
#define TPM_PCR_INDEX_INITRD_SYSEXTS 13U
/* List of PE sections that have special meaning for us in unified kernels. This is the canonical order in
* which we measure the sections into TPM PCR 11 (see above). PLEASE DO NOT REORDER! */
typedef enum UnifiedSection {
UNIFIED_SECTION_LINUX,
UNIFIED_SECTION_OSREL,
UNIFIED_SECTION_CMDLINE,
UNIFIED_SECTION_INITRD,
UNIFIED_SECTION_SPLASH,
UNIFIED_SECTION_DTB,
_UNIFIED_SECTION_MAX,
} UnifiedSection;
extern const char* const unified_sections[_UNIFIED_SECTION_MAX + 1];

View file

@ -187,6 +187,54 @@ int efi_loader_get_features(uint64_t *ret) {
return 0;
}
int efi_stub_get_features(uint64_t *ret) {
_cleanup_free_ void *v = NULL;
size_t s;
int r;
assert(ret);
if (!is_efi_boot()) {
*ret = 0;
return 0;
}
r = efi_get_variable(EFI_LOADER_VARIABLE(StubFeatures), NULL, &v, &s);
if (r == -ENOENT) {
_cleanup_free_ char *info = NULL;
/* The new (v252+) StubFeatures variable is not supported, let's see if it's systemd-stub at all */
r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubInfo), &info);
if (r < 0) {
if (r != -ENOENT)
return r;
/* Variable not set, definitely means not systemd-stub */
} else if (first_word(info, "systemd-stub")) {
/* An older systemd-stub version. Let's hardcode the feature set, since it was pretty
* static in all its versions. */
*ret = EFI_STUB_FEATURE_REPORT_BOOT_PARTITION;
return 0;
}
/* No features supported */
*ret = 0;
return 0;
}
if (r < 0)
return r;
if (s != sizeof(uint64_t))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"StubFeatures EFI variable doesn't have the right size.");
memcpy(ret, v, sizeof(uint64_t));
return 0;
}
int efi_loader_get_config_timeout_one_shot(usec_t *ret) {
_cleanup_free_ char *v = NULL;
static struct stat cache_stat = {};

View file

@ -16,6 +16,7 @@ int efi_loader_get_boot_usec(usec_t *ret_firmware, usec_t *ret_loader);
int efi_loader_get_entries(char ***ret);
int efi_loader_get_features(uint64_t *ret);
int efi_stub_get_features(uint64_t *ret);
int efi_loader_get_config_timeout_one_shot(usec_t *ret);
int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat);
@ -38,6 +39,10 @@ static inline int efi_loader_get_features(uint64_t *ret) {
return -EOPNOTSUPP;
}
static inline int efi_stub_get_features(uint64_t *ret) {
return -EOPNOTSUPP;
}
static inline int efi_loader_get_config_timeout_one_shot(usec_t *ret) {
return -EOPNOTSUPP;
}

View file

@ -43,6 +43,18 @@ env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $
tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000
/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && exit 1
echo HALLO > /tmp/tpmdata1
echo foobar > /tmp/tpmdata2
cat > /tmp/result <<EOF
11:sha1=5177e4ad69db92192c10e5f80402bf81bfec8a81
11:sha256=37b48bd0b222394dbe3cceff2fca4660c4b0a90ae9369ec90b42f14489989c13
11:sha384=5573f9b2caf55b1d0a6a701f890662d682af961899f0419cf1e2d5ea4a6a68c1f25bd4f5b8a0865eeee82af90f5cb087
11:sha512=961305d7e9981d6606d1ce97b3a9a1f92610cac033e9c39064895f0e306abc1680463d55767bd98e751eae115bdef3675a9ee1d29ed37da7885b1db45bb2555b
EOF
/usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 | cmp - /tmp/result
echo OK >/testok
exit 0