pcrextend: automatically measure SMBIOS product ID at boot

Now that PCRs are not that expensive anymore, let's use them to measure
the SMBIOS product ID to one.
This commit is contained in:
Lennart Poettering
2024-06-04 12:01:10 +02:00
parent 2b90bf1730
commit 0196abbd10
10 changed files with 111 additions and 11 deletions

View File

@@ -21,10 +21,11 @@
<refname>systemd-pcrphase-sysinit.service</refname>
<refname>systemd-pcrphase-initrd.service</refname>
<refname>systemd-pcrmachine.service</refname>
<refname>systemd-pcrproduct.service</refname>
<refname>systemd-pcrfs-root.service</refname>
<refname>systemd-pcrfs@.service</refname>
<refname>systemd-pcrextend</refname>
<refpurpose>Measure boot phase into TPM2 PCR 11, machine ID and file system identity into PCR 15</refpurpose>
<refpurpose>Measure boot phases, machine ID, product UUID and file system identity into TPM PCRs and NvPCRs</refpurpose>
</refnamediv>
<refsynopsisdiv>
@@ -49,6 +50,10 @@
(see <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>) into
PCR 15.</para>
<para><filename>systemd-pcrproduct.service</filename> is a system service that measures the firmware
product UUID (as provided by one of SMBIOS, Devicetree, …) into a NvPCR named
<literal>hardware</literal>.</para>
<para><filename>systemd-pcrfs-root.service</filename> and <filename>systemd-pcrfs@.service</filename> are
services that measure file system identity information (i.e. mount point, file system type, label and
UUID, partition label and UUID) into PCR 15. <filename>systemd-pcrfs-root.service</filename> does so for
@@ -156,8 +161,9 @@
<term><option>--pcr=</option></term>
<listitem><para>Takes the index of the PCR to extend. If <option>--machine-id</option> or
<option>--file-system=</option> are specified defaults to 15, otherwise defaults to 11. May not be
combined with <option>--nvpcr=</option>.</para>
<option>--file-system=</option> are specified defaults to 15, otherwise (and unless
<option>--product-id</option> is specified) defaults to 11. May not be combined with
<option>--nvpcr=</option>.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
@@ -167,7 +173,8 @@
<listitem><para>Takes a name of an NvPCR to extend. NvPCRs are additional PCRs implemented via TPM NV
indexes. The name should be a short string such as <literal>hardware</literal> or
<literal>disk-encryption</literal>. May not be combined with <option>--pcr=</option>.</para>
<literal>disk-encryption</literal>. If <option>--product-id</option> is specified defaults
<literal>hardware</literal>. May not be combined with <option>--pcr=</option>.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
@@ -214,6 +221,15 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--product-id</option></term>
<listitem><para>Instead of measuring a word specified on the command line into PCR 11, measure the
firmware's product UUID into an NvPCR named <literal>hardware</literal>.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--file-system=</option></term>

View File

