]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tpm2: optionally disable TPMA_NV_ORDERLY for NvPCRs
authorLennart Poettering <lennart@amutable.com>
Thu, 2 Jul 2026 06:52:16 +0000 (08:52 +0200)
committerLennart Poettering <lennart@amutable.com>
Thu, 2 Jul 2026 19:51:54 +0000 (21:51 +0200)
NVIndexes in TPMs can operate in two modes:

1. Backed by TPM RAM. In this case they are only written to NVRAM on an
   orderly TPM shutdown when the system goes down. (TPMA_NV_ORDERLY flag
   is on)

2. Backed by TPM NVRAM. In this case the nvindex value is written to NVRAM
   on every write, and things are not delayed until orderly shutdown.

Normally mode 1 sounds like the obvious choice for NvPCRs, which reset
to zero anyway at boot. However, things are more complicated since
real-life TPMs tend to have a lot less RAM than NVRAM (both are
constrained but RAM even more than NVRAM). Hence there's value in using
NVRAM right-away. However, writing to NVRAM all the time means wearing
it out (since NVRAM is more vulnerable to that).

So far we unconditionally went for mode 1, but ran into space
constraints of RAM due to that.

Let's improve things a bit, and use orderly mode for NvPCRs we expect to
write many times, and non-orderly mode for those we expect to write only
a small, fixed number of times at boot, and not anymore during runtime.
Right now, this is only the "hardware" NvPCR, which measures hw identity
at boot.

Hopefully, this stretches available resources a bit further.

This also makes sure if the flag was set differently on allocation as
we'd set now, we accept it and won't complain, to make upgrades safe.

Suggested by Andreas Fuchs.

docs/TPM2_PCR_MEASUREMENTS.md
src/shared/tpm2-util.c
src/tpm2-setup/nvpcr/hardware.nvpcr.in

index faa918f6319f6b62188906e58c17aa04af24e550..87d8d3b6757340c0bcac72ee5eadab938ec6423e 100644 (file)
@@ -74,6 +74,18 @@ fields are:
   least important ones are skipped gracefully rather than the allocation failing
   arbitrarily. Ties are broken by name. Priority does not affect the NV index,
   the algorithm, or anything measured into the NvPCR.
+* `orderly` — a boolean, defaulting to `true`. It controls whether the NV index
+  is allocated with the `TPMA_NV_ORDERLY` attribute set, which selects whether
+  the TPM keeps the NV index in RAM or in persistent memory (NVRAM). On
+  physical TPMs RAM is typically much more constrained than persistent memory,
+  but persistent memory is subject to wear. We hence prefer `TPMA_NV_ORDERLY`
+  disabled (i.e. NVRAM) for NvPCRs that are written only once each boot — which
+  translates into a conservative number of write cycles over the lifetime of a
+  TPM — but enabled (i.e. RAM) for NvPCRs we expect to be written many times
+  during runtime, so that we minimize wear. This reflects real-life experience
+  where the RAM in TPMs is so constrained that allocating many NvPCRs in TPM
+  RAM simply doesn't work. For now, only the `hardware` NvPCR (which is written
+  just once, early at boot) sets this flag to false.
 
 There's one complication: these NV indexes (like any NV indexes) can be deleted
 by anyone with access to the TPM, and then be recreated. This could be used to
index 3a9fbaa7e47e3eee24b7ea2905d18050589efa59..1920d6f171927ec193e5fe9292ed055dfe7de7fa 100644 (file)
@@ -7183,6 +7183,7 @@ static int tpm2_define_nvpcr_nv_index(
                 const Tpm2Handle *session,
                 TPM2_HANDLE nv_index,
                 TPMI_ALG_HASH algorithm,
+                bool orderly,
                 Tpm2Handle **ret_nv_handle) {
 
         _cleanup_(tpm2_handle_freep) Tpm2Handle *new_handle = NULL;
@@ -7215,7 +7216,7 @@ static int tpm2_define_nvpcr_nv_index(
                         .nvIndex = nv_index,
                         .nameAlg = algorithm,
                         .attributes = TPMA_NV_CLEAR_STCLEAR |
-                                      TPMA_NV_ORDERLY |
+                                      (orderly ? TPMA_NV_ORDERLY : 0) |
                                       TPMA_NV_OWNERWRITE |
                                       TPMA_NV_AUTHWRITE |
                                       TPMA_NV_OWNERREAD |
@@ -7259,7 +7260,7 @@ static int tpm2_define_nvpcr_nv_index(
                 if (nv_public_real->size < endoffsetof_field(TPMS_NV_PUBLIC, attributes) + sizeof_field(TPMS_NV_PUBLIC, dataSize) ||
                     nv_public_real->nvPublic.nvIndex != public_info.nvPublic.nvIndex ||
                     nv_public_real->nvPublic.nameAlg != public_info.nvPublic.nameAlg ||
-                    ((nv_public_real->nvPublic.attributes ^ public_info.nvPublic.attributes) & ~TPMA_NV_WRITTEN) != 0 ||
+                    ((nv_public_real->nvPublic.attributes ^ public_info.nvPublic.attributes) & ~(TPMA_NV_WRITTEN|TPMA_NV_ORDERLY)) != 0 ||
                     nv_public_real->nvPublic.dataSize != public_info.nvPublic.dataSize)
                         return log_debug_errno(SYNTHETIC_ERRNO(EEXIST),
                                                "Public data of nvindex 0x%x does not match our expectations.", nv_index);
@@ -7976,6 +7977,7 @@ typedef struct NvPCRData {
         uint16_t algorithm;
         uint32_t nv_index;
         uint64_t priority;
+        bool orderly;
 } NvPCRData;
 
 static void nvpcr_data_done(NvPCRData *d) {
@@ -8016,12 +8018,14 @@ static int nvpcr_data_load(const char *name, NvPCRData *ret) {
                 { "algorithm", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_tpm2_algorithm, offsetof(NvPCRData, algorithm), 0                 },
                 { "nvIndex",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32,      offsetof(NvPCRData, nv_index),  SD_JSON_MANDATORY },
                 { "priority",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,      offsetof(NvPCRData, priority),  0                 },
+                { "orderly",   SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool,     offsetof(NvPCRData, orderly),   0                 },
                 {},
         };
 
         _cleanup_(nvpcr_data_done) NvPCRData p = {
                 .algorithm = TPM2_ALG_SHA256,
                 .priority = TPM2_NVPCR_PRIORITY_DEFAULT,
+                .orderly = true,
         };
         r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
         if (r < 0)
@@ -8652,6 +8656,7 @@ int tpm2_nvpcr_initialize(
                         session,
                         p.nv_index,
                         p.algorithm,
+                        p.orderly,
                         &nv_handle);
         if (r < 0)
                 return r;
index bb4a3afa6957a6c6a34822097b8c1733cd17e157..0fdf32d309459ed6d8026656cd1522b9b8a0851b 100644 (file)
@@ -2,5 +2,6 @@
     "name" : "hardware",
     "algorithm" : "sha256",
     "nvIndex" : {{TPM2_NVPCR_BASE + 0}},
-    "priority" : 500
+    "priority" : 500,
+    "orderly" : false
 }