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
This commit is contained in:
Lennart Poettering
2025-06-02 13:31:29 +02:00
committed by Mike Yuan
parent aecb6eaed7
commit 2be3a06bb2
6 changed files with 69 additions and 43 deletions

13
NEWS
View File

@@ -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

View File

@@ -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 <varname>DevicePolicy=</varname> is set to
<literal>closed</literal> or <literal>strict</literal>, or set to <literal>auto</literal> and
<varname>DeviceAllow=</varname> is set, or <varname>PrivateDevices=</varname> is set, then this
setting adds <filename>/dev/tpmrm0</filename> with <constant>rw</constant> mode to
<varname>DeviceAllow=</varname>. See
analyzed offline, or be generated externally. See
<citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for the details about <varname>DevicePolicy=</varname> or <varname>DeviceAllow=</varname>.</para>

View File

@@ -3,6 +3,7 @@
#include <sys/mount.h>
#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 */

View File

@@ -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,

View File

@@ -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");

View File

@@ -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;
}
}
}