]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pcrextend: automatically measure SMBIOS product ID at boot
authorLennart Poettering <lennart@poettering.net>
Tue, 4 Jun 2024 10:01:10 +0000 (12:01 +0200)
committerLennart Poettering <lennart@poettering.net>
Sun, 2 Nov 2025 20:14:35 +0000 (21:14 +0100)
Now that PCRs are not that expensive anymore, let's use them to measure
the SMBIOS product ID to one.

man/systemd-pcrphase.service.xml
src/pcrextend/pcrextend.c
src/shared/pcrextend-util.c
src/shared/pcrextend-util.h
src/shared/tpm2-util.c
src/shared/tpm2-util.h
src/tpm2-setup/meson.build
src/tpm2-setup/nvpcr/hardware.nvpcr.in [new file with mode: 0644]
units/meson.build
units/systemd-pcrproduct.service.in [new file with mode: 0644]

index a00eda08bb1e15f6e24b5ee1577c86fa0f31f99d..b95007dfcba6448dcce4ceb12a45effaf54b415a 100644 (file)
     <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>
     (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
         <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>
 
         <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>
         <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>
 
index 68d90d4e4a325ff1c579ad59d7031e82e43b1c8c..641b1e8cbe6ad82847a23bb34057ced1bd3b87d4 100644 (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.");
index e1e80a93eeb61fb819e11f7a145c8ec481406acd..15e0da822a6ef1c0bf326d2128a1b19674300419 100644 (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;
+}
index 7dd612b8fb8ffbd60ce872bc14ead2de6a6f95e7..88bd23c10d98304199cc80c5dd60a3756615937a 100644 (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);
index 49da185271986b1b3d23f5a8721e4da21d74e77a..da74ca06a5ad1a8af223a8008cc098a8afa7e9e5 100644 (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);
index 564a3e46ad3855186e2e8926bca314870a4c4b44..667d783cea5306f61db8e2943f2f52b47304d927 100644 (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;
index 36082486d30a83b5956dbec98aa2cb7684df31e8..0221a5fba16d5465b331a898df9bce81a2f35bec 100644 (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',
diff --git a/src/tpm2-setup/nvpcr/hardware.nvpcr.in b/src/tpm2-setup/nvpcr/hardware.nvpcr.in
new file mode 100644 (file)
index 0000000..ddeb7f9
--- /dev/null
@@ -0,0 +1,5 @@
+{
+    "name" : "hardware",
+    "algorithm" : "sha256",
+    "nvIndex" : {{TPM2_NVPCR_BASE + 0}}
+}
index ba2dfcab065f71d35ada92807ccd227e8cd5c12d..bd788f6d0b671f6c2935c06dbb7c09d1ed9e9386 100644 (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'],
diff --git a/units/systemd-pcrproduct.service.in b/units/systemd-pcrproduct.service.in
new file mode 100644 (file)
index 0000000..a70fff1
--- /dev/null
@@ -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