]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pcrextend: make use new nvindex-based PCRs
authorLennart Poettering <lennart@poettering.net>
Mon, 3 Jun 2024 19:44:50 +0000 (21:44 +0200)
committerLennart Poettering <lennart@poettering.net>
Sun, 2 Nov 2025 20:14:35 +0000 (21:14 +0100)
catalog/systemd.catalog.in
man/systemd-pcrphase.service.xml
src/pcrextend/pcrextend.c
src/shared/varlink-io.systemd.PCRExtend.c
src/systemd/sd-messages.h

index cb1142b1940d9f69380ed950081fc02b07cc75eb..4152112078c630ba853b8b4467d0756be11af039 100644 (file)
@@ -743,6 +743,19 @@ PCRs ares extended with a different string, to ensure that security
 policies for TPM-bound secrets and other resources are limited to specific
 phases of the runtime.
 
+-- 4c2e46d266a747c6ac1460aa54484fa7
+Subject: TPM NvPCR Extended
+Defined-By: systemd
+Support: %SUPPORT_URL%
+
+The Trusted Platform Module's (TPM) additional Platform Configuration Register
+(PCR) stored in non-volatile indexes (NV Indexes) @NVPCR@, has been extended
+with the string '@MEASURING@'.
+
+System state, configuration and properties are cryptographically measured into
+the security chip in an irreversible way to provide local and remote
+attestation of system state and identity.
+
 -- f9b0be465ad540d0850ad32172d57c21
 Subject: Memory Trimmed
 Defined-By: systemd
index e301435f8ea070fbfc4122e134922291f4444193..a00eda08bb1e15f6e24b5ee1577c86fa0f31f99d 100644 (file)
     <title>Options</title>
 
     <para>The <filename>/usr/lib/systemd/system-pcrextend</filename> executable may also be invoked from the
-    command line, where it expects the word to extend into PCR 11, as well as the following switches:</para>
+    command line, where it expects the word to extend into a PCR or NvPCR, as well as the following
+    switches:</para>
 
     <variablelist>
       <varlistentry>
         <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.</para>
+        <option>--file-system=</option> are specified defaults to 15, otherwise defaults to 11. May not be
+        combined with <option>--nvpcr=</option>.</para>
 
         <xi:include href="version-info.xml" xpointer="v255"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--nvpcr=</option></term>
+
+        <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>
+
+        <xi:include href="version-info.xml" xpointer="v259"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--tpm2-device=<replaceable>PATH</replaceable></option></term>
 
         <xi:include href="version-info.xml" xpointer="v253"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--early</option></term>
