]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
logind: automatically pull in systemd-pcrlogin@.service on first login
authorLennart Poettering <lennart@amutable.com>
Thu, 21 May 2026 12:48:29 +0000 (14:48 +0200)
committerLennart Poettering <lennart@amutable.com>
Fri, 22 May 2026 10:15:14 +0000 (12:15 +0200)
src/login/logind-dbus.c
src/login/logind-user.c
src/login/logind-user.h
units/user-runtime-dir@.service.in
units/user@.service.in

index 5ff5a1f656a9baca6f1dc5c09424ede0f66256a8..b773dbfa427a759ec3fbdda9fdffaa506fa73108 100644 (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);
 
index 11ca04dc8de24511b982c9f88cb313cd8ce5c9be..c0342dfe16bd59eec7dca6b31abcbe6540a13321 100644 (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);
         }
 
index b38af08744b23239183aee18feb15659b5d29e18..2ae8faf3754d84b139c5dd79c10b0487ba17f23b 100644 (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 */
index 241e9267bb0c85254ca863194ed1c87a669c1d47..d83b32570d27a1b55e3a575f3e35319f10d29b23 100644 (file)
 [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]
index 381ab2a0db54e2e2cde400e2358ce61789f680d3..a58af4a7d040908acd874ca9ccae15fe4c32ffdb 100644 (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]