Merge pull request #24458 from poettering/stub-embedd-sig

optionally, embed PCR signature and public key in new sd-stub PE sections
This commit is contained in:
Lennart Poettering
2022-09-09 18:18:37 +02:00
committed by GitHub
10 changed files with 334 additions and 116 deletions

65
TODO
View File

@@ -117,6 +117,11 @@ Deprecations and removals:
Features:
* during the initrd → host transition measure a fixed value into TPM PCR 11
(where we already measure the UKI into), so that unlock policies for disk
enryption/credential encryption can be put together that only work in the
initrd or only on the host (or both).
* Add support for extra verity configuration options to systemd-reart (FEC, hash type, etc)
* chase_symlinks(): take inspiraton from path_extract_filename() and return
@@ -160,11 +165,6 @@ Features:
* 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.
@@ -249,8 +249,7 @@ Features:
* repart: allow defining additional partitions via credential
* tmpfiles: add snippet that provisions /etc/hosts, /etc/motd,
/root/.ssh/authorized_keys from credential
* tmpfiles: add snippet that provisions /root/.ssh/authorized_keys from credential
* timesyncd: pick NTP server info from credential
@@ -343,50 +342,11 @@ Features:
* given that /etc/ssh/ssh_config.d/ is a thing now, ship a drop-in for that
that hooks up userbdctl ssh-key stuff.
* allow embedding a signature blob for PCR hashes into separate section in
unified kernel binaries. This section should be picked up by sd-stub, and
passed in a file to the booted kernel (via initrd cpio, as usual). Usecase:
this way we can implement disk encryption policies that bind to specific
kernel PCR state, without breaking things on every kernel update. As long as
the kernel includes the PCR signature blob we should be good, as disk
encryption can then pass the signature to the TPM to unlock their secrets.
Why do this via a separate PE section? That's because the PCR state depends
on the measured kernel/initrd of course, thus we cannot put the signature
into the kernel/initrd itself, because that would require a time machine.
Hence we have to find a separate place. A simple solution is a PE section
of its own, because then it is next to the kernel and initrd which after all
are stored in PE sections of their own too. Building a unified kernel would
thus mean, calculating PCR values for the raw kernel image, and raw initrd
image, then signing those PCR values with a vendor key, and then combining
sd-stub, raw kernel image, raw initrd, and PCR signature into a unified
kernel image.
* a new tool "systemd-trust" or so, that can calculate PCR hashes offline, and
optionally sign them. for that we should extend our syntax for specifying pcr
policies (e.g. the string like "4+7+9") so that it can also include explicit
hash values, i.e.
4=sha256:0ef149998289474e4bb31813edda6ad7f3c991b2d8dec6e8fe4db7a1f039f2d1+7=sha256:87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7+9=sha256:0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f
and file names to calculate hashes from, i.e.
4=file:/boot/vmlinuz+7=file:/boot/initrd/+9=file:/etc/fstab"
The systemd-trust tool should then be able to resolve any "underspecifed"
form into the form with explicit hash values.
* maybe add support for binding and connecting AF_UNIX sockets in the file
system outside of the 108ch limit. When connecting, open O_PATH fd to socket
inode first, then connect to /proc/self/fd/XYZ. When binding, create symlink
to target dir in /tmp, and bind through it.
* tmpfiles: for f/F/w lines, if the argument columns is left unspecified, look
for a service credential named after the file path to write to, and load
contents to write from there. Usecase: provision arbitrary files from
credentials. Example use: with a line like "f /root/.ssh/authorized-keys
0644 root root" in a tmpfiles.d/ snippet add
LoadCredential=root.ssh.authorized-keys via drop-in to
systemd-tmpfiles.service, and then provision an SSH access key through
nspawn's --load-credential=, through qemu's fw_cfg, or via systemd-stub's
credntial pick-up. The latter is particularly interesting to implement SSH
access to an initrd.
* systemd-homed: when initializing, look for a credential sysemd.homed.register
or so with JSON user records to automatically register if not registered yet.
Usecase: deploy a system, and add an account one can directly log into.
@@ -406,14 +366,11 @@ Features:
set up the directory so that it can only be accessed if host and app are in
order.
* TPM2: add auth policy for signed PCR values to make updates easy. i.e. do
what tpm2_policyauthorize tool does. To be truly useful scheme needs to be a
bit more elaborate though: policy probably must take some nvram based
generation counter into account that can only monotonically increase and can
be used to invalidate old PCR signatures. Otherwise people could downgrade to
old signed PCR sets whenever they want. Usecase: encrypt the rootfs with LUKS
with a key that can only be unlocked via a pristine pre-built Fedora
kernel+initrd.
* TPM2: extend unlock policy to protect against version downgrades in signed
policies: policy probably must take some nvram based generation counter into
account that can only monotonically increase and can be used to invalidate
old PCR signatures. Otherwise people could downgrade to old signed PCR sets
whenever they want.
* update HACKING.md to suggest developing systemd with the ideas from:
https://0pointer.net/blog/testing-my-system-code-in-usr-without-modifying-usr.html

