pcrextend: skip measurement gracefully when the TPM can't be used

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>
This commit is contained in:
Ivan Kruglov
2026-06-12 15:39:32 +02:00
committed by Zbigniew Jędrzejewski-Szmek
parent f61e1e5cf4
commit c925e405f3
+53 -3
View 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;