+
+        <listitem><para>Selects early-boot mode. Specifically this means that the NvPCR anchor secret is not
+        attempted to be written into <filename>/var/lib/</filename> and the boot loader partition in addition
+        to <filename>/run/</filename>. (Unlike the latter the former are generally not mounted and writable
+        during early boot or in the initrd.)</para>
+
+        <xi:include href="version-info.xml" xpointer="v259"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--machine-id</option></term>
 
 
         <xi:include href="version-info.xml" xpointer="v252"/></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><filename>/usr/lib/nvpcr/*.nvpcr</filename></term>
+
+        <listitem><para>Definition files for NvPCRs.</para>
+        <xi:include href="version-info.xml" xpointer="v259"/></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index cc50ad3ff79185e0ce3431bb86c2c83811d11ddc..68d90d4e4a325ff1c579ad59d7031e82e43b1c8c 100644 (file)
@@ -28,11 +28,14 @@ static char **arg_banks = NULL;
 static char *arg_file_system = NULL;
 static bool arg_machine_id = false;
 static unsigned arg_pcr_index = UINT_MAX;
+static char *arg_nvpcr_name = NULL;
 static bool arg_varlink = false;
+static bool arg_early = false;
 
 STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_nvpcr_name, freep);
 
 #define EXTENSION_STRING_SAFE_LIMIT 1024
 
@@ -53,10 +56,12 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --version           Print version\n"
                "     --bank=DIGEST       Select TPM PCR bank (SHA1, SHA256)\n"
                "     --pcr=INDEX         Select TPM PCR index (0…23)\n"
+               "     --nvpcr=NAME        Select TPM PCR mode nvindex name\n"
                "     --tpm2-device=PATH  Use specified TPM2 device\n"
                "     --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"
+               "     --early             Run in early boot mode, without access to /var/\n"
                "\nSee the %2$s for details.\n",
                program_invocation_short_name,
                link,
@@ -73,10 +78,12 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_VERSION = 0x100,
                 ARG_BANK,
                 ARG_PCR,
+                ARG_NVPCR,
                 ARG_TPM2_DEVICE,
                 ARG_GRACEFUL,
                 ARG_FILE_SYSTEM,
                 ARG_MACHINE_ID,
+                ARG_EARLY,
         };
 
         static const struct option options[] = {
@@ -84,10 +91,12 @@ static int parse_argv(int argc, char *argv[]) {
                 { "version",     no_argument,       NULL, ARG_VERSION     },
                 { "bank",        required_argument, NULL, ARG_BANK        },
                 { "pcr",         required_argument, NULL, ARG_PCR         },
+                { "nvpcr",       required_argument, NULL, ARG_NVPCR       },
                 { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
                 { "graceful",    no_argument,       NULL, ARG_GRACEFUL    },
                 { "file-system", required_argument, NULL, ARG_FILE_SYSTEM },
                 { "machine-id",  no_argument,       NULL, ARG_MACHINE_ID  },
+                { "early",       no_argument,       NULL, ARG_EARLY       },
                 {}
         };
 
@@ -127,6 +136,15 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_pcr_index = r;
                         break;
 
+                case ARG_NVPCR:
+                        if (!tpm2_nvpcr_name_is_valid(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR name is not valid: %s", optarg);
+
+                        r = free_and_strdup_warn(&arg_nvpcr_name, optarg);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_TPM2_DEVICE: {
                         _cleanup_free_ char *device = NULL;
 
@@ -158,6 +176,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_machine_id = true;
                         break;
 
+                case ARG_EARLY:
+                        arg_early = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -165,15 +187,18 @@ static int parse_argv(int argc, char *argv[]) {
                         assert_not_reached();
                 }
 
-        if (arg_file_system && arg_machine_id)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system= and --machine-id may not be combined.");
+        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_pcr_index != UINT_MAX && arg_nvpcr_name)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--pcr= and --nvpcr= may not be combined.");
 
         r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
         if (r < 0)
                 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)
+        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 */
@@ -198,7 +223,34 @@ static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) {
         return 0;
 }
 
-static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2UserspaceEventType event) {
+static int escape_and_truncate_data(const void *data, size_t size, char **ret) {
+        _cleanup_free_ char *safe = NULL;
+
+        assert(data || size == 0);
+
+        if (size > EXTENSION_STRING_SAFE_LIMIT) {
+                safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT);
+                if (!safe)
+                        return -ENOMEM;
+
+                if (!strextend(&safe, "..."))
+                        return -ENOMEM;
+        } else {
+                safe = cescape_length(data, size);
+                if (!safe)
+                        return -ENOMEM;
+        }
+
+        *ret = TAKE_PTR(safe);
+        return 0;
+}
+
+static int extend_pcr_now(
+                unsigned pcr,
+                const void *data,
+                size_t size,
+                Tpm2UserspaceEventType event) {
+
         _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
         int r;
 
@@ -218,18 +270,8 @@ static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2Userspace
                 return log_oom();
 
         _cleanup_free_ char *safe = NULL;
-        if (size > EXTENSION_STRING_SAFE_LIMIT) {
-                safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT);
-                if (!safe)
-                        return log_oom();
-
-                if (!strextend(&safe, "..."))
-                        return log_oom();
-        } else {
-                safe = cescape_length(data, size);
-                if (!safe)
-                        return log_oom();
-        }
+        if (escape_and_truncate_data(data, size, &safe) < 0)
+                return log_oom();
 
         log_debug("Measuring '%s' into PCR index %u, banks %s.", safe, pcr, joined_banks);
 
@@ -247,8 +289,55 @@ static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2Userspace
         return 0;
 }
 