View File

@@ -37,12 +37,12 @@
<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. The result may optionally be
signed cryptographically, to allow TPM2 policies that can only be unlocked if a certain set of kernels is
booted, for which such a PCR signature can be provided.</para>
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> file, boot
splash file, and TPM2 PCR PEM public key 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. The result may optionally be signed cryptographically, to allow TPM2 policies that can only be
unlocked if a certain set of kernels is booted, for which such a PCR signature can be provided.</para>
</refsect1>
<refsect1>
@@ -66,9 +66,9 @@
<listitem><para>Pre-calculate the expected values 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. (Alternatively, specify <option>--current</option> to use the current values of PCR
register 11 instead.)</para></listitem>
<option>--splash=</option>, <option>--dtb=</option>, <option>--pcrpkey=</option> see below. Only
<option>--linux=</option> is mandatory. (Alternatively, specify <option>--current</option> to use the
current values of PCR register 11 instead.)</para></listitem>
</varlistentry>
<varlistentry>
@@ -104,6 +104,7 @@
<term><option>--initrd=PATH</option></term>
<term><option>--splash=PATH</option></term>
<term><option>--dtb=PATH</option></term>
<term><option>--pcrpkey=PATH</option></term>
<listitem><para>When used with the <command>calculate</command> or <command>sign</command> verb,
configures the files to read the unified kernel image components from. Each option corresponds with
@@ -135,7 +136,14 @@
<term><option>--public-key=PATH</option></term>
<listitem><para>These switches take paths to a pair of PEM encoded RSA key files, for use with
the <command>sign</command> command.</para></listitem>
the <command>sign</command> command.</para>
<para>Note the difference between the <option>--pcrpkey=</option> and <option>--public-key=</option>
switches. The former selects the data to include in the <literal>.pcrpkey</literal> PE section of the
unified kernel image, the latter picks the public key of the key pair used to sign the resulting PCR
11 values. The former is the key that the booted system will likely use to lock disk and credential
encryption to, the latter is the key used for unlocking such resources again. Hence, typically the
same PEM key should be supplied in both cases.</para></listitem>
</varlistentry>
<varlistentry>
@@ -185,19 +193,11 @@
</example>
<example>
<title>Generate a private/public key pair, and a unified kernel image, and a TPM PCR 11 signature for it</title>
<title>Generate a private/public key pair, and a unified kernel image, and a TPM PCR 11 signature for
it, and embed the signature and the public key in the image</title>
<programlisting># openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out tpm2-pcr-private.pem
# openssl rsa -pubout -in tpm2-pcr-private.pem -out tpm2-pcr-public.pem
# 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 sign \
--linux=vmlinux \
--osrel=os-release.txt \
@@ -205,10 +205,22 @@
--initrd=initrd.cpio \
--splash=splash.bmp \
--dtb=devicetree.dtb \
--pcrpkey=tpm2-pcr-public.pem \
--bank=sha1 \
--bank=sha256 \
--private-key=tpm2-pcr-private.pem \
--public-key=tpm2-pcr-public.pem > tpm2-pcr-signature.json</programlisting>
--public-key=tpm2-pcr-public.pem > tpm2-pcr-signature.json
# 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 \
--add-section .pcrsig=tpm2-pcr-signature.json --change-section-vma .splash=0x80000 \
--add-section .pcrpkey=tpm2-pcr-public.pem --change-section-vma .splash=0x90000 \
/usr/lib/systemd/boot/efi/linuxx64.efi.stub \
foo.efi</programlisting>
<para>Later on, enroll the signed PCR policy on a LUKS volume:</para>
@@ -217,6 +229,11 @@
<para>And then unlock the device with the signature:</para>
<programlisting># /usr/lib/systemd/systemd-cryptsetup attach myvolume /dev/sda5 - tpm2-device=auto,tpm2-signature=/path/to/tpm2-pcr-signature.json</programlisting>
<para>Note that when the generated unified kernel image <filename>foo.efi</filename> is booted the
signature and public key files will be placed at locations <command>systemd-cryptenroll</command> and
<command>systemd-cryptsetup</command> will look for anyway, and thus these paths do not actually need to
be specified.</para>
</example>
</refsect1>