@@ -27,6 +27,7 @@ static char *arg_tpm2_device = NULL;
static char **arg_banks = NULL;
static char *arg_file_system = NULL;
static bool arg_machine_id = false;
static bool arg_product_id = false;
static unsigned arg_pcr_index = UINT_MAX;
static char *arg_nvpcr_name = NULL;
static bool arg_varlink = false;
@@ -50,6 +51,7 @@ static int help(int argc, char *argv[], void *userdata) {
printf("%1$s [OPTIONS...] WORD\n"
"%1$s [OPTIONS...] --file-system=PATH\n"
"%1$s [OPTIONS...] --machine-id\n"
"%1$s [OPTIONS...] --product-id\n"
"\n%5$sExtend a TPM2 PCR with boot phase, machine ID, or file system ID.%6$s\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
@@ -61,6 +63,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --graceful Exit gracefully if no TPM2 device is found\n"
" --file-system=PATH Measure UUID/labels of file system into PCR 15\n"
" --machine-id Measure machine ID into PCR 15\n"
" --product-id Measure SMBIOS product ID into NvPCR 'hardware'\n"
" --early Run in early boot mode, without access to /var/\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
@@ -83,6 +86,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_GRACEFUL,
ARG_FILE_SYSTEM,
ARG_MACHINE_ID,
ARG_PRODUCT_ID,
ARG_EARLY,
};
@@ -96,6 +100,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "graceful", no_argument, NULL, ARG_GRACEFUL },
{ "file-system", required_argument, NULL, ARG_FILE_SYSTEM },
{ "machine-id", no_argument, NULL, ARG_MACHINE_ID },
{ "product-id", no_argument, NULL, ARG_PRODUCT_ID },
{ "early", no_argument, NULL, ARG_EARLY },
{}
};
@@ -176,6 +181,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_machine_id = true;
break;
case ARG_PRODUCT_ID:
arg_product_id = true;
break;
case ARG_EARLY:
arg_early = true;
break;
@@ -187,8 +196,8 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached();
}
if (!!arg_file_system + arg_machine_id > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-id may not be combined.");
if (!!arg_file_system + arg_machine_id + arg_product_id > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-id, --product-id may not be combined.");
if (arg_pcr_index != UINT_MAX && arg_nvpcr_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--pcr= and --nvpcr= may not be combined.");
@@ -198,10 +207,16 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
if (r > 0)
arg_varlink = true;
else if (arg_pcr_index == UINT_MAX && !arg_nvpcr_name)
arg_pcr_index = (arg_file_system || arg_machine_id) ?
TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */
TPM2_PCR_KERNEL_BOOT; /* → PCR 11 */
else if (arg_pcr_index == UINT_MAX && !arg_nvpcr_name) {
arg_pcr_index =
(arg_file_system || arg_machine_id) ? TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */
!arg_product_id ? TPM2_PCR_KERNEL_BOOT : /* → PCR 11 */
UINT_MAX;
r = free_and_strdup_warn(&arg_nvpcr_name, arg_product_id ? "hardware" : NULL);
if (r < 0)
return r;
}
return 1;
}
@@ -463,6 +478,17 @@ static int run(int argc, char *argv[]) {
return r;
event = TPM2_EVENT_MACHINE_ID;
} else if (arg_product_id) {
if (optind != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
r = pcrextend_product_id_word(&word);
if (r < 0)
return r;
event = TPM2_EVENT_PRODUCT_ID;
} else {
if (optind+1 != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");

View File

@@ -10,6 +10,7 @@
#include "errno-util.h"
#include "escape.h"
#include "fd-util.h"
#include "id128-util.h"
#include "log.h"
#include "mountpoint-util.h"
#include "pcrextend-util.h"
@@ -158,3 +159,24 @@ int pcrextend_machine_id_word(char **ret) {
*ret = TAKE_PTR(word);
return 0;
}
int pcrextend_product_id_word(char **ret) {
_cleanup_free_ char *word = NULL;
sd_id128_t pid;
int r;
assert(ret);
r = id128_get_product(&pid);
if (IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* No product UUID field, or an all-zero or all-0xFF UUID */
word = strdup("product-id:missing");
else if (r < 0)
return log_error_errno(r, "Failed to acquire product ID: %m");
else
word = strjoin("product-id:", SD_ID128_TO_STRING(pid));
if (!word)
return log_oom();
*ret = TAKE_PTR(word);
return 0;
}

View File

@@ -3,3 +3,4 @@
int pcrextend_file_system_word(const char *path, char **ret, char **ret_normalized_path);
int pcrextend_machine_id_word(char **ret);
int pcrextend_product_id_word(char **ret);

View File

@@ -6424,6 +6424,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA
[TPM2_EVENT_FILESYSTEM] = "filesystem",
[TPM2_EVENT_VOLUME_KEY] = "volume-key",
[TPM2_EVENT_MACHINE_ID] = "machine-id",
[TPM2_EVENT_PRODUCT_ID] = "product-id",
};
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);

View File

@@ -142,6 +142,7 @@ typedef enum Tpm2UserspaceEventType {
TPM2_EVENT_FILESYSTEM,
TPM2_EVENT_VOLUME_KEY,
TPM2_EVENT_MACHINE_ID,
TPM2_EVENT_PRODUCT_ID,
_TPM2_USERSPACE_EVENT_TYPE_MAX,
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
} Tpm2UserspaceEventType;

View File

@@ -35,7 +35,7 @@ executables += [
]
if conf.get('ENABLE_BOOTLOADER') == 1 and conf.get('HAVE_OPENSSL') == 1 and conf.get('HAVE_TPM2') == 1
nvpcrs = []
nvpcrs = [ 'hardware' ]
foreach n : nvpcrs
custom_target(
input : 'nvpcr/' + n + '.nvpcr.in',

View File

@@ -0,0 +1,5 @@
{
"name" : "hardware",
"algorithm" : "sha256",
"nvIndex" : {{TPM2_NVPCR_BASE + 0}}
}

View File

@@ -556,6 +556,11 @@ units = [
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['factory-reset.target.wants/'],
},
{
'file' : 'systemd-pcrproduct.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['sysinit.target.wants/'],
},
{
'file' : 'systemd-pcrphase-initrd.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2', 'ENABLE_INITRD'],

View File

@@ -0,0 +1,23 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=TPM PCR Product ID Measurement
Documentation=man:systemd-pcrproduct.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
After=tpm2.target
Before=sysinit.target shutdown.target
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-pcrextend --graceful --product-id