tpm2: measure user records on first login (#42228)

This is useful so that we can "blow a fuse" in TPM policies once a user
logged in.
This commit is contained in:
Luca Boccassi
2026-05-22 13:28:33 +01:00
committed by GitHub
19 changed files with 300 additions and 36 deletions
-4
View File
@@ -1737,8 +1737,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
- maybe: in PID1, when we detect we run in an initrd, make superblock read-only
early on, but provide opt-out via kernel cmdline.
- measure all log-in attempts into a new nvpcr
- measure GPT and LUKS headers somewhere when we use them (i.e. in
systemd-gpt-auto-generator/systemd-repart and in systemd-cryptsetup?)
@@ -1902,8 +1900,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
keyring, so that the kernel does this validation for us for verity and kernel
modules
- on first login of a user, measure its identity to some nvpcr
- on shutdown: move utmp, wall, audit logic all into PID 1 (or logind?)
- once swtpm's sd_notify() support has landed in the distributions, remove the
+17
View File
@@ -322,6 +322,23 @@ colon-separated strings, identifying the file system type, UUID, label as well
as the GPT partition entry UUID, entry type UUID and entry label (in UTF-8,
without trailing NUL bytes).
### NvPCR `login` (base+3), user logins
The `systemd-pcrlogin@.service` service (a per-UID template unit started by
`systemd-logind.service` on a user's first login of the current boot) will
measure that user's record into the `login` NvPCR. Each user is measured exactly
once per boot (the unit is `Type=oneshot`/`RemainAfterExit=yes` and is never
stopped again), so the NvPCR forms an append-only record of which user
identities were activated during the current boot. Note that its value is
inherently dynamic: it depends on *which* users log in and *in which order*.
→ **Measured hash** covers the string "login:", suffixed by the (escaped)
user name, a colon, and the user's record reduced to its `regular`, `perMachine`
and `binding` sections (i.e. with the `privileged`, `secret`, `status` and
`signature` sections stripped), normalized and serialized to canonical,
single-line JSON. Example string:
`login:lennart:{"userName":"lennart","uid":1000,…}`.
### PCR 9, NvPCR initialization separator
After completion of `systemd-tpm2-setup.service` (which initializes all NvPCRs
+1
View File
@@ -1147,6 +1147,7 @@ manpages = [
['systemd-pcrextend',
'systemd-pcrfs-root.service',
'systemd-pcrfs@.service',
'systemd-pcrlogin@.service',
'systemd-pcrmachine.service',
'systemd-pcrnvdone.service',
'systemd-pcrosseparator.service',
+33 -3
View File
@@ -23,11 +23,12 @@
<refname>systemd-pcrmachine.service</refname>
<refname>systemd-pcrosseparator.service</refname>
<refname>systemd-pcrproduct.service</refname>
<refname>systemd-pcrlogin@.service</refname>
<refname>systemd-pcrfs-root.service</refname>
<refname>systemd-pcrfs@.service</refname>
<refname>systemd-pcrnvdone.service</refname>
<refname>systemd-pcrextend</refname>
<refpurpose>Measure boot phases, machine ID, product UUID and file system identity into TPM PCRs and NvPCRs</refpurpose>
<refpurpose>Measure boot phases, machine ID, product UUID, user records and file system identity into TPM PCRs and NvPCRs</refpurpose>
</refnamediv>
<refsynopsisdiv>
@@ -37,6 +38,7 @@
<para><filename>systemd-pcrmachine.service</filename></para>
<para><filename>systemd-pcrosseparator.service</filename></para>
<para><filename>systemd-pcrproduct.service</filename></para>
<para><filename>systemd-pcrlogin@.service</filename></para>
<para><filename>systemd-pcrfs-root.service</filename></para>
<para><filename>systemd-pcrfs@.service</filename></para>
<para><filename>systemd-pcrnvdone.service</filename></para>
@@ -64,6 +66,20 @@
product UUID (as provided by one of SMBIOS, Devicetree, …) into a NvPCR named
<literal>hardware</literal>.</para>
<para><filename>systemd-pcrlogin@.service</filename> is a template service that measures the (reduced,
canonical JSON) user record of the user indicated by its instance identifier (a UID) into a NvPCR named
<literal>login</literal>. It is started by
<citerefentry><refentrytitle>systemd-logind.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
on the user's first login of the current boot, much like
<citerefentry><refentrytitle>user-runtime-dir@.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
Unlike the latter it is intentionally never stopped again: it is a <varname>Type=oneshot</varname> service
with <varname>RemainAfterExit=yes</varname> in <filename>system.slice</filename>, so each user is measured
exactly once per boot regardless of how often they log in and out (NvPCR extensions form a hash chain, so
measuring the same record twice would otherwise make the NvPCR value unpredictable). The resulting
<literal>login</literal> NvPCR (together with the
<filename>/run/log/systemd/tpm2-measure.log</filename> event log) thus provides a TPM-backed,
append-only record of which user identities were activated during the current boot.</para>
<para><filename>systemd-pcrnvdone.service</filename> is a system service that measures a separator event
into PCR 9 once all NvPCRs have completed initialization.</para>
@@ -244,6 +260,20 @@
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--login=</option></term>
<listitem><para>Instead of measuring a word specified on the command line into PCR 11, measure the
user record of the specified user into an NvPCR named <literal>login</literal>. The parameter must be a
user name or numeric UID. The record is reduced to its <literal>regular</literal>,
<literal>perMachine</literal> and <literal>binding</literal> sections (i.e. the privileged, secret,
status and signature sections are stripped, so that for example password hash changes do not perturb
the NvPCR), normalized and serialized to canonical JSON before measurement. This is primarily used by
<filename>systemd-pcrlogin@.service</filename>.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--file-system=</option></term>
@@ -259,8 +289,8 @@
<listitem><para>Set the event log event type for this measurement. Pass <literal>help</literal> for a
list of currently defined identifiers. Defaults to an appropriate value for
<option>--machine-id</option>, <option>--product-id</option>, <option>--file-system=</option>, and
otherwise to <literal>phase</literal>.</para>
<option>--machine-id</option>, <option>--product-id</option>, <option>--file-system=</option>,
<option>--login=</option>, and otherwise to <literal>phase</literal>.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
+1 -1
View File
@@ -4321,7 +4321,7 @@ int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *err
/* If the user is stopping, we're tracking stop jobs here. So don't send reply. */
if (!user->stopping) {
char **user_job;
FOREACH_ARGUMENT(user_job, &user->runtime_dir_job, &user->service_manager_job)
FOREACH_ARGUMENT(user_job, &user->runtime_dir_job, &user->service_manager_job, &user->measure_job)
if (streq_ptr(path, *user_job)) {
*user_job = mfree(*user_job);
+60 -2
View File
@@ -10,6 +10,7 @@
#include "bus-locator.h"
#include "bus-util.h"
#include "clean-ipc.h"
#include "efi-loader.h"
#include "env-file.h"
#include "errno-util.h"
#include "escape.h"
@@ -84,6 +85,10 @@ int user_new(Manager *m, UserRecord *ur, User **ret) {
if (r < 0)
return r;
r = unit_name_build("systemd-pcrlogin", lu, ".service", &u->measure_unit);
if (r < 0)
return r;
r = hashmap_put(m->users, UID_TO_PTR(ur->uid), u);
if (r < 0)
return r;
@@ -100,6 +105,10 @@ int user_new(Manager *m, UserRecord *ur, User **ret) {
if (r < 0)
return r;
r = hashmap_put(m->user_units, u->measure_unit, u);
if (r < 0)
return r;
*ret = TAKE_PTR(u);
return 0;
}
@@ -118,15 +127,21 @@ User *user_free(User *u) {
if (u->service_manager_unit) {
(void) hashmap_remove_value(u->manager->user_units, u->service_manager_unit, u);
free(u->service_manager_job);
free(u->service_manager_unit);
}
free(u->service_manager_job);
if (u->runtime_dir_unit) {
(void) hashmap_remove_value(u->manager->user_units, u->runtime_dir_unit, u);
free(u->runtime_dir_job);
free(u->runtime_dir_unit);
}
free(u->runtime_dir_job);
if (u->measure_unit) {
(void) hashmap_remove_value(u->manager->user_units, u->measure_unit, u);
free(u->measure_unit);
}
free(u->measure_job);
if (u->slice) {
(void) hashmap_remove_value(u->manager->user_units, u->slice, u);
@@ -177,6 +192,7 @@ static int user_save_internal(User *u) {
env_file_fputs_assignment(f, "RUNTIME=", u->runtime_path);
env_file_fputs_assignment(f, "RUNTIME_DIR_JOB=", u->runtime_dir_job);
env_file_fputs_assignment(f, "SERVICE_JOB=", u->service_manager_job);
env_file_fputs_assignment(f, "MEASURE_JOB=", u->measure_job);
if (u->display)
env_file_fputs_assignment(f, "DISPLAY=", u->display->id);
@@ -303,6 +319,7 @@ int user_load(User *u) {
r = parse_env_file(NULL, u->state_file,
"RUNTIME_DIR_JOB", &u->runtime_dir_job,
"SERVICE_JOB", &u->service_manager_job,
"MEASURE_JOB", &u->measure_job,
"STOPPING", &stopping,
"REALTIME", &realtime,
"MONOTONIC", &monotonic,
@@ -358,6 +375,39 @@ static int user_start_runtime_dir(User *u) {
return 0;
}
static int user_start_measure(User *u) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(u);
assert(!u->stopping);
assert(u->manager);
assert(u->measure_unit);
/* Measures the user's record into the 'login' TPM2 NvPCR on the first login of this boot. This is
* strictly best-effort: a measurement failure must never block the login (simply because NvPCRs might
* not be available). Unlike the runtime-dir and service-manager units, this one is started but never
* stopped (see user_stop_service()): it is a Type=oneshot/RemainAfterExit unit living in
* system.slice that stays active until reboot, so a later re-login's StartUnit is a no-op and every
* user is measured exactly once per boot.
*
* Only bother if we're actually running in measured-OS mode. The unit itself also carries
* ConditionSecurity=measured-os, but checking here avoids queuing a no-op job on every first login
* on systems that don't measure the OS */
if (efi_measured_os(LOG_DEBUG) <= 0)
return 0;
u->measure_job = mfree(u->measure_job);
r = manager_start_unit(u->manager, u->measure_unit, &error, &u->measure_job);
if (r < 0)
return log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_WARNING,
r, "Failed to start user measurement service '%s', ignoring: %s",
u->measure_unit, bus_error_message(&error, r));
return 0;
}
static bool user_wants_service_manager(const User *u) {
assert(u);
@@ -512,6 +562,14 @@ int user_start(User *u) {
/* Set slice parameters */
(void) user_update_slice(u);
/* Measure the user record into the 'login' NvPCR. We do this *before* starting the runtime dir
* (and, further below, the service manager): manager_start_unit() is synchronous, so by the
* time those units are enqueued the measurement's start job already exists, and their
* After=systemd-pcrlogin@%i.service ordering then guarantees the measurement completes before
* the user's session becomes available. Best-effort and never stopped again, so each user is
* measured exactly once per boot. */
(void) user_start_measure(u);
(void) user_start_runtime_dir(u);
}
+7
View File
@@ -43,6 +43,13 @@ typedef struct User {
char *service_manager_unit;
char *service_manager_job;
/* systemd-pcrlogin@UID.service — measures the user record into the 'login' NvPCR on first login.
* Unlike the units above, logind starts this but never stops it: it must run exactly once per boot,
* so it lives in system.slice, stays active until reboot, and is intentionally absent from
* user_stop_service(). */
char *measure_unit;
char *measure_job;
Session *display;
dual_timestamp timestamp; /* When this User object was 'started' the first time */
+45 -24
View File
@@ -10,17 +10,19 @@
#include "efi-loader.h"
#include "escape.h"
#include "format-table.h"
#include "help-util.h"
#include "json-util.h"
#include "main-func.h"
#include "options.h"
#include "parse-argument.h"
#include "pcrextend-util.h"
#include "pretty-print.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "tpm2-pcr.h"
#include "tpm2-util.h"
#include "user-record.h"
#include "userdb.h"
#include "varlink-io.systemd.PCRExtend.h"
#include "varlink-util.h"
@@ -30,6 +32,7 @@ static char **arg_banks = NULL;
static char *arg_file_system = NULL;
static bool arg_machine_id = false;
static bool arg_product_id = false;
static UserRecord *arg_login = NULL;
static uint32_t arg_pcr_mask = 0;
static char *arg_nvpcr_name = NULL;
static bool arg_varlink = false;
@@ -40,40 +43,31 @@ STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep);
STATIC_DESTRUCTOR_REGISTER(arg_nvpcr_name, freep);
STATIC_DESTRUCTOR_REGISTER(arg_login, user_record_unrefp);
#define EXTENSION_STRING_SAFE_LIMIT 1024
static int help(void) {
_cleanup_free_ char *link = NULL;
_cleanup_(table_unrefp) Table *options = NULL;
int r;
r = terminal_urlify_man("systemd-pcrextend", "8", &link);
if (r < 0)
return log_oom();
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
printf("%1$s [OPTIONS...] WORD\n"
"%1$s [OPTIONS...] --file-system=PATH\n"
"%1$s [OPTIONS...] --machine-id\n"
"%1$s [OPTIONS...] --product-id\n"
"\n%2$sExtend a TPM2 PCR with boot phase, machine ID, or file system ID.%3$s\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal());
printf("\n%sOptions:%s\n",
ansi_underline(),
ansi_normal());
help_cmdline("[OPTIONS...] WORD");
help_cmdline("[OPTIONS...] --file-system=PATH");
help_cmdline("[OPTIONS...] --machine-id");
help_cmdline("[OPTIONS...] --product-id");
help_cmdline("[OPTIONS...] --login=UID|USER");
help_abstract("Extend a TPM2 PCR with boot phase, machine ID, file system ID or user record.");
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
printf("\nSee the %s for details.\n", link);
help_man_page_reference("systemd-pcrextend", "8");
return 0;
}
@@ -169,6 +163,19 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
arg_product_id = true;
break;
OPTION_LONG("login", "UID|USER",
"Measure a user's record into NvPCR 'login'"): {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
r = userdb_by_name(opts.arg, /* match= */ NULL, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur);
if (r < 0)
return log_error_errno(r, "Failed to look up user '%s': %m", opts.arg);
user_record_unref(arg_login);
arg_login = TAKE_PTR(ur);
break;
}
OPTION_LONG("early", NULL,
"Run in early boot mode, without access to /var/"):
arg_early = true;
@@ -185,8 +192,8 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
break;
}
if (!!arg_file_system + arg_machine_id + arg_product_id > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-id, --product-id may not be combined.");
if (!!arg_file_system + arg_machine_id + arg_product_id + !!arg_login > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-id, --product-id, --login= may not be combined.");
if (arg_pcr_mask != 0 && arg_nvpcr_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--pcr= and --nvpcr= may not be combined.");
@@ -199,10 +206,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
else if (arg_pcr_mask == 0 && !arg_nvpcr_name) {
arg_pcr_mask =
(arg_file_system || arg_machine_id) ? INDEX_TO_MASK(uint32_t, TPM2_PCR_SYSTEM_IDENTITY) : /* → PCR 15 */
!arg_product_id ? INDEX_TO_MASK(uint32_t, TPM2_PCR_KERNEL_BOOT) : /* → PCR 11 */
0;
(arg_product_id || arg_login) ? 0 : /* → NvPCR */
INDEX_TO_MASK(uint32_t, TPM2_PCR_KERNEL_BOOT); /* → PCR 11 */
r = free_and_strdup_warn(&arg_nvpcr_name, arg_product_id ? "hardware" : NULL);
r = free_and_strdup_warn(&arg_nvpcr_name,
arg_product_id ? "hardware" :
arg_login ? "login" :
NULL);
if (r < 0)
return r;
}
@@ -496,6 +506,17 @@ static int run(int argc, char *argv[]) {
return r;
event = TPM2_EVENT_PRODUCT_ID;
} else if (arg_login) {
if (n_args != 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
r = pcrextend_login_word(arg_login, &word);
if (r < 0)
return r;
event = TPM2_EVENT_LOGIN;
} else {
if (n_args != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
+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;
+1
View File
@@ -45,6 +45,7 @@ executables += [
if conf.get('ENABLE_BOOTLOADER') == 1 and conf.get('HAVE_OPENSSL') == 1 and conf.get('HAVE_TPM2') == 1
nvpcrs = [ 'cryptsetup',
'hardware',
'login',
'verity']
foreach n : nvpcrs
custom_target(
+6
View File
@@ -0,0 +1,6 @@
{
"name" : "login",
"algorithm" : "sha256",
"nvIndex" : {{TPM2_NVPCR_BASE + 3}},
"priority" : 800
}
+35
View File
@@ -82,6 +82,41 @@ AAA_LINE="$(echo "$SETUP_LOG" | grep -n "Setting up NvPCR 'aaa'" | cut -d: -f1)"
ZZZ_LINE="$(echo "$SETUP_LOG" | grep -n "Setting up NvPCR 'zzz'" | cut -d: -f1)"
test "$ZZZ_LINE" -lt "$AAA_LINE"
# Test the --login= mode and the 'login' NvPCR, used in production by systemd-pcrlogin@.service.
if [[ -f /usr/lib/nvpcr/login.nvpcr ]]; then
login_nvpcr_value() {
systemd-analyze nvpcrs login --json=pretty | jq -r '.[] | select(.name=="login") | .value'
}
# Extract the most recently measured word for the 'login' NvPCR from the event log.
login_last_word() {
jq --seq --slurp -r '[.[] | select(.content.nvIndexName=="login") | .content.string] | last' </run/log/systemd/tpm2-measure.log
}
# Measure root's user record. This lazily initializes the 'login' NvPCR if it isn't already.
"$SD_PCREXTEND" --login=root
# The 'login' NvPCR must now exist and carry a non-empty value.
LOGIN_DIGEST1="$(login_nvpcr_value)"
test -n "$LOGIN_DIGEST1"
test "$LOGIN_DIGEST1" != "null"
# A matching event log entry must be present (the word is "login:<name>:<canonical json>").
grep -F '"nvIndexName":"login","string":"login:root:' /run/log/systemd/tpm2-measure.log >/dev/null
LOGIN_WORD_BY_NAME="$(login_last_word)"
# Looking the same user up by numeric UID must yield the identical measured word
# (systemd-pcrextend uses USERDB_PARSE_NUMERIC, and systemd-pcrlogin@.service is instanced by UID).
"$SD_PCREXTEND" --login=0
LOGIN_WORD_BY_UID="$(login_last_word)"
test "$LOGIN_WORD_BY_NAME" = "$LOGIN_WORD_BY_UID"
# Direct tool invocations always re-extend (the once-per-boot guarantee lives in the unit's
# RemainAfterExit=yes, not in the tool), so the NvPCR value must have advanced.
LOGIN_DIGEST2="$(login_nvpcr_value)"
test "$LOGIN_DIGEST2" != "$LOGIN_DIGEST1"
fi
systemd-analyze identify-tpm2
udevadm test-builtin 'tpm2_id identify' /dev/tpmrm0
+4
View File
@@ -604,6 +604,10 @@ units = [
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['sysinit.target.wants/'],
},
{
'file' : 'systemd-pcrlogin@.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
},
{
'file' : 'systemd-pcrphase-initrd.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2', 'ENABLE_INITRD'],
+24
View File
@@ -0,0 +1,24 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=TPM NvPCR Measurement of User Record for UID %i
Documentation=man:systemd-pcrlogin@.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
After=tpm2.target systemd-pcrnvdone.service
Before=shutdown.target
RequiresMountsFor=/var/lib/systemd/nvpcr
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-os
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-pcrextend --graceful --login=%i
+4 -1
View File
@@ -10,7 +10,10 @@
[Unit]
Description=User Runtime Directory /run/user/%i
Documentation=man:user@.service(5)
After=systemd-logind.service dbus.service
# Order after the user record measurement (started by logind before this unit), so that the user's record is
# measured into the 'login' TPM2 NvPCR before the runtime dir — and thus the user's session — becomes
# available. This is ordering only (no Wants=/Requires=): a measurement failure must never block login.
After=systemd-logind.service dbus.service systemd-pcrlogin@%i.service
IgnoreOnIsolate=yes
[Service]
+3 -1
View File
@@ -11,7 +11,9 @@
Description=User Manager for UID %i
Documentation=man:user@.service(5)
BindsTo=user-runtime-dir@%i.service
After=systemd-logind.service user-runtime-dir@%i.service dbus.service systemd-oomd.service
# Order after the user record measurement (see user-runtime-dir@.service), so the user's record is measured
# into the 'login' TPM2 NvPCR before the user manager starts. Ordering only, so login is never blocked on it.
After=systemd-logind.service user-runtime-dir@%i.service dbus.service systemd-oomd.service systemd-pcrlogin@%i.service
IgnoreOnIsolate=yes
[Service]