From: Lennart Poettering Date: Mon, 2 Jun 2025 11:31:29 +0000 (+0200) Subject: core: when PrivateDevices= is enabled and we need to decrypt TPM2 credentials, go... X-Git-Tag: v258-rc1~257 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2be3a06bb23591098e02911851a9854d7998b152;p=thirdparty%2Fsystemd.git core: when PrivateDevices= is enabled and we need to decrypt TPM2 credentials, go via IPC Also, if a device ACL list is defined, also go via IPC (instead of trying to patch it, as before). The outcome is that the tighter rules continue to apply when configured. Fixes: #35959 --- diff --git a/NEWS b/NEWS index c317c20fbd8..acbaf968906 100644 --- a/NEWS +++ b/NEWS @@ -122,6 +122,19 @@ CHANGES WITH 258 in spe: GPG. The local keyring /etc/systemd/import-pubring.gpg is still parsed if present, to preserve backward compatibility. + * Normally, per-user encrypted credentials are decrypted via the the + systemd-creds.socket Varlink service, while the per-system ones are + directly encrypted within the execution context of the intended + service (which hence typically required access to /dev/tpmrm0). This + has been changed: units that enable either PrivateDevices= or use + DeviceAllow=/DevicePolicy= (and thus restrict access to device nodes) + will now also make use of the systemd-creds.socket Varlink + functionality, and will not attempt to decrypt the credentials + in-process (and attempt to try to talk to the TPM for + that). Previously, encrypted credentials for per-system services were + incompatible with PrivateDevices= and resulted in automatic extension + of the DeviceAllow= list. The latter behaviour has been removed. + Announcements of Future Feature Removals: * Support for System V service scripts is deprecated and will be diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index cd7b1056bef..f9e790e4923 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -3684,11 +3684,7 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX authenticated credentials improves security as credentials are not stored in plaintext and only authenticated and decrypted into plaintext the moment a service requiring them is started. Moreover, credentials may be bound to the local hardware and installations, so that they cannot easily be - analyzed offline, or be generated externally. When DevicePolicy= is set to - closed or strict, or set to auto and - DeviceAllow= is set, or PrivateDevices= is set, then this - setting adds /dev/tpmrm0 with rw mode to - DeviceAllow=. See + analyzed offline, or be generated externally. See systemd.resource-control5 for the details about DevicePolicy= or DeviceAllow=. diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c index 3077ddd92ed..06a2794c82d 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -3,6 +3,7 @@ #include #include "acl-util.h" +#include "cgroup.h" #include "creds-util.h" #include "errno-util.h" #include "exec-credential.h" @@ -237,22 +238,6 @@ bool exec_context_has_credentials(const ExecContext *c) { !ordered_set_isempty(c->import_credentials); } -bool exec_context_has_encrypted_credentials(const ExecContext *c) { - assert(c); - - const ExecLoadCredential *load_cred; - HASHMAP_FOREACH(load_cred, c->load_credentials) - if (load_cred->encrypted) - return true; - - const ExecSetCredential *set_cred; - HASHMAP_FOREACH(set_cred, c->set_credentials) - if (set_cred->encrypted) - return true; - - return false; -} - bool mount_point_is_credentials(const char *runtime_prefix, const char *path) { const char *e; @@ -445,8 +430,30 @@ static int credential_search_path(const ExecParameters *params, CredentialSearch return 0; } +static bool device_nodes_restricted( + const ExecContext *c, + const CGroupContext *cgroup_context) { + + assert(c); + assert(cgroup_context); + + /* Returns true if we have any reason to believe we might not be able to access the TPM device + * directly, even if we run as root/PID 1. This could be because /dev/ is replaced by a private + * version, or because a device node access list is configured. */ + + if (c->private_devices) + return true; + + if (cgroup_context->device_policy != CGROUP_DEVICE_POLICY_AUTO || + cgroup_context->device_allow) + return true; + + return false; +} + struct load_cred_args { const ExecContext *context; + const CGroupContext *cgroup_context; const ExecParameters *params; const char *unit; bool encrypted; @@ -473,20 +480,30 @@ static int maybe_decrypt_and_write_credential( assert(data || size == 0); if (args->encrypted) { + CredentialFlags flags = 0; /* only allow user creds in user scope */ + switch (args->params->runtime_scope) { case RUNTIME_SCOPE_SYSTEM: - /* In system mode talk directly to the TPM */ - r = decrypt_credential_and_warn( - id, - now(CLOCK_REALTIME), - /* tpm2_device= */ NULL, - /* tpm2_signature_path= */ NULL, - getuid(), - &IOVEC_MAKE(data, size), - CREDENTIAL_ANY_SCOPE, - &plaintext); - break; + /* In system mode talk directly to the TPM – unless we live in a device sandbox + * which might block TPM device access. */ + + flags |= CREDENTIAL_ANY_SCOPE; + + if (!device_nodes_restricted(args->context, args->cgroup_context)) { + r = decrypt_credential_and_warn( + id, + now(CLOCK_REALTIME), + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + getuid(), + &IOVEC_MAKE(data, size), + flags, + &plaintext); + break; + } + + _fallthrough_; case RUNTIME_SCOPE_USER: /* In per user mode we'll not have access to the machine secret, nor to the TPM (most @@ -498,7 +515,7 @@ static int maybe_decrypt_and_write_credential( now(CLOCK_REALTIME), getuid(), &IOVEC_MAKE(data, size), - /* flags= */ 0, /* only allow user creds in user scope */ + flags, &plaintext); break; @@ -770,6 +787,7 @@ static int load_cred_recurse_dir_cb( static int acquire_credentials( const ExecContext *context, + const CGroupContext *cgroup_context, const ExecParameters *params, const char *unit, const char *p, @@ -781,6 +799,7 @@ static int acquire_credentials( int r; assert(context); + assert(cgroup_context); assert(params); assert(unit); assert(p); @@ -795,6 +814,7 @@ static int acquire_credentials( struct load_cred_args args = { .context = context, + .cgroup_context = cgroup_context, .params = params, .unit = unit, .write_dfd = dfd, @@ -921,6 +941,7 @@ static int acquire_credentials( static int setup_credentials_internal( const ExecContext *context, + const CGroupContext *cgroup_context, const ExecParameters *params, const char *unit, const char *final, /* This is where the credential store shall eventually end up at */ @@ -1030,7 +1051,7 @@ static int setup_credentials_internal( (void) label_fix_full(AT_FDCWD, where, final, 0); - r = acquire_credentials(context, params, unit, where, uid, gid, workspace_mounted); + r = acquire_credentials(context, cgroup_context, params, unit, where, uid, gid, workspace_mounted); if (r < 0) { /* If we're using final place as workspace, and failed to acquire credentials, we might * have left half-written creds there. Let's get rid of the whole mount, so future @@ -1074,6 +1095,7 @@ static int setup_credentials_internal( int exec_setup_credentials( const ExecContext *context, + const CGroupContext *cgroup_context, const ExecParameters *params, const char *unit, uid_t uid, @@ -1140,6 +1162,7 @@ int exec_setup_credentials( r = setup_credentials_internal( context, + cgroup_context, params, unit, p, /* final mount point */ @@ -1177,6 +1200,7 @@ int exec_setup_credentials( r = setup_credentials_internal( context, + cgroup_context, params, unit, p, /* final mount point */ diff --git a/src/core/exec-credential.h b/src/core/exec-credential.h index c082d6d6f7e..b712b71e6e6 100644 --- a/src/core/exec-credential.h +++ b/src/core/exec-credential.h @@ -44,7 +44,6 @@ int exec_context_put_import_credential(ExecContext *c, const char *glob, const c bool exec_params_need_credentials(const ExecParameters *p); bool exec_context_has_credentials(const ExecContext *c); -bool exec_context_has_encrypted_credentials(const ExecContext *c); int exec_context_get_credential_directory( const ExecContext *context, @@ -56,6 +55,7 @@ int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_r int exec_setup_credentials( const ExecContext *context, + const CGroupContext *cgroup_context, const ExecParameters *params, const char *unit, uid_t uid, diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 9e7af81f1ef..dc06754eca6 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -5272,7 +5272,7 @@ int exec_invoke( return log_error_errno(r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]); } - r = exec_setup_credentials(context, params, params->unit_id, uid, gid); + r = exec_setup_credentials(context, cgroup_context, params, params->unit_id, uid, gid); if (r < 0) { *exit_status = EXIT_CREDENTIALS; return log_error_errno(r, "Failed to set up credentials: %m"); diff --git a/src/core/unit.c b/src/core/unit.c index 1f46b96ad95..1d23d24cc87 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -4427,13 +4427,6 @@ int unit_patch_contexts(Unit *u) { if (r < 0) return r; } - - /* If there are encrypted credentials we might need to access the TPM. */ - if (exec_context_has_encrypted_credentials(ec)) { - r = cgroup_context_add_device_allow(cc, "char-tpm", CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE); - if (r < 0) - return r; - } } }