+static int extend_nvpcr_now(
+                const char *name,
+                const void *data,
+                size_t size,
+                Tpm2UserspaceEventType event) {
+
+        _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
+        int r;
+
+        r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
+        if (r < 0)
+                return r;
+
+        _cleanup_free_ char *safe = NULL;
+        if (escape_and_truncate_data(data, size, &safe) < 0)
+                return log_oom();
+
+        log_debug("Measuring '%s' into NvPCR index '%s'.", safe, name);
+
+        r = tpm2_nvpcr_extend_bytes(c, /* session= */ NULL, name, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe);
+        if (r == -ENETDOWN) {
+                /* NvPCR is not initialized yet. Let's do this now. */
+
+                _cleanup_(iovec_done_erase) struct iovec anchor_secret = {};
+                r = tpm2_nvpcr_acquire_anchor_secret(&anchor_secret, /* sync_secondary= */ !arg_early);
+                if (r < 0)
+                        return r;
+
+                r = tpm2_nvpcr_initialize(c, /* session= */ NULL, name, &anchor_secret);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to extend NvPCR index '%s' with anchor secret: %m", name);
+
+                r = tpm2_nvpcr_extend_bytes(c, /* session= */ NULL, name, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe);
+        }
+        if (r < 0)
+                return log_error_errno(r, "Could not extend NvPCR: %m");
+
+        log_struct(LOG_INFO,
+                   "MESSAGE_ID=" SD_MESSAGE_TPM_NVPCR_EXTEND_STR,
+                   LOG_MESSAGE("Extended NvPCR index '%s' with '%s'.", name, safe),
+                   "MEASURING=%s", safe,
+                   "NVPCR=%u", name);
+
+        return 0;
+}
+
 typedef struct MethodExtendParameters {
         unsigned pcr;
+        const char *nvpcr;
         const char *text;
         struct iovec data;
 } MethodExtendParameters;
