logind: automatically pull in systemd-pcrlogin@.service on first login

This commit is contained in:
Lennart Poettering
2026-05-22 12:15:14 +02:00
parent e32209c69c
commit 6f6ca5a224
5 changed files with 75 additions and 5 deletions
+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 the user is stopping, we're tracking stop jobs here. So don't send reply. */
if (!user->stopping) { if (!user->stopping) {
char **user_job; 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)) { if (streq_ptr(path, *user_job)) {
*user_job = mfree(*user_job); *user_job = mfree(*user_job);
+60 -2
View File
@@ -10,6 +10,7 @@
#include "bus-locator.h" #include "bus-locator.h"
#include "bus-util.h" #include "bus-util.h"
#include "clean-ipc.h" #include "clean-ipc.h"
#include "efi-loader.h"
#include "env-file.h" #include "env-file.h"
#include "errno-util.h" #include "errno-util.h"
#include "escape.h" #include "escape.h"
@@ -84,6 +85,10 @@ int user_new(Manager *m, UserRecord *ur, User **ret) {
if (r < 0) if (r < 0)
return r; 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); r = hashmap_put(m->users, UID_TO_PTR(ur->uid), u);
if (r < 0) if (r < 0)
return r; return r;
@@ -100,6 +105,10 @@ int user_new(Manager *m, UserRecord *ur, User **ret) {
if (r < 0) if (r < 0)
return r; return r;
r = hashmap_put(m->user_units, u->measure_unit, u);
if (r < 0)
return r;
*ret = TAKE_PTR(u); *ret = TAKE_PTR(u);
return 0; return 0;
} }
@@ -118,15 +127,21 @@ User *user_free(User *u) {
if (u->service_manager_unit) { if (u->service_manager_unit) {
(void) hashmap_remove_value(u->manager->user_units, u->service_manager_unit, u); (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_unit);
} }
free(u->service_manager_job);
if (u->runtime_dir_unit) { if (u->runtime_dir_unit) {
(void) hashmap_remove_value(u->manager->user_units, u->runtime_dir_unit, u); (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_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) { if (u->slice) {
(void) hashmap_remove_value(u->manager->user_units, u->slice, u); (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=", u->runtime_path);
env_file_fputs_assignment(f, "RUNTIME_DIR_JOB=", u->runtime_dir_job); 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, "SERVICE_JOB=", u->service_manager_job);
env_file_fputs_assignment(f, "MEASURE_JOB=", u->measure_job);
if (u->display) if (u->display)
env_file_fputs_assignment(f, "DISPLAY=", u->display->id); 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, r = parse_env_file(NULL, u->state_file,
"RUNTIME_DIR_JOB", &u->runtime_dir_job, "RUNTIME_DIR_JOB", &u->runtime_dir_job,
"SERVICE_JOB", &u->service_manager_job, "SERVICE_JOB", &u->service_manager_job,
"MEASURE_JOB", &u->measure_job,
"STOPPING", &stopping, "STOPPING", &stopping,
"REALTIME", &realtime, "REALTIME", &realtime,
"MONOTONIC", &monotonic, "MONOTONIC", &monotonic,
@@ -358,6 +375,39 @@ static int user_start_runtime_dir(User *u) {
return 0; 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) { static bool user_wants_service_manager(const User *u) {
assert(u); assert(u);
@@ -512,6 +562,14 @@ int user_start(User *u) {
/* Set slice parameters */ /* Set slice parameters */
(void) user_update_slice(u); (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); (void) user_start_runtime_dir(u);
} }
+7
View File
@@ -43,6 +43,13 @@ typedef struct User {
char *service_manager_unit; char *service_manager_unit;
char *service_manager_job; 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; Session *display;
dual_timestamp timestamp; /* When this User object was 'started' the first time */ dual_timestamp timestamp; /* When this User object was 'started' the first time */
+4 -1
View File
@@ -10,7 +10,10 @@
[Unit] [Unit]
Description=User Runtime Directory /run/user/%i Description=User Runtime Directory /run/user/%i
Documentation=man:user@.service(5) 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 IgnoreOnIsolate=yes
[Service] [Service]
+3 -1
View File
@@ -11,7 +11,9 @@
Description=User Manager for UID %i Description=User Manager for UID %i
Documentation=man:user@.service(5) Documentation=man:user@.service(5)
BindsTo=user-runtime-dir@%i.service 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 IgnoreOnIsolate=yes
[Service] [Service]