diff options
-rw-r--r-- | TODO | 65 | ||||
-rw-r--r-- | man/systemd-measure.xml | 59 | ||||
-rw-r--r-- | man/systemd-stub.xml | 123 | ||||
-rw-r--r-- | src/boot/efi/cpio.c | 57 | ||||
-rw-r--r-- | src/boot/efi/cpio.h | 14 | ||||
-rw-r--r-- | src/boot/efi/stub.c | 104 | ||||
-rw-r--r-- | src/boot/measure.c | 6 | ||||
-rw-r--r-- | src/fundamental/tpm-pcr.c | 2 | ||||
-rw-r--r-- | src/fundamental/tpm-pcr.h | 10 | ||||
-rw-r--r-- | tmpfiles.d/systemd.conf.in | 6 |
10 files changed, 332 insertions, 114 deletions
@@ -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 diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml index 0fc0d0e87d..69ac348184 100644 --- a/man/systemd-measure.xml +++ b/man/systemd-measure.xml @@ -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> diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 1e9bb5d631..f8c3eee393 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -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> @@ -191,6 +216,21 @@ </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> </row> @@ -280,6 +320,66 @@ </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> <para>In order to assemble an UEFI PE kernel image from various components as described above, use an @@ -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> diff --git a/src/boot/efi/cpio.c b/src/boot/efi/cpio.c index b21cf53b46..c71c218e4f 100644 --- a/src/boot/efi/cpio.c +++ b/src/boot/efi/cpio.c @@ -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; +} diff --git a/src/boot/efi/cpio.h b/src/boot/efi/cpio.h index 672a751825..beebef3d8b 100644 --- a/src/boot/efi/cpio.h +++ b/src/boot/efi/cpio.h @@ -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); diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 3f6832e0c2..494972fa38 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -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) - 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; + for (size_t i = 0; i < n_extra_initrds; i++) { + if (!extra_initrds[i]) + continue; - n += global_credential_initrd_size; - } - if (sysext_initrd) { - if (n > UINTN_MAX - sysext_initrd_size) + if (n > UINTN_MAX - extra_initrd_sizes[i]) 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) { diff --git a/src/boot/measure.c b/src/boot/measure.c index e8404026cb..bc8f720514 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -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 }, diff --git a/src/fundamental/tpm-pcr.c b/src/fundamental/tpm-pcr.c index 97b3c7b9d2..7609d83c2e 100644 --- a/src/fundamental/tpm-pcr.c +++ b/src/fundamental/tpm-pcr.c @@ -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, }; diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h index fb0774f70d..235d4841b0 100644 --- a/src/fundamental/tpm-pcr.h +++ b/src/fundamental/tpm-pcr.h @@ -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; +} diff --git a/tmpfiles.d/systemd.conf.in b/tmpfiles.d/systemd.conf.in index e23e102782..d267a6b2e6 100644 --- a/tmpfiles.d/systemd.conf.in +++ b/tmpfiles.d/systemd.conf.in @@ -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 %} |