pcrextend: add support for measuring a user record, to be executed on first login of the user

This is supposed to be useful to mark an interactive user login as a
"break glass" event in the measurement logs, i.e. as in many typically
headless scenerios this indicates debug access or similar.
This commit is contained in:
Lennart Poettering
2026-05-22 12:15:12 +02:00
parent 0bd861ff76
commit e32209c69c
11 changed files with 166 additions and 9 deletions
+54
View File
@@ -2,6 +2,7 @@
#include "sd-device.h"
#include "sd-id128.h"
#include "sd-json.h"
#include "sd-varlink.h"
#include "alloc-util.h"
@@ -22,6 +23,7 @@
#include "string-util.h"
#include "strv.h"
#include "tpm2-pcr.h"
#include "user-record.h"
static int device_get_file_system_word(
sd_device *d,
@@ -187,6 +189,58 @@ int pcrextend_product_id_word(char **ret) {
return 0;
}
int pcrextend_login_word(UserRecord *ur, char **ret) {
int r;
assert(ur);
assert(ret);
/* Reduce the user record to the sections that make up its stable, host-specific identity, and turn
* that into a word to measure into the 'login' NvPCR. We deliberately keep the 'regular',
* 'perMachine' and 'binding' sections (the portable identity plus how it is realized on *this*
* host), but drop 'privileged' (so password hash churn doesn't perturb the NvPCR), 'secret' and
* 'status' (transient) and 'signature' (so the scheme is identical for signed and unsigned records).
* Only 'regular' is required, the others are merely allowed, so plain NSS users reduce cleanly. */
UserRecordLoadFlags mask =
USER_RECORD_REQUIRE_REGULAR |
USER_RECORD_ALLOW_PER_MACHINE |
USER_RECORD_ALLOW_BINDING |
USER_RECORD_STRIP_PRIVILEGED |
USER_RECORD_STRIP_SECRET |
USER_RECORD_STRIP_STATUS |
USER_RECORD_STRIP_SIGNATURE |
USER_RECORD_PERMISSIVE;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
r = user_group_record_mangle(ur->json, mask, &v, /* ret_mask= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to reduce user record of '%s': %m", ur->user_name);
/* Normalize so the serialization is canonical (stable key order) regardless of how the record was
* assembled by the various userdb backends. */
r = sd_json_variant_normalize(&v);
if (r < 0)
return log_error_errno(r, "Failed to normalize user record of '%s': %m", ur->user_name);
/* Format to compact, single-line JSON (no SD_JSON_FORMAT_NEWLINE), so the measured word stays on one
* line and the colon-separated word structure is unambiguous. */
_cleanup_free_ char *text = NULL;
r = sd_json_variant_format(v, /* flags= */ 0, &text);
if (r < 0)
return log_error_errno(r, "Failed to format user record of '%s': %m", ur->user_name);
_cleanup_free_ char *name_escaped = xescape(ur->user_name, ":"); /* Avoid ambiguity around ":" */
if (!name_escaped)
return log_oom();
_cleanup_free_ char *word = strjoin("login:", name_escaped, ":", text);
if (!word)
return log_oom();
*ret = TAKE_PTR(word);
return 0;
}
int pcrextend_verity_word(
const char *name,
const struct iovec *root_hash,
+3
View File
@@ -3,11 +3,14 @@
#include <sys/uio.h>
#include "shared-forward.h"
int pcrextend_file_system_word(const char *path, char **ret, char **ret_normalized_path);
int pcrextend_machine_id_word(char **ret);
int pcrextend_product_id_word(char **ret);
int pcrextend_verity_word(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig, char **ret);
int pcrextend_imds_userdata_word(const struct iovec *data, char **ret);
int pcrextend_login_word(UserRecord *ur, char **ret);
int pcrextend_verity_now(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig);
int pcrextend_imds_userdata_now(const struct iovec *data);
+1
View File
@@ -6702,6 +6702,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA
[TPM2_EVENT_DM_VERITY] = "dm-verity",
[TPM2_EVENT_IMDS_USERDATA] = "imds-userdata",
[TPM2_EVENT_OS_SEPARATOR] = "os-separator",
[TPM2_EVENT_LOGIN] = "login",
};
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);
+1
View File
@@ -151,6 +151,7 @@ typedef enum Tpm2UserspaceEventType {
TPM2_EVENT_DM_VERITY,
TPM2_EVENT_IMDS_USERDATA,
TPM2_EVENT_OS_SEPARATOR,
TPM2_EVENT_LOGIN,
_TPM2_USERSPACE_EVENT_TYPE_MAX,
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
} Tpm2UserspaceEventType;