From: Paul Meyer Date: Tue, 14 Jul 2026 11:26:56 +0000 (+0200) Subject: tpm2-util: keep the measurement log's torn-write marker intact X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=afff448cf47659a91a8b2ba74e26449dead48f32;p=thirdparty%2Fsystemd.git tpm2-util: keep the measurement log's torn-write marker intact The userspace measurement log carries a sticky-bit marker while a writer is between updating a measurement register and appending the matching record, so that a writer dying in between leaves the log detectably incomplete. However, the next successful writer used to clear the marker again after appending its own record, erasing the evidence that an earlier writer had died and the log is still missing a record. Keep the marker set in that case instead; the new record is appended and synced regardless. The marker likewise stays set if the measurement itself fails, so that any non-clean completion remains flagged: a spurious flag on a failed but harmless measurement is preferable to erasing evidence of a real gap, and PCR replay stays authoritative either way. Finally, warn when systemd-pcrlock loads a marked log, so the resulting PCR validation failures come with a hint at their cause. Signed-off-by: Paul Meyer --- diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 2d084d8eb18..cb39737b1ec 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -1179,6 +1179,7 @@ static int event_log_load_userspace(EventLog *el) { _cleanup_free_ char *b = NULL; bool beginning = true; const char *path; + struct stat st; size_t bn = 0; int r; @@ -1197,6 +1198,13 @@ static int event_log_load_userspace(EventLog *el) { if (flock(fileno(f), LOCK_SH) < 0) return log_error_errno(errno, "Failed to lock userspace TPM measurement log file: %m"); + /* The sticky bit marks the log as incomplete: a writer died between updating a PCR and appending + * the matching record. Load the log anyway, the affected PCRs will simply fail validation. */ + if (fstat(fileno(f), &st) < 0) + log_warning_errno(errno, "Failed to stat userspace TPM measurement log file, cannot determine whether it is complete, ignoring: %m"); + else if (FLAGS_SET(st.st_mode, S_ISVTX)) + log_warning("Userspace TPM measurement log file is marked as incomplete, PCR validation may fail."); + for (;;) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; EventLogRecord *record; diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 3e393f053aa..c43616df51e 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -7778,8 +7778,9 @@ static int tpm2_userspace_log_dirty(int fd) { /* We set the sticky bit when we are about to append to the log file. We'll unset it afterwards * again. If we manage to take a lock on a file that has it set we know we didn't write it fully and - * it is corrupted. Ideally we'd like to use user xattrs for this, but unfortunately tmpfs (which is - * our assumed backend fs) doesn't know user xattrs. */ + * it is corrupted. We return -ESTALE then; callers shall not reset the marker when they are done, + * so that the incompleteness remains detectable. Ideally we'd like to use user xattrs for this, but + * unfortunately tmpfs (which is our assumed backend fs) doesn't know user xattrs. */ if (fstat(fd, &st) < 0) return log_debug_errno(errno, "Failed to fstat TPM log file, ignoring: %m"); @@ -7793,7 +7794,7 @@ static int tpm2_userspace_log_dirty(int fd) { return 0; } -static int tpm2_userspace_log_clean(int fd) { +static int tpm2_userspace_log_clean(int fd, bool reset_marker) { int r; if (fd < 0) /* Apparently tpm2_local_log_open() failed earlier, let's not complain again */ @@ -7802,6 +7803,12 @@ static int tpm2_userspace_log_clean(int fd) { if (fsync(fd) < 0) return log_debug_errno(errno, "Failed to sync JSON data: %m"); + /* If the dirty marker was already set when we acquired the log, an earlier writer died before + * writing its record, i.e. the log is missing a record. Keep the marker then, so that the + * incompleteness remains detectable. */ + if (!reset_marker) + return 0; + /* Unset S_ISVTX again */ if (fchmod(fd, 0600) < 0) return log_debug_errno(errno, "Failed to chmod() TPM log file, ignoring: %m"); @@ -7820,7 +7827,8 @@ static int tpm2_userspace_log( const char *nv_index_name, const TPML_DIGEST_VALUES *values, Tpm2UserspaceEventType event_type, - const char *description) { + const char *description, + bool reset_marker) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *array = NULL; _cleanup_free_ char *f = NULL; @@ -7909,7 +7917,7 @@ static int tpm2_userspace_log( if (r < 0) return log_debug_errno(r, "Failed to write JSON data to log: %m"); - r = tpm2_userspace_log_clean(fd); + r = tpm2_userspace_log_clean(fd, reset_marker); if (r < 0) return r; @@ -7986,7 +7994,7 @@ int tpm2_pcr_extend_bytes( * and our measurement and change either */ log_fd = tpm2_userspace_log_open(); - (void) tpm2_userspace_log_dirty(log_fd); + bool reset_marker = tpm2_userspace_log_dirty(log_fd) >= 0; rc = sym_Esys_PCR_Extend( c->esys_context, ESYS_TR_PCR0 + pcr_index, @@ -8009,7 +8017,8 @@ int tpm2_pcr_extend_bytes( /* nv_index_name= */ NULL, &values, event_type, - description); + description, + reset_marker); return 0; #else /* HAVE_OPENSSL */ @@ -8179,7 +8188,7 @@ static int nvpcr_extend_bytes( log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", p.nv_index); - (void) tpm2_userspace_log_dirty(log_fd); + bool reset_marker = tpm2_userspace_log_dirty(log_fd) >= 0; r = tpm2_extend_nvpcr_nv_index( c, @@ -8203,7 +8212,8 @@ static int nvpcr_extend_bytes( name, &digest_values, event_type, - description); + description, + reset_marker); return 0; #else /* HAVE_OPENSSL */ @@ -8739,7 +8749,7 @@ int tpm2_nvpcr_initialize( log_debug("Successfully acquired handle to NV index 0x%" PRIx32 ".", p.nv_index); - tpm2_userspace_log_dirty(log_fd); + bool reset_marker = tpm2_userspace_log_dirty(log_fd) >= 0; rc = sym_Esys_NV_Extend( c->esys_context, /* authHandle= */ nv_handle->esys_handle, @@ -8777,7 +8787,7 @@ int tpm2_nvpcr_initialize( if (r < 0) return log_debug_errno(r, "Failed to write anchor file: %m"); - tpm2_userspace_log_clean(log_fd); + (void) tpm2_userspace_log_clean(log_fd, reset_marker); log_fd = safe_close(log_fd); /* Now also measure the initialization into PCR 9, so that there's a trace of it in regular PCRs. You