@@ -262,9 +351,10 @@ static void method_extend_parameters_done(MethodExtendParameters *p) {
 static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
 
         static const sd_json_dispatch_field dispatch_table[] = {
-                { "pcr",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,         offsetof(MethodExtendParameters, pcr),  SD_JSON_MANDATORY },
-                { "text", SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0              },
-                { "data", SD_JSON_VARIANT_STRING,        json_dispatch_unbase64_iovec,  offsetof(MethodExtendParameters, data), 0              },
+                { "pcr",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,         offsetof(MethodExtendParameters, pcr),   0 },
+                { "nvpcr", SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(MethodExtendParameters, nvpcr), 0 },
+                { "text",  SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(MethodExtendParameters, text),  0 },
+                { "data",  SD_JSON_VARIANT_STRING,        json_dispatch_unbase64_iovec,  offsetof(MethodExtendParameters, data),  0 },
                 {}
         };
         _cleanup_(method_extend_parameters_done) MethodExtendParameters p = {
@@ -278,19 +368,38 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va
         if (r != 0)
                 return r;
 
-        if (!TPM2_PCR_INDEX_VALID(p.pcr))
+        if (p.nvpcr) {
+                /* Specifying both nvpcr name and pcr doesn't make sense */
+                if (p.pcr != UINT_MAX)
+                        return sd_varlink_error_invalid_parameter_name(link, "nvpcr");
+
+                if (!tpm2_nvpcr_name_is_valid(p.nvpcr))
+                        return sd_varlink_error_invalid_parameter_name(link, "nvpcr");
+
+        } else if (!TPM2_PCR_INDEX_VALID(p.pcr))
                 return sd_varlink_error_invalid_parameter_name(link, "pcr");
 
+        struct iovec *extend_iovec, text_iovec;
+
         if (p.text) {
                 /* Specifying both the text string and the binary data is not allowed */
                 if (p.data.iov_base)
                         return sd_varlink_error_invalid_parameter_name(link, "data");
 
-                r = extend_now(p.pcr, p.text, strlen(p.text), _TPM2_USERSPACE_EVENT_TYPE_INVALID);
+                text_iovec = IOVEC_MAKE_STRING(p.text);
+                extend_iovec = &text_iovec;
+
         } else if (p.data.iov_base)
-                r = extend_now(p.pcr, p.data.iov_base, p.data.iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
+                extend_iovec = &p.data;
         else
                 return sd_varlink_error_invalid_parameter_name(link, "text");
+
+        if (p.nvpcr) {
+                r = extend_nvpcr_now(p.nvpcr, extend_iovec->iov_base, extend_iovec->iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
+                if (r == -ENOENT)
+                        return sd_varlink_error(link, "io.systemd.PCRExtend.NoSuchNvPCR", NULL);
+        } else
+                r = extend_pcr_now(p.pcr, extend_iovec->iov_base, extend_iovec->iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
         if (r < 0)
                 return r;
 
@@ -354,7 +463,6 @@ static int run(int argc, char *argv[]) {
                         return r;
 
                 event = TPM2_EVENT_MACHINE_ID;
-
         } else {
                 if (optind+1 != argc)
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
@@ -386,9 +494,12 @@ static int run(int argc, char *argv[]) {
                 return EXIT_SUCCESS;
         }
 
-        r = extend_now(arg_pcr_index, word, strlen(word), event);
+        if (arg_nvpcr_name)
+                r = extend_nvpcr_now(arg_nvpcr_name, word, strlen(word), event);
+        else
+                r = extend_pcr_now(arg_pcr_index, word, strlen(word), event);
         if (r < 0)
-                return log_error_errno(r, "Failed to create TPM2 context: %m");
+                return r;
 
         return EXIT_SUCCESS;
 }
index d104fc0853b3f9c1159cfdba0a89644f6ecb9d0d..84ee2e731ba324337687cffff4e466c07f82a2e1 100644 (file)
@@ -6,14 +6,18 @@ static SD_VARLINK_DEFINE_METHOD(
                 Extend,
                 SD_VARLINK_FIELD_COMMENT("PCR number to extend, in range of 0…23"),
                 SD_VARLINK_DEFINE_INPUT(pcr, SD_VARLINK_INT, 0),
+                SD_VARLINK_DEFINE_INPUT(nvpcr, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Text string to measure. (Specify either this, or the 'data' field below, not both)"),
                 SD_VARLINK_DEFINE_INPUT(text, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Binary data to measure, encoded in Base64. (Specify either this, or the 'text' field above, not both)"),
                 SD_VARLINK_DEFINE_INPUT(data, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
 
+static SD_VARLINK_DEFINE_ERROR(NoSuchNvPCR);
+
 SD_VARLINK_DEFINE_INTERFACE(
                 io_systemd_PCRExtend,
                 "io.systemd.PCRExtend",
                 SD_VARLINK_INTERFACE_COMMENT("TPM PCR Extension APIs"),
                 SD_VARLINK_SYMBOL_COMMENT("Measure some text or binary data into a PCR"),
-                &vl_method_Extend);
+                &vl_method_Extend,
+                &vl_error_NoSuchNvPCR);
index 02edf9facf71b004ca38af8ca3e8819fa851fb8a..6dc7dd527a09a1b144620bc135c70a98fd6ea01b 100644 (file)
@@ -272,6 +272,8 @@ _SD_BEGIN_DECLARATIONS;
 
 #define SD_MESSAGE_TPM_PCR_EXTEND                     SD_ID128_MAKE(3f,7d,5e,f3,e5,4f,43,02,b4,f0,b1,43,bb,27,0c,ab)
 #define SD_MESSAGE_TPM_PCR_EXTEND_STR                 SD_ID128_MAKE_STR(3f,7d,5e,f3,e5,4f,43,02,b4,f0,b1,43,bb,27,0c,ab)
+#define SD_MESSAGE_TPM_NVPCR_EXTEND                   SD_ID128_MAKE(4c,2e,46,d2,66,a7,47,c6,ac,14,60,aa,54,48,4f,a7)
+#define SD_MESSAGE_TPM_NVPCR_EXTEND_STR               SD_ID128_MAKE_STR(4c,2e,46,d2,66,a7,47,c6,ac,14,60,aa,54,48,4f,a7)
 
 #define SD_MESSAGE_MEMORY_TRIM                        SD_ID128_MAKE(f9,b0,be,46,5a,d5,40,d0,85,0a,d3,21,72,d5,7c,21)
 #define SD_MESSAGE_MEMORY_TRIM_STR                    SD_ID128_MAKE_STR(f9,b0,be,46,5a,d5,40,d0,85,0a,d3,21,72,d5,7c,21)