View File

@@ -45,9 +45,9 @@
system into the Linux world.</para>
<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, which may then be signed
via UEFI SecureBoot as a whole, covering all individual resources at once. Specifically it may
include:</para>
itself. This allows combining various resources inside a single PE binary image (usually called "Unified
Kernel Image", or "UKI" for short), which may then be signed via UEFI SecureBoot as a whole, covering all
individual resources at once. Specifically it may include:</para>
<itemizedlist>
<listitem><para>The ELF Linux kernel images will be looked for in the <literal>.linux</literal> PE
@@ -68,6 +68,14 @@
<listitem><para>A boot splash (in Windows <filename>.BMP</filename> format) to show on screen before
invoking the kernel will be looked for in the <literal>.splash</literal> PE section.</para></listitem>
<listitem><para>A set of cryptographic signatures for expected TPM2 PCR values when this kernel is
booted, in JSON format, in the <literal>.pcrsig</literal> section. This is useful for implementing TPM2
policies that bind disk encryption and similar to kernels that are signed by a specific
key.</para></listitem>
<listitem><para>A public key in PEM format matching this TPM2 PCR signature data in the
<literal>.pcrpkey</literal> section.</para></listitem>
</itemizedlist>
<para>If UEFI SecureBoot is enabled and the <literal>.cmdline</literal> section is present in the executed
@@ -81,8 +89,25 @@
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>
<para>The contents of seven of these eight PE sections are measured into TPM PCR 11, that is otherwise
not used. Thus, it can be pre-calculated without too much effort. The <literal>.pcrsig</literal> section
is not included in this PCR measurement, since it's supposed to contain signatures for the expected
results for these measurements, i.e. of the outputs of the measurement operation, and thus cannot also be
input to it.</para>
<para>When <literal>.pcrsig</literal> and/or <literal>.pcrpkey</literal> are present in a unified kernel
image their contents are passed to the booted kernel in an synthetic initrd cpio archive that places them in the
<filename>/.extra/tpm2-pcr-signature.json</filename> and
<filename>/.extra/tpm2-pcr-public-key.pem</filename> files. Typically, a
<citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> line then
ensures they are copied into <filename>/run/systemd/tpm2-pcr-signature.json</filename> and
<filename>/run/systemd/tpm2-pcr-public-key.pem</filename> where they remain accessible even after the
system transitions out of the initrd environment into the host file system. Tools such
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
and <citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry>
will automatically use files present under these paths to unlock protected resources (encrypted storage
or credentials) or bind encryption to booted kernels.</para>
</refsect1>
<refsect1>
@@ -133,7 +158,7 @@
</refsect1>
<refsect1>
<title>TPM2 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
measure it as a whole to TPM PCR 4, covering all embedded resources, such as the stub code itself, the
@@ -166,12 +191,12 @@
</row>
<row>
<entry>Boot splash (embedded in the unified PE binary)</entry>
<entry>Core kernel code (embedded in unified PE binary)</entry>
<entry>4 + 11</entry>
</row>
<row>
<entry>Core kernel code (embedded in unified PE binary)</entry>
<entry>OS release information (embedded in the unified PE binary)</entry>
<entry>4 + 11</entry>
</row>
@@ -190,6 +215,21 @@
<entry>12</entry>
</row>
<row>
<entry>Boot splash (embedded in the unified PE binary)</entry>
<entry>4 + 11</entry>
</row>
<row>
<entry>TPM2 PCR signature JSON (embedded in unified PE binary, synthesized into initrd)</entry>
<entry>4 + 9</entry>
</row>
<row>
<entry>TPM2 PCR PEM public key (embedded in unified PE binary, synthesized into initrd)</entry>
<entry>4 + 9 + 11</entry>
</row>
<row>
<entry>Credentials (synthesized initrd from companion files)</entry>
<entry>9 + 12</entry>
@@ -279,6 +319,66 @@
url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>.</para>
</refsect1>
<refsect1>
<title>initrd Resources</title>
<para>The following resources are passed as initrd cpio archives to the booted kernel, and thus make up
the initial file system hierarchy in the initrd execution environment:</para>
<variablelist>
<varlistentry>
<term><filename>/</filename></term>
<listitem><para>The main initrd from the <literal>.initrd</literal> PE section of the unified kernel image.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>/.extra/credentials/*.cred</filename></term>
<listitem><para>Credential files (suffix <literal>.cred</literal>) that are placed next to the
unified kernel image (as described above) are copied into the
<filename>/.extra/credentials/</filename> directory in the initrd execution
environment.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>/.extra/global_credentials/*.cred</filename></term>
<listitem><para>Similar, credential files in the <filename>/loader/credentials/</filename> directory
in the file system the unified kernel image is placed in are copied into the
<filename>/.extra/global_credentials/</filename> directory in the initrd execution
environment.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>/.extra/sysext/*.raw</filename></term>
<listitem><para>System extension image files (suffix <literal>.raw</literal>) that are placed next to
the unified kernel image (as described above) are copied into the
<filename>/.extra/sysext/</filename> directory in the initrd execution environment.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>/.extra/tpm2-pcr-signature.json</filename></term>
<listitem><para>The TPM2 PCR signature JSON object included in the <literal>.pcrsig</literal> PE
section of the unified kernel image is copied into the
<filename>/.extra/tpm2-pcr-signature.json</filename> file in the initrd execution
environment.</para></listitem>
</varlistentry>
<varlistentry>
<term><filename>/.extra/tpm2-pcr-pkey.pem</filename></term>
<listitem><para>The PEM public key included in the <literal>.pcrpkey</literal> PE section of the
unified kernel image is copied into the <filename>/.extra/tpm2-pcr-public-key.pem</filename> file in
the initrd execution environment.</para></listitem>
</varlistentry>
</variablelist>
<para>Note that all these files are located in the <literal>tmpfs</literal> file system the kernel sets
up for the initrd file hierarchy and are thus lost when the system transitions from the initrd execution
environment into the host file system. If these resources shall be kept around over this transition they
need to be copied to a place that survives the transition first, for example via a suitable
<citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> line. By
default, this is done for the TPM2 PCR signature and public key files.</para>
</refsect1>
<refsect1>
<title>Assembling Kernel Images</title>
@@ -313,6 +413,10 @@
<para>This expects a pair of X.509 private key and certificate as parameters and then signs the UEFI PE
executable we generated above for UEFI SecureBoot and generates a signed UEFI PE executable as
result.</para>
<para>See
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
an example involving the <literal>.pcrsig</literal> and <literal>.pcrpkey</literal> sections.</para>
</refsect1>
<refsect1>
@@ -325,7 +429,8 @@
<ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink>,
<ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>,
<citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry project='archlinux'><refentrytitle>sbsign</refentrytitle><manvolnum>1</manvolnum></citerefentry>
<citerefentry project='archlinux'><refentrytitle>sbsign</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@@ -487,3 +487,60 @@ nothing:
return EFI_SUCCESS;
}
EFI_STATUS pack_cpio_literal(
const void *data,
size_t data_size,
const char *target_dir_prefix,
const char16_t *target_filename,
uint32_t dir_mode,
uint32_t access_mode,
const uint32_t tpm_pcr[],
UINTN n_tpm_pcr,
const char16_t *tpm_description,
void **ret_buffer,
UINTN *ret_buffer_size,
bool *ret_measured) {
uint32_t inode = 1; /* inode counter, so that each item gets a new inode */
_cleanup_free_ void *buffer = NULL;
UINTN buffer_size;
EFI_STATUS err;
assert(data || data_size == 0);
assert(target_dir_prefix);
assert(target_filename);
assert(tpm_pcr || n_tpm_pcr == 0);
assert(ret_buffer);
assert(ret_buffer_size);
/* Generate the leading directory inodes right before adding the first files, to the
* archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */
err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Failed to pack cpio prefix: %r", err);
err = pack_cpio_one(
target_filename,
data, data_size,
target_dir_prefix,
access_mode,
&inode,
&buffer, &buffer_size);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Failed to pack cpio file %s: %r", target_filename, err);
err = pack_cpio_trailer(&buffer, &buffer_size);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Failed to pack cpio trailer: %r");
err = measure_cpio(buffer, buffer_size, tpm_pcr, n_tpm_pcr, tpm_description, ret_measured);
if (err != EFI_SUCCESS)
return err;
*ret_buffer = TAKE_PTR(buffer);
*ret_buffer_size = buffer_size;
return EFI_SUCCESS;
}

