From cb7f95ff5f25a28afeadd668d3c0530005982b3b Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 10 Jun 2026 13:02:01 +0200 Subject: [PATCH 1/3] credentials: add policy that can allow key=null creds from the ESP This commit adds a new `systemd.credentials_boot_policy=` kernel commandline that allows to control if credentials with a `null` key are accepted. The possible options are: * strict: always insist on tpm encryption * tofu: allow null encryption in firstboot mode and when no tpm is available * relaxed: allow null encryption when sb is off, or no tpm is available * off: allow null encryption always The default is currently `relaxed` which is the same behavior as before. This replaces the initial idea of using plaintext credentials at firstboot (thanks to Lennart for this nicer and simpler design). Note that this also moves `in_first_boot()` to `basic/initrd-util` which is a better fit now. --- docs/CREDENTIALS.md | 27 ++++++++ man/kernel-command-line.xml | 1 + man/systemd.xml | 35 ++++++++++ src/basic/initrd-util.c | 24 +++++++ src/basic/initrd-util.h | 2 + src/shared/condition.c | 22 ------- src/shared/creds-util.c | 128 +++++++++++++++++++++++++++++------- src/shared/creds-util.h | 13 ++++ src/test/test-creds.c | 29 ++++++++ test/units/TEST-54-CREDS.sh | 42 ++++++++++++ 10 files changed, 278 insertions(+), 45 deletions(-) diff --git a/docs/CREDENTIALS.md b/docs/CREDENTIALS.md index 149cf2f7db2..5260db6b332 100644 --- a/docs/CREDENTIALS.md +++ b/docs/CREDENTIALS.md @@ -274,6 +274,33 @@ SetCredentialEncrypted=foobar: \ … ``` +### Null-Key Encryption and the Boot Policy + +In some situations a credential must be provisioned to a machine that does not +yet (or does not at all) possess a key to encrypt against, for example before +first boot completes, or on systems without a TPM2 device. For these cases +`systemd-creds encrypt --with-key=null` produces a credential wrapped in the +normal `systemd-creds` envelope, but encrypted with a fixed zero-length key. +Such a credential travels through the same path as any other encrypted +credential (e.g. it may be dropped into the ESP under `/loader/credentials/` or +`*.efi.extra.d/`), but it offers neither confidentiality nor authenticity. +Because of that, accepting it is risky on a system that could do better, and +whether it is accepted at boot is controlled by the `systemd.credentials_boot_policy=` +kernel command line option: + +| Mode | A null-key credential is accepted when… | +|-----------|---------------------------------------------------| +| `strict` | never | +| `tofu` | this is the first boot, or no TPM2 is available | +| `relaxed` | SecureBoot is off, or no TPM2 is available | +| `off` | always | + +The default is `relaxed`. This policy only governs the +default acceptance decision; credentials encrypted against a host key or TPM2 +device are always accepted. See +[systemd(1)](https://www.freedesktop.org/software/systemd/man/latest/systemd.html) +for details. + ## Inheritance from Container Managers, Hypervisors, Kernel Command Line, or the UEFI Boot Environment Sometimes it is useful to parameterize whole systems the same way as services, diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 768c7653fb4..9134c987c9a 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -67,6 +67,7 @@ systemd.set_credential= systemd.set_credential_binary= systemd.import_credentials= + systemd.credentials_boot_policy= systemd.reload_limit_interval_sec= systemd.reload_limit_burst= systemd.minimum_uptime_sec= diff --git a/man/systemd.xml b/man/systemd.xml index 557cd766482..51ce1854fbb 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -965,6 +965,41 @@ + + systemd.credentials_boot_policy= + + Controls under which conditions a credential encrypted with a null + key (i.e. a credential wrapped in a systemd-creds envelope but offering neither + confidentiality nor authenticity, see + systemd-creds1) + is accepted at boot. This provides a fallback for provisioning credentials before a machine has a key + to encrypt them against, for example during first boot or on systems without a TPM2 device. Takes + one of , , or + : + + + never accepts a null key, i.e. always + insists on proper encryption. + + (trust on first use) accepts a null key + during first boot, or when no TPM2 device is available. + + accepts a null key when SecureBoot is + disabled, or when no TPM2 device is available. + + always accepts a null key. + + + Defaults to . Note that this policy only applies to the default acceptance + decision; it has no effect on credentials encrypted against a host key or TPM2 device, which are + always accepted. + + For further information see System and Service + Credentials documentation. + + + + quiet diff --git a/src/basic/initrd-util.c b/src/basic/initrd-util.c index c45920b3070..fc497f59f5e 100644 --- a/src/basic/initrd-util.c +++ b/src/basic/initrd-util.c @@ -1,11 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include "env-util.h" #include "errno-util.h" #include "initrd-util.h" #include "log.h" +#include "parse-util.h" static int saved_in_initrd = -1; @@ -38,3 +40,25 @@ bool in_initrd(void) { void in_initrd_force(bool value) { saved_in_initrd = value; } + +bool in_first_boot(void) { + static int first_boot = -1; + int r; + + if (first_boot >= 0) + return first_boot; + + const char *e = secure_getenv("SYSTEMD_FIRST_BOOT"); + if (e) { + r = parse_boolean(e); + if (r < 0) + log_debug_errno(r, "Failed to parse $SYSTEMD_FIRST_BOOT, ignoring: %m"); + else + return (first_boot = r); + } + + r = RET_NERRNO(access("/run/systemd/first-boot", F_OK)); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to check if /run/systemd/first-boot exists, assuming no: %m"); + return r >= 0; +} diff --git a/src/basic/initrd-util.h b/src/basic/initrd-util.h index 387c2c6717c..ec55ec34028 100644 --- a/src/basic/initrd-util.h +++ b/src/basic/initrd-util.h @@ -5,3 +5,5 @@ bool in_initrd(void); void in_initrd_force(bool value); + +bool in_first_boot(void); diff --git a/src/shared/condition.c b/src/shared/condition.c index 2b4a29ed5c6..be2754c15eb 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -960,28 +960,6 @@ static int condition_test_needs_update(Condition *c, char **env) { return timespec_load_nsec(&usr.st_mtim) > timestamp; } -static bool in_first_boot(void) { - static int first_boot = -1; - int r; - - if (first_boot >= 0) - return first_boot; - - const char *e = secure_getenv("SYSTEMD_FIRST_BOOT"); - if (e) { - r = parse_boolean(e); - if (r < 0) - log_debug_errno(r, "Failed to parse $SYSTEMD_FIRST_BOOT, ignoring: %m"); - else - return (first_boot = r); - } - - r = RET_NERRNO(access("/run/systemd/first-boot", F_OK)); - if (r < 0 && r != -ENOENT) - log_debug_errno(r, "Failed to check if /run/systemd/first-boot exists, assuming no: %m"); - return r >= 0; -} - static int condition_test_first_boot(Condition *c, char **env) { int r; diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index eaee54ee57a..a1173714825 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -24,6 +24,7 @@ #include "find-esp.h" #include "format-util.h" #include "fs-util.h" +#include "initrd-util.h" #include "io-util.h" #include "json-util.h" #include "log.h" @@ -31,10 +32,12 @@ #include "mkdir.h" #include "parse-util.h" #include "path-util.h" +#include "proc-cmdline.h" #include "random-util.h" #include "recurse-dir.h" #include "sparse-endian.h" #include "stat-util.h" +#include "string-table.h" #include "string-util.h" #include "tmpfile-util.h" #include "tpm2-pcr.h" @@ -366,6 +369,39 @@ int get_credential_user_password(const char *username, char **ret_password, bool return r; } +static const char* const credential_boot_policy_table[_CRED_BOOT_POLICY_MAX] = { + [CRED_BOOT_STRICT] = "strict", + [CRED_BOOT_TOFU] = "tofu", + [CRED_BOOT_RELAXED] = "relaxed", + [CRED_BOOT_OFF] = "off", +}; + +DEFINE_STRING_TABLE_LOOKUP(credential_boot_policy, CredentialBootPolicy); + +bool credential_boot_policy_accepts_null(CredentialBootPolicy policy, bool first_boot, bool have_tpm2, bool secure_boot) { + + /* Decides whether a null-key encrypted credential (which offers neither confidentiality nor + * authenticity) may be accepted, given the configured policy and the current system state. */ + + switch (policy) { + + case CRED_BOOT_STRICT: + return false; + + case CRED_BOOT_TOFU: + return first_boot || !have_tpm2; + + case CRED_BOOT_RELAXED: + return !secure_boot || !have_tpm2; + + case CRED_BOOT_OFF: + return true; + + default: + assert_not_reached(); + } +} + #if HAVE_OPENSSL #define CREDENTIAL_HOST_SECRET_SIZE 4096 @@ -1195,6 +1231,72 @@ int encrypt_credential_and_warn( return 0; } +static CredentialBootPolicy query_credential_boot_policy(void) { + static CredentialBootPolicy cached = _CRED_BOOT_POLICY_INVALID; + _cleanup_free_ char *value = NULL; + int r; + + if (cached >= 0) + return cached; + + /* default to RELAXED if invalid or unset */ + cached = CRED_BOOT_RELAXED; + r = proc_cmdline_get_key("systemd.credentials_boot_policy", PROC_CMDLINE_STRIP_RD_PREFIX, &value); + if (r < 0) + log_debug_errno(r, "Failed to read systemd.credentials_boot_policy= from kernel command line, ignoring: %m"); + else if (r > 0) { + CredentialBootPolicy p = credential_boot_policy_from_string(value); + if (p < 0) + log_warning("Invalid systemd.credentials_boot_policy= value '%s', ignoring.", value); + else + cached = p; + } + + return cached; +} + +static int check_null_key_policy(CredentialFlags flags) { + if (FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL)) + return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), + "Credential uses null key, but that's not allowed, refusing."); + + if (FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) + return 0; + + /* So this is a credential encrypted with a zero length key. We support this to cover for the + * case where neither a host key not a TPM2 are available (specifically: initrd environments + * where the host key is not yet accessible and no TPM2 chip exists at all), to minimize + * different codeflow for TPM2 and non-TPM2 codepaths. Of course, credentials encoded this + * way offer no confidentiality nor authenticity. Because of that it's important we refuse to + * use them on systems that actually *do* have a TPM2 chip – if we are in SecureBoot + * mode. Otherwise an attacker could hand us credentials like this and we'd use them thinking + * they are trusted, even though they are not. + * + * Which conditions actually lead us to accept a null-key credential is configurable via + * systemd.credentials_boot_policy=, which also covers the first boot case (before any key + * exists yet); the decision itself is made in credential_boot_policy_accepts_null(). */ + + CredentialBootPolicy policy = query_credential_boot_policy(); + bool first_boot = in_first_boot(), have_tpm2 = efi_has_tpm2(), secure_boot = is_efi_secure_boot(); + + if (!credential_boot_policy_accepts_null(policy, first_boot, have_tpm2, secure_boot)) + return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), + "Credential uses null key, but systemd.credentials_boot_policy=%s refuses it here (TPM2=%s, SecureBoot=%s, first boot=%s).", + credential_boot_policy_to_string(policy), + yes_no(have_tpm2), yes_no(secure_boot), yes_no(first_boot)); + + /* Accepting a null-key credential on a tpm2 host is undesired so keep it auditable */ + if (have_tpm2 && !first_boot) + log_warning("Credential uses null key intended for use when TPM2 is absent, but TPM2 is present! " + "Accepting anyway, under systemd.credentials_boot_policy=%s.", + credential_boot_policy_to_string(policy)); + else + log_debug("Credential uses null key, accepted under systemd.credentials_boot_policy=%s.", + credential_boot_policy_to_string(policy)); + + return 0; +} + int decrypt_credential_and_warn( const char *validate_name, usec_t validate_timestamp, @@ -1254,29 +1356,9 @@ int decrypt_credential_and_warn( } if (sd_id128_equal(h->id, CRED_AES256_GCM_BY_NULL)) { - if (FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL)) - return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), - "Credential uses null key, but that's not allowed, refusing."); - - if (!FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) { - /* So this is a credential encrypted with a zero length key. We support this to cover for the - * case where neither a host key not a TPM2 are available (specifically: initrd environments - * where the host key is not yet accessible and no TPM2 chip exists at all), to minimize - * different codeflow for TPM2 and non-TPM2 codepaths. Of course, credentials encoded this - * way offer no confidentiality nor authenticity. Because of that it's important we refuse to - * use them on systems that actually *do* have a TPM2 chip – if we are in SecureBoot - * mode. Otherwise an attacker could hand us credentials like this and we'd use them thinking - * they are trusted, even though they are not. */ - - if (efi_has_tpm2()) { - if (is_efi_secure_boot()) - return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), - "Credential uses null key intended for fallback use when TPM2 is absent — but TPM2 is present, and SecureBoot is enabled, refusing."); - - log_warning("Credential uses null key intended for use when TPM2 is absent, but TPM2 is present! Accepting anyway, since SecureBoot is disabled."); - } else - log_debug("Credential uses null key intended for use when TPM2 is absent, and TPM2 indeed is absent. Accepting."); - } + r = check_null_key_policy(flags); + if (r < 0) + return r; } if (CRED_KEY_IS_SCOPED(h->id)) { diff --git a/src/shared/creds-util.h b/src/shared/creds-util.h index 32c5a0ba88d..3d058ecf6a2 100644 --- a/src/shared/creds-util.h +++ b/src/shared/creds-util.h @@ -66,6 +66,19 @@ typedef enum CredentialFlags { CREDENTIAL_IPC_ALLOW_INTERACTIVE = 1 << 3, } CredentialFlags; +typedef enum CredentialBootPolicy { + CRED_BOOT_STRICT, /* never accept a null key */ + CRED_BOOT_TOFU, /* accept a null key during first boot or when no TPM2 is available */ + CRED_BOOT_RELAXED, /* accept a null key when SecureBoot is off or when no TPM2 is available */ + CRED_BOOT_OFF, /* always accept a null key */ + _CRED_BOOT_POLICY_MAX, + _CRED_BOOT_POLICY_INVALID = -EINVAL, +} CredentialBootPolicy; + +DECLARE_STRING_TABLE_LOOKUP(credential_boot_policy, CredentialBootPolicy); + +bool credential_boot_policy_accepts_null(CredentialBootPolicy policy, bool first_boot, bool have_tpm2, bool secure_boot); + /* The four modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of * both, as well as one with a fixed zero length key if TPM2 is missing (the latter of course provides no * authenticity or confidentiality, but is still useful for integrity protection, and makes things simpler diff --git a/src/test/test-creds.c b/src/test/test-creds.c index e380550a5a7..cbcf0c3444c 100644 --- a/src/test/test-creds.c +++ b/src/test/test-creds.c @@ -228,6 +228,35 @@ TEST(credential_encrypt_decrypt) { ASSERT_OK_ERRNO(setenv("SYSTEMD_CREDENTIAL_SECRET", ec, true)); } +TEST(credential_boot_policy) { + + /* String table round-trip */ + for (CredentialBootPolicy p = 0; p < _CRED_BOOT_POLICY_MAX; p++) { + const char *s = credential_boot_policy_to_string(p); + ASSERT_NOT_NULL(s); + ASSERT_EQ(credential_boot_policy_from_string(s), p); + } + ASSERT_STREQ(credential_boot_policy_to_string(CRED_BOOT_TOFU), "tofu"); + ASSERT_TRUE(credential_boot_policy_from_string("bogus") < 0); + + /* Exhaustively check the accept decision against every (first_boot, have_tpm2, secure_boot) state */ + for (int first_boot = 0; first_boot < 2; first_boot++) + for (int have_tpm2 = 0; have_tpm2 < 2; have_tpm2++) + for (int secure_boot = 0; secure_boot < 2; secure_boot++) { + /* strict never accepts a null key, off always does */ + ASSERT_FALSE(credential_boot_policy_accepts_null(CRED_BOOT_STRICT, first_boot, have_tpm2, secure_boot)); + ASSERT_TRUE(credential_boot_policy_accepts_null(CRED_BOOT_OFF, first_boot, have_tpm2, secure_boot)); + + /* tofu accepts when first boot, or no TPM2 */ + ASSERT_EQ(credential_boot_policy_accepts_null(CRED_BOOT_TOFU, first_boot, have_tpm2, secure_boot), + first_boot || !have_tpm2); + + /* relaxed accepts when SecureBoot off, or no TPM2 */ + ASSERT_EQ(credential_boot_policy_accepts_null(CRED_BOOT_RELAXED, first_boot, have_tpm2, secure_boot), + !secure_boot || !have_tpm2); + } +} + TEST(mime_type_matches) { static const sd_id128_t tags[] = { diff --git a/test/units/TEST-54-CREDS.sh b/test/units/TEST-54-CREDS.sh index f2321481f50..7c2774fa103 100755 --- a/test/units/TEST-54-CREDS.sh +++ b/test/units/TEST-54-CREDS.sh @@ -168,6 +168,48 @@ systemd-creds decrypt /tmp/cred.enc /tmp/cred.dec diff /tmp/cred.orig /tmp/cred.dec rm -f /tmp/cred.{enc,dec} +# Null-key credentials and the systemd.credentials_boot_policy= setting. +# +# A null-key credential ("--with-key=null") is wrapped in the normal systemd-creds +# envelope but offers neither confidentiality nor authenticity, so whether it is +# accepted at decrypt time is governed by systemd.credentials_boot_policy= and the system +# state. We only assert the branches that are deterministic regardless of the test +# bed's TPM2/SecureBoot state (which is fixed and has no test override here); the +# full accepts_null() truth table for all states is covered by test-creds.c. +systemd-creds --name=nullcred --with-key=null encrypt /tmp/cred.orig /tmp/cred.enc + +# --allow-null bypasses the policy, even under the strictest setting. +SYSTEMD_PROC_CMDLINE="systemd.credentials_boot_policy=strict" \ + systemd-creds --name=nullcred --allow-null decrypt /tmp/cred.enc /tmp/cred.dec +diff /tmp/cred.orig /tmp/cred.dec +rm -f /tmp/cred.dec + +# --refuse-null always refuses, even under the most permissive setting. +(! SYSTEMD_PROC_CMDLINE="systemd.credentials_boot_policy=off" \ + systemd-creds --name=nullcred --refuse-null decrypt /tmp/cred.enc /tmp/cred.dec) + +# strict never accepts a null key, regardless of system state. +(! SYSTEMD_PROC_CMDLINE="systemd.credentials_boot_policy=strict" \ + systemd-creds --name=nullcred decrypt /tmp/cred.enc /tmp/cred.dec) + +# off always accepts a null key, regardless of system state. +SYSTEMD_PROC_CMDLINE="systemd.credentials_boot_policy=off" \ + systemd-creds --name=nullcred decrypt /tmp/cred.enc /tmp/cred.dec +diff /tmp/cred.orig /tmp/cred.dec +rm -f /tmp/cred.dec + +# An invalid policy value falls back to the default (relaxed), which accepts during first boot. +SYSTEMD_PROC_CMDLINE="systemd.credentials_boot_policy=bogus" SYSTEMD_FIRST_BOOT=1 \ + systemd-creds --name=nullcred decrypt /tmp/cred.enc /tmp/cred.dec +diff /tmp/cred.orig /tmp/cred.dec +rm -f /tmp/cred.dec + +# tofu accepts during first boot, independent of TPM2/SecureBoot. +SYSTEMD_PROC_CMDLINE="systemd.credentials_boot_policy=tofu" SYSTEMD_FIRST_BOOT=1 \ + systemd-creds --name=nullcred decrypt /tmp/cred.enc /tmp/cred.dec +diff /tmp/cred.orig /tmp/cred.dec +rm -f /tmp/cred.{enc,dec} + (! unshare -m bash -exc "mount -t tmpfs tmpfs /run/credentials && systemd-creds list") (! unshare -m bash -exc "mount -t tmpfs tmpfs /run/credentials && systemd-creds --system list") (! CREDENTIALS_DIRECTORY="" systemd-creds list) From eb8227c671eed4ea913f9362eefe1dcf9720829a Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 3 Jul 2026 17:37:31 +0200 Subject: [PATCH 2/3] core: make `/run/systemd/first-boot` available earlier For the new systemd.credentials_boot_policy= setting we need to be able to differenciate if its a firstboot or not early. But `manager_set_first_boot` is run after `initialize_runtime()` which imports the credentials. So we need to touch the file so that the credentials_boot_policy setting can work. Unfortunately we cannot just move manager_set_first_boot() earlier because there is no manager yet at this point. --- src/core/main.c | 5 +++++ src/core/manager.c | 19 +++++++++++++++---- src/core/manager.h | 2 ++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index 5c02e308800..11c7f6fad88 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -2614,6 +2614,11 @@ static void log_execution_mode(bool *ret_first_boot) { } } + /* Make the first-boot file visible now: the credential import + * consults in_first_boot() for systemd.credentials_boot_policy= which runs + * before the manager object is created. */ + (void) update_first_boot_file(first_boot); + assert_se(uname(&uts) >= 0); if (strverscmp_improved(uts.release, KERNEL_BASELINE_VERSION) < 0) diff --git a/src/core/manager.c b/src/core/manager.c index 27ca84c4e9e..f5daa59e1ab 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -4842,21 +4842,32 @@ fail: } void manager_set_first_boot(Manager *m, bool b) { + int r; + assert(m); if (!MANAGER_IS_SYSTEM(m)) return; if (m->first_boot != (int) b) { - if (b) - (void) touch("/run/systemd/first-boot"); - else - (void) unlink("/run/systemd/first-boot"); + r = update_first_boot_file(b); + if (r < 0) + log_warning_errno(r, "Failed to update the first-boot file, ignoring: %m"); } m->first_boot = b; } +int update_first_boot_file(bool b) { + if (b) + return touch("/run/systemd/first-boot"); + + if (unlink("/run/systemd/first-boot") < 0 && errno != ENOENT) + return -errno; + + return 0; +} + void manager_disable_confirm_spawn(void) { (void) touch("/run/systemd/confirm_spawn_disabled"); } diff --git a/src/core/manager.h b/src/core/manager.h index 8262bfbec85..12d6cf44709 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -719,6 +719,8 @@ void manager_log_caller(Manager *manager, PidRef *caller, const char *method); int manager_allocate_idle_pipe(Manager *m); +int update_first_boot_file(bool b); + void unit_defaults_init(UnitDefaults *defaults, RuntimeScope scope); void unit_defaults_done(UnitDefaults *defaults); From 9d947d0a2afbbf5767e2fc5c82b9e21c20c41120 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 3 Jul 2026 17:28:45 +0200 Subject: [PATCH 3/3] basic: update in_first_boot() to return an error This commit tweaks the in_first_boot() code to make it a proper shared helper. It tweaks the comments about the caching policy. This is important because while this is simialr to `in_initrd()` there is a suble difference: we never cache the result of access() because other parts of systemd will use this file to signal if anything about firstboot changes. This also tweaks it to return an int instead of a bool. So any error is returned as -errno. This allows other parts of systemd (like the random-seed) that need to make decisions about "unknown" first-boot state. This also means that the consumers need to get updated and we can now also use the new helper in random-seed-tool.c and have only a single helper to check for first-boot. --- src/basic/initrd-util.c | 34 +++++++++++++++--------------- src/basic/initrd-util.h | 2 +- src/random-seed/random-seed-tool.c | 5 +++-- src/shared/condition.c | 2 +- src/shared/creds-util.c | 5 ++++- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/basic/initrd-util.c b/src/basic/initrd-util.c index fc497f59f5e..6c273c2e8a4 100644 --- a/src/basic/initrd-util.c +++ b/src/basic/initrd-util.c @@ -1,13 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "env-util.h" #include "errno-util.h" #include "initrd-util.h" #include "log.h" -#include "parse-util.h" static int saved_in_initrd = -1; @@ -41,24 +39,26 @@ void in_initrd_force(bool value) { saved_in_initrd = value; } -bool in_first_boot(void) { - static int first_boot = -1; +int in_first_boot(void) { + static int first_boot_env_parse_cached = -1; int r; - if (first_boot >= 0) - return first_boot; + if (first_boot_env_parse_cached >= 0) + return first_boot_env_parse_cached; - const char *e = secure_getenv("SYSTEMD_FIRST_BOOT"); - if (e) { - r = parse_boolean(e); - if (r < 0) - log_debug_errno(r, "Failed to parse $SYSTEMD_FIRST_BOOT, ignoring: %m"); - else - return (first_boot = r); - } + r = secure_getenv_bool("SYSTEMD_FIRST_BOOT"); + if (r >= 0) + return (first_boot_env_parse_cached = r); + if (r != -ENXIO) + log_debug_errno(r, "Failed to parse $SYSTEMD_FIRST_BOOT, ignoring: %m"); + /* This is not cached and must *never* be cached, other parts of systemd write it to signal + * an update to the first-boot state. */ r = RET_NERRNO(access("/run/systemd/first-boot", F_OK)); - if (r < 0 && r != -ENOENT) - log_debug_errno(r, "Failed to check if /run/systemd/first-boot exists, assuming no: %m"); - return r >= 0; + if (r >= 0) + return true; + if (r == -ENOENT) + return false; + + return log_debug_errno(r, "Failed to check /run/systemd/first-boot: %m"); } diff --git a/src/basic/initrd-util.h b/src/basic/initrd-util.h index ec55ec34028..f83b5c9d5d0 100644 --- a/src/basic/initrd-util.h +++ b/src/basic/initrd-util.h @@ -6,4 +6,4 @@ bool in_initrd(void); void in_initrd_force(bool value); -bool in_first_boot(void); +int in_first_boot(void); diff --git a/src/random-seed/random-seed-tool.c b/src/random-seed/random-seed-tool.c index 289291761f0..9f9439c8c57 100644 --- a/src/random-seed/random-seed-tool.c +++ b/src/random-seed/random-seed-tool.c @@ -15,6 +15,7 @@ #include "fd-util.h" #include "format-table.h" #include "fs-util.h" +#include "initrd-util.h" #include "io-util.h" #include "log.h" #include "main-func.h" @@ -88,8 +89,8 @@ static CreditEntropy may_credit(int seed_fd) { /* Don't credit the random seed if we are in first-boot mode, because we are supposed to start from * scratch. This is a safety precaution for cases where people ship "golden" images with empty * /etc but populated /var that contains a random seed. */ - r = RET_NERRNO(access("/run/systemd/first-boot", F_OK)); - if (r == -ENOENT) + r = in_first_boot(); + if (r == 0) /* All is good, we are not in first-boot mode. */ return CREDIT_ENTROPY_YES_PLEASE; if (r < 0) { diff --git a/src/shared/condition.c b/src/shared/condition.c index be2754c15eb..d879e24d9ef 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -974,7 +974,7 @@ static int condition_test_first_boot(Condition *c, char **env) { if (r < 0) return r; - return in_first_boot() == r; + return (in_first_boot() > 0) == r; } static int condition_test_environment(Condition *c, char **env) { diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index a1173714825..d646c617f41 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -1277,7 +1277,10 @@ static int check_null_key_policy(CredentialFlags flags) { * exists yet); the decision itself is made in credential_boot_policy_accepts_null(). */ CredentialBootPolicy policy = query_credential_boot_policy(); - bool first_boot = in_first_boot(), have_tpm2 = efi_has_tpm2(), secure_boot = is_efi_secure_boot(); + + bool have_tpm2 = efi_has_tpm2(), secure_boot = is_efi_secure_boot(); + /* in_first_boot() can return <0 on error: use the safe default in this case (not-first-boot). */ + bool first_boot = in_first_boot() > 0; if (!credential_boot_policy_accepts_null(policy, first_boot, have_tpm2, secure_boot)) return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON),