]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pcrextend: skip measurement gracefully when the TPM can't be used
authorIvan Kruglov <mail@ikruglov.com>
Wed, 10 Jun 2026 15:12:50 +0000 (08:12 -0700)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 12 Jun 2026 13:39:32 +0000 (15:39 +0200)
So far --graceful only short-circuited when no TPM was present at all (the
!tpm2_is_mostly_supported() check). If a TPM is present but can't actually be
used for the measurement we want, the measurement still failed hard. For
systemd-pcrextend instances ordered before sysinit.target this pushes the
system to degraded and blocks boot, which defeats the purpose of --graceful.

Make the two extend helpers report every "TPM is present-or-absent but cannot
be used for this measurement" condition with a single errno, -EOPNOTSUPP: no
usable PCR bank, no TPM device, missing crypto (e.g. AES-128-CFB), no NvPCR
support, and OpenSSL-less builds. tpm2_context_new_or_warn() reports a missing
device as -ENOENT, so each helper translates that to -EOPNOTSUPP at the call
site, keeping every errno single-meaning.

Co-developed-by: Claude Opus 4.8 <noreply@anthropic.com>
src/pcrextend/pcrextend.c

index d5d4a23d51a77662f6b704e599f3000266fd6c76..b42a190a9f86159fb96e6ae832852b87e0cc8253 100644 (file)
@@ -261,6 +261,48 @@ static int escape_and_truncate_data(const void *data, size_t size, char **ret) {
         return 0;
 }
 
+static int tpm2_context_new_for_measurement(Tpm2Context **ret) {
+        int r;
+
+        assert(ret);
+
+        /* Wrapper around tpm2_context_new_or_warn() that translates a missing TPM device from -ENOENT to
+         * -EOPNOTSUPP, for two reasons:
+         *
+         *  - It disambiguates -ENOENT on the NvPCR path. There -ENOENT also means "no such NvPCR definition"
+         *    (from tpm2_nvpcr_extend_bytes() → nvpcr_data_load()), which vl_method_extend() maps to the
+         *    io.systemd.PCRExtend.NoSuchNvPCR error. If "no TPM device" stayed -ENOENT it would be
+         *    misreported as a missing NvPCR definition. So we keep each errno single-meaning: -ENOENT = "no
+         *    such NvPCR definition", -EOPNOTSUPP = "TPM cannot be used for this measurement" (also what
+         *    tpm2_context_new_or_warn() already returns for missing crypto / an OpenSSL-less build, and what
+         *    extend_pcr_now() returns when no PCR bank is enabled).
+         *
+         *  - It lets the --graceful skip in run() match a single errno (-EOPNOTSUPP).
+         *
+         * We deliberately translate *only* -ENOENT here, not every "TPM unusable" errno:
+         *
+         *  - Genuine absence (no TPM hardware, no tpm2 libraries) never reaches this point under --graceful:
+         *    run() bails out earlier at the tpm2_is_mostly_supported() guard, which requires the kernel
+         *    driver + tpm subsystem + libtss2 esys/rc/mu. So broadening the set buys nothing for the common
+         *    "no TPM" case.
+         *
+         *  - -ENOPKG (TCTI driver libtss2-tcti-device.so.0 not loadable) can therefore only happen when the
+         *    rest of the stack *is* present — i.e. a half-installed tpm2-tss, a misconfiguration.
+         *
+         *  - -ENOTRECOVERABLE (TCTI/Esys init or TPM startup failed) means a TPM is present but
+         *    malfunctioning.
+         *
+         * Both of the latter are real faults we want to surface and fail on, not silently skip:
+         * --graceful's contract is "no TPM2 device is found", i.e. absence, not breakage. -EINVAL (bad
+         * device string), -ENOMEM, etc. likewise stay hard errors. */
+
+        r = tpm2_context_new_or_warn(arg_tpm2_device, ret);
+        if (r == -ENOENT)
+                return -EOPNOTSUPP;
+
+        return r;
+}
+
 static int extend_pcr_now(
                 uint32_t pcr_mask,
                 const void *data,
@@ -272,7 +314,7 @@ static int extend_pcr_now(
 
         assert(pcr_mask != 0);
 
-        r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
+        r = tpm2_context_new_for_measurement(&c);
         if (r < 0)
                 return r;
 
@@ -280,7 +322,7 @@ static int extend_pcr_now(
         if (r < 0)
                 return r;
         if (strv_isempty(arg_banks)) /* Still none? */
-                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate.");
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Found a TPM2 without enabled PCR banks. Can't operate.");
 
         _cleanup_free_ char *joined_banks = NULL;
         joined_banks = strv_join(arg_banks, ", ");
@@ -320,7 +362,7 @@ static int extend_nvpcr_now(
 
         assert(name);
 
-        r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
+        r = tpm2_context_new_for_measurement(&c);
         if (r < 0)
                 return r;
 
@@ -556,6 +598,14 @@ static int run(int argc, char *argv[]) {
                 r = extend_nvpcr_now(arg_nvpcr_name, word, strlen(word), event);
         else
                 r = extend_pcr_now(arg_pcr_mask, word, strlen(word), event);
+        /* Both extend paths report "TPM cannot be used for this measurement" (no PCR bank, missing crypto,
+         * no TPM device — see tpm2_context_new_for_measurement()) as -EOPNOTSUPP. Under --graceful we skip
+         * those rather than fail and block boot. Genuine faults keep their own errno and are never
+         * suppressed. */
+        if (arg_graceful && r == -EOPNOTSUPP) {
+                log_notice_errno(r, "TPM2 cannot be used for measurement (no usable PCR bank, missing device, or missing crypto support), skipping gracefully.");
+                return EXIT_SUCCESS;
+        }
         if (r < 0)
                 return r;