View File

@@ -18,3 +18,17 @@ EFI_STATUS pack_cpio(
void **ret_buffer,
UINTN *ret_buffer_size,
bool *ret_measured);
EFI_STATUS pack_cpio_literal(
const void *data,
size_t data_size,
const char *target_dir_prefix,
const char16_t *target_filename,
uint32_t dir_mode,
uint32_t access_mode,
const uint32_t tpm_pcr[],
UINTN n_tpm_pcr,
const char16_t *tpm_description,
void **ret_buffer,
UINTN *ret_buffer_size,
bool *ret_measured);

View File

@@ -20,9 +20,7 @@ _used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: syste
static EFI_STATUS combine_initrd(
EFI_PHYSICAL_ADDRESS initrd_base, UINTN initrd_size,
const void *credential_initrd, UINTN credential_initrd_size,
const void *global_credential_initrd, UINTN global_credential_initrd_size,
const void *sysext_initrd, UINTN sysext_initrd_size,
const void * const extra_initrds[], const size_t extra_initrd_sizes[], size_t n_extra_initrds,
EFI_PHYSICAL_ADDRESS *ret_initrd_base, UINTN *ret_initrd_size) {
EFI_PHYSICAL_ADDRESS base = UINT32_MAX; /* allocate an area below the 32bit boundary for this */
@@ -36,23 +34,15 @@ static EFI_STATUS combine_initrd(
/* Combines four initrds into one, by simple concatenation in memory */
n = ALIGN4(initrd_size); /* main initrd might not be padded yet */
if (credential_initrd) {
if (n > UINTN_MAX - credential_initrd_size)
for (size_t i = 0; i < n_extra_initrds; i++) {
if (!extra_initrds[i])
continue;
if (n > UINTN_MAX - extra_initrd_sizes[i])
return EFI_OUT_OF_RESOURCES;
n += credential_initrd_size;
}
if (global_credential_initrd) {
if (n > UINTN_MAX - global_credential_initrd_size)
return EFI_OUT_OF_RESOURCES;
n += global_credential_initrd_size;
}
if (sysext_initrd) {
if (n > UINTN_MAX - sysext_initrd_size)
return EFI_OUT_OF_RESOURCES;
n += sysext_initrd_size;
n += extra_initrd_sizes[i];
}
err = BS->AllocatePages(
@@ -78,12 +68,12 @@ static EFI_STATUS combine_initrd(
}
}
if (credential_initrd)
p = mempcpy(p, credential_initrd, credential_initrd_size);
if (global_credential_initrd)
p = mempcpy(p, global_credential_initrd, global_credential_initrd_size);
if (sysext_initrd)
p = mempcpy(p, sysext_initrd, sysext_initrd_size);
for (size_t i = 0; i < n_extra_initrds; i++) {
if (!extra_initrds[i])
continue;
p = mempcpy(p, extra_initrds[i], extra_initrd_sizes[i]);
}
assert((uint8_t*) PHYSICAL_ADDRESS_TO_POINTER(base) + n == p);
@@ -150,10 +140,9 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
}
EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
_cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL;
UINTN credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0;
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;
_cleanup_free_ void *sysext_initrd = NULL;
EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base;
_cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
@@ -190,11 +179,15 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
* 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 (!unified_section_measure(section)) /* shall not measure? */
continue;
if (szs[section] == 0) /* not found */
continue;
m = false;
/* First measure the name of the section */
(void) tpm_log_event_ascii(
TPM_PCR_INDEX_KERNEL_IMAGE,
@@ -301,6 +294,45 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
if (sysext_measured)
(void) efivar_set_uint_string(LOADER_GUID, L"StubPcrInitRDSysExts", TPM_PCR_INDEX_INITRD_SYSEXTS, 0);
/* 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. it's input are PCR measurement, and hence it shouldn't
* itself be input for PCR measurements. */
if (szs[UNIFIED_SECTION_PCRSIG] > 0)
(void) pack_cpio_literal(
(uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRSIG],
szs[UNIFIED_SECTION_PCRSIG],
".extra",
L"tpm2-pcr-signature.json",
/* dir_mode= */ 0555,
/* access_mode= */ 0444,
/* tpm_pcr= */ NULL,
/* n_tpm_pcr= */ 0,
/* tpm_description= */ NULL,
&pcrsig_initrd,
&pcrsig_initrd_size,
/* 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 (szs[UNIFIED_SECTION_PCRPKEY] > 0)
(void) pack_cpio_literal(
(uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRPKEY],
szs[UNIFIED_SECTION_PCRPKEY],
".extra",
L"tpm2-pcr-public-key.pem",
/* dir_mode= */ 0555,
/* access_mode= */ 0444,
/* tpm_pcr= */ NULL,
/* n_tpm_pcr= */ 0,
/* tpm_description= */ NULL,
&pcrpkey_initrd,
&pcrpkey_initrd_size,
/* ret_measured= */ NULL);
linux_size = szs[UNIFIED_SECTION_LINUX];
linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX];
@@ -314,9 +346,21 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
/* If we have generated initrds dynamically, let's combine them with the built-in initrd. */
err = combine_initrd(
initrd_base, initrd_size,
credential_initrd, credential_initrd_size,
global_credential_initrd, global_credential_initrd_size,
sysext_initrd, sysext_initrd_size,
(const void*const[]) {
credential_initrd,
global_credential_initrd,
sysext_initrd,
pcrsig_initrd,
pcrpkey_initrd,
},
(const size_t[]) {
credential_initrd_size,
global_credential_initrd_size,
sysext_initrd_size,
pcrsig_initrd_size,
pcrpkey_initrd_size,
},
5,
&initrd_base, &initrd_size);
if (err != EFI_SUCCESS)
return err;
@@ -325,6 +369,8 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
credential_initrd = mfree(credential_initrd);
global_credential_initrd = mfree(global_credential_initrd);
sysext_initrd = mfree(sysext_initrd);
pcrsig_initrd = mfree(pcrsig_initrd);
pcrpkey_initrd = mfree(pcrpkey_initrd);
}
if (dt_size > 0) {

View File

@@ -68,6 +68,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --initrd=PATH Path to initrd image\n"
" --splash=PATH Path to splash bitmap\n"
" --dtb=PATH Path to Devicetree file\n"
" --pcrpkey=PATH Path to public key for PCR signatures in DER format\n"
" -c --current Use current PCR values\n"
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
" --tpm2-device=PATH Use specified TPM2 device\n"
@@ -96,8 +97,10 @@ static int parse_argv(int argc, char *argv[]) {
ARG_CMDLINE,
ARG_INITRD,
ARG_SPLASH,
ARG_DTB,
_ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */
_ARG_SECTION_LAST,
ARG_DTB = _ARG_SECTION_LAST,
ARG_PCRPKEY = _ARG_SECTION_LAST,
ARG_BANK,
ARG_PRIVATE_KEY,
ARG_PUBLIC_KEY,
@@ -115,6 +118,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "initrd", required_argument, NULL, ARG_INITRD },
{ "splash", required_argument, NULL, ARG_SPLASH },
{ "dtb", required_argument, NULL, ARG_DTB },
{ "pcrpkey", required_argument, NULL, ARG_PCRPKEY },
{ "current", no_argument, NULL, 'c' },
{ "bank", required_argument, NULL, ARG_BANK },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },

View File

@@ -11,5 +11,7 @@ const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = {
[UNIFIED_SECTION_INITRD] = ".initrd",
[UNIFIED_SECTION_SPLASH] = ".splash",
[UNIFIED_SECTION_DTB] = ".dtb",
[UNIFIED_SECTION_PCRSIG] = ".pcrsig",
[UNIFIED_SECTION_PCRPKEY] = ".pcrpkey",
NULL,
};

View File

@@ -1,6 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "macro-fundamental.h"
/* 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,
@@ -32,7 +34,15 @@ typedef enum UnifiedSection {
UNIFIED_SECTION_INITRD,
UNIFIED_SECTION_SPLASH,
UNIFIED_SECTION_DTB,
UNIFIED_SECTION_PCRSIG,
UNIFIED_SECTION_PCRPKEY,
_UNIFIED_SECTION_MAX,
} UnifiedSection;
extern const char* const unified_sections[_UNIFIED_SECTION_MAX + 1];
static inline bool unified_section_measure(UnifiedSection section) {
/* Don't include the PCR signature in the PCR measurements, since they sign the expected result of
* the measurement, and hence shouldn't be input to it. */
return section >= 0 && section < _UNIFIED_SECTION_MAX && section != UNIFIED_SECTION_PCRSIG;
}

View File

@@ -64,3 +64,9 @@ d /var/lib/systemd/coredump 0755 root root 3d
d /var/lib/private 0700 root root -
d /var/log/private 0700 root root -
d /var/cache/private 0700 root root -
{% if ENABLE_EFI %}
# Copy sd-stub provided PCR signature and and public key file from initrd into /run/, so that it will survive the initrd stage
C /run/systemd/tpm2-pcr-signature.json 0444 root root - /.extra/tpm2-pcr-signature.json
C /run/systemd/tpm2-pcr-public-key.pem 0444 root root - /.extra/tpm2-pcr-public-key.pem
{% endif %}