diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 53daff0f64..380501e04a 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -1720,6 +1720,26 @@ CapabilityBoundingSet=~CAP_B CAP_C
NoNewPrivileges=yes is implied.
+
+ KeyringMode=
+
+ Controls how the kernel session keyring is set up for the service (see session-keyring7 for
+ details on the session keyring). Takes one of , ,
+ . If set to no special keyring setup is done, and the kernel's
+ default behaviour is applied. If is used a new session keyring is allocated when a
+ service process is invoked, and it is not linked up with any user keyring. This is the recommended setting for
+ system services, as this ensures that multiple services running under the same system user ID (in particular
+ the root user) do not share their key material among each other. If is used a new
+ session keyring is allocated as for , but the user keyring of the user configured with
+ User= is linked into it, so that keys assigned to the user may be requested by the unit's
+ processes. In this modes multiple units running processes under the same user ID may share key material. Unless
+ is selected the unique invocation ID for the unit (see below) is added as a protected
+ key by the name invocation_id to the newly created session keyring. Defaults to
+ for the system service manager and to for the user service
+ manager.
+
+
RuntimeDirectory=
diff --git a/src/basic/missing.h b/src/basic/missing.h
index 653c5b7766..eb6c42e132 100644
--- a/src/basic/missing.h
+++ b/src/basic/missing.h
@@ -1128,6 +1128,10 @@ typedef int32_t key_serial_t;
#define KEYCTL_DESCRIBE 6
#endif
+#ifndef KEYCTL_LINK
+#define KEYCTL_LINK 8
+#endif
+
#ifndef KEYCTL_READ
#define KEYCTL_READ 11
#endif
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 2c0124229b..86799b13e7 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -53,12 +53,11 @@
#include "utf8.h"
BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput);
-
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInput);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode);
-
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode);
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode);
static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_home, protect_home, ProtectHome);
static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_system, protect_system, ProtectSystem);
@@ -873,6 +872,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("BindPaths", "a(ssbt)", property_get_bind_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("BindReadOnlyPaths", "a(ssbt)", property_get_bind_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MountAPIVFS", "b", bus_property_get_bool, offsetof(ExecContext, mount_apivfs), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("KeyringMode", "s", property_get_exec_keyring_mode, offsetof(ExecContext, keyring_mode), SD_BUS_VTABLE_PROPERTY_CONST),
/* Obsolete/redundant properties: */
SD_BUS_PROPERTY("Capabilities", "s", property_get_empty_string, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
@@ -2139,6 +2139,27 @@ int bus_exec_context_set_transient_property(
return 1;
+ } else if (streq(name, "KeyringMode")) {
+
+ const char *s;
+ ExecKeyringMode m;
+
+ r = sd_bus_message_read(message, "s", &s);
+ if (r < 0)
+ return r;
+
+ m = exec_keyring_mode_from_string(s);
+ if (m < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid key ring mode");
+
+ if (mode != UNIT_CHECK) {
+ c->keyring_mode = m;
+
+ unit_write_drop_in_private_format(u, mode, name, "KeyringMode=%s", exec_keyring_mode_to_string(m));
+ }
+
+ return 1;
+
} else if (streq(name, "RuntimeDirectoryPreserve")) {
const char *s;
ExecPreserveMode m;
diff --git a/src/core/execute.c b/src/core/execute.c
index e2d84feb0d..825a0f9468 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -2163,10 +2163,17 @@ static int apply_working_directory(
return 0;
}
-static int setup_keyring(Unit *u, const ExecParameters *p, uid_t uid, gid_t gid) {
+static int setup_keyring(
+ Unit *u,
+ const ExecContext *context,
+ const ExecParameters *p,
+ uid_t uid, gid_t gid) {
+
key_serial_t keyring;
+ int r;
assert(u);
+ assert(context);
assert(p);
/* Let's set up a new per-service "session" kernel keyring for each system service. This has the benefit that
@@ -2179,6 +2186,9 @@ static int setup_keyring(Unit *u, const ExecParameters *p, uid_t uid, gid_t gid)
if (!(p->flags & EXEC_NEW_KEYRING))
return 0;
+ if (context->keyring_mode == EXEC_KEYRING_INHERIT)
+ return 0;
+
keyring = keyctl(KEYCTL_JOIN_SESSION_KEYRING, 0, 0, 0, 0);
if (keyring == -1) {
if (errno == ENOSYS)
@@ -2213,6 +2223,55 @@ static int setup_keyring(Unit *u, const ExecParameters *p, uid_t uid, gid_t gid)
if (keyctl(KEYCTL_CHOWN, keyring, uid, gid, 0) < 0)
return log_error_errno(errno, "Failed to change ownership of session keyring: %m");
+ /* When requested link the user keyring into the session keyring. */
+ if (context->keyring_mode == EXEC_KEYRING_SHARED) {
+ uid_t saved_uid;
+ gid_t saved_gid;
+
+ /* Acquiring a reference to the user keyring is nasty. We briefly change identity in order to get things
+ * set up properly by the kernel. If we don't do that then we can't create it atomically, and that
+ * sucks for parallel execution. This mimics what pam_keyinit does, too.*/
+
+ saved_uid = getuid();
+ saved_gid = getgid();
+
+ if (gid_is_valid(gid) && gid != saved_gid) {
+ if (setregid(gid, -1) < 0)
+ return log_error_errno(errno, "Failed to change GID for user keyring: %m");
+ }
+
+ if (uid_is_valid(uid) && uid != saved_uid) {
+ if (setreuid(uid, -1) < 0) {
+ (void) setregid(saved_gid, -1);
+ return log_error_errno(errno, "Failed to change UID for user keyring: %m");
+ }
+ }
+
+ if (keyctl(KEYCTL_LINK,
+ KEY_SPEC_USER_KEYRING,
+ KEY_SPEC_SESSION_KEYRING, 0, 0) < 0) {
+
+ r = -errno;
+
+ (void) setreuid(saved_uid, -1);
+ (void) setregid(saved_gid, -1);
+
+ return log_error_errno(r, "Failed to link user keyring into session keyring: %m");
+ }
+
+ if (uid_is_valid(uid) && uid != saved_uid) {
+ if (setreuid(saved_uid, -1) < 0) {
+ (void) setregid(saved_gid, -1);
+ return log_error_errno(errno, "Failed to change UID back for user keyring: %m");
+ }
+ }
+
+ if (gid_is_valid(gid) && gid != saved_gid) {
+ if (setregid(saved_gid, -1) < 0)
+ return log_error_errno(errno, "Failed to change GID back for user keyring: %m");
+ }
+ }
+
return 0;
}
@@ -2705,7 +2764,7 @@ static int exec_child(
(void) umask(context->umask);
- r = setup_keyring(unit, params, uid, gid);
+ r = setup_keyring(unit, context, params, uid, gid);
if (r < 0) {
*exit_status = EXIT_KEYRING;
return r;
@@ -3571,7 +3630,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
"%sMountAPIVFS: %s\n"
"%sIgnoreSIGPIPE: %s\n"
"%sMemoryDenyWriteExecute: %s\n"
- "%sRestrictRealtime: %s\n",
+ "%sRestrictRealtime: %s\n"
+ "%sKeyringMode: %s\n",
prefix, c->umask,
prefix, c->working_directory ? c->working_directory : "/",
prefix, c->root_directory ? c->root_directory : "/",
@@ -3588,7 +3648,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
prefix, yes_no(c->mount_apivfs),
prefix, yes_no(c->ignore_sigpipe),
prefix, yes_no(c->memory_deny_write_execute),
- prefix, yes_no(c->restrict_realtime));
+ prefix, yes_no(c->restrict_realtime),
+ prefix, exec_keyring_mode_to_string(c->keyring_mode));
if (c->root_image)
fprintf(f, "%sRootImage: %s\n", prefix, c->root_image);
@@ -4368,3 +4429,11 @@ static const char* const exec_directory_type_table[_EXEC_DIRECTORY_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(exec_directory_type, ExecDirectoryType);
+
+static const char* const exec_keyring_mode_table[_EXEC_KEYRING_MODE_MAX] = {
+ [EXEC_KEYRING_INHERIT] = "inherit",
+ [EXEC_KEYRING_PRIVATE] = "private",
+ [EXEC_KEYRING_SHARED] = "shared",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(exec_keyring_mode, ExecKeyringMode);
diff --git a/src/core/execute.h b/src/core/execute.h
index f7c20dbcb3..133eca5846 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -80,6 +80,14 @@ typedef enum ExecPreserveMode {
_EXEC_PRESERVE_MODE_INVALID = -1
} ExecPreserveMode;
+typedef enum ExecKeyringMode {
+ EXEC_KEYRING_INHERIT,
+ EXEC_KEYRING_PRIVATE,
+ EXEC_KEYRING_SHARED,
+ _EXEC_KEYRING_MODE_MAX,
+ _EXEC_KEYRING_MODE_INVALID = -1,
+} ExecKeyringMode;
+
struct ExecStatus {
dual_timestamp start_timestamp;
dual_timestamp exit_timestamp;
@@ -189,6 +197,8 @@ struct ExecContext {
bool smack_process_label_ignore;
char *smack_process_label;
+ ExecKeyringMode keyring_mode;
+
char **read_write_paths, **read_only_paths, **inaccessible_paths;
unsigned long mount_flags;
BindMount *bind_mounts;
@@ -368,5 +378,8 @@ ExecUtmpMode exec_utmp_mode_from_string(const char *s) _pure_;
const char* exec_preserve_mode_to_string(ExecPreserveMode i) _const_;
ExecPreserveMode exec_preserve_mode_from_string(const char *s) _pure_;
+const char* exec_keyring_mode_to_string(ExecKeyringMode i) _const_;
+ExecKeyringMode exec_keyring_mode_from_string(const char *s) _pure_;
+
const char* exec_directory_type_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_directory_type_from_string(const char *s) _pure_;
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 94e29397f3..f7d5f24861 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -54,6 +54,7 @@ $1.CapabilityBoundingSet, config_parse_capability_set, 0,
$1.AmbientCapabilities, config_parse_capability_set, 0, offsetof($1, exec_context.capability_ambient_set)
$1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec)
$1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context)
+$1.KeyringMode, config_parse_exec_keyring_mode, 0, offsetof($1, exec_context.keyring_mode)
m4_ifdef(`HAVE_SECCOMP',
`$1.SystemCallFilter, config_parse_syscall_filter, 0, offsetof($1, exec_context)
$1.SystemCallArchitectures, config_parse_syscall_archs, 0, offsetof($1, exec_context.syscall_archs)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 66ad92d460..d319934ee2 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -4163,6 +4163,8 @@ int config_parse_protect_system(
return 0;
}
+DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode, "Failed to parse keyring mode");
+
#define FOLLOW_MAX 8
static int open_follow(char **filename, FILE **_f, Set *names, char **_final) {
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 49b3b405df..50910586ac 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -117,6 +117,7 @@ int config_parse_user_group(const char *unit, const char *filename, unsigned lin
int config_parse_user_group_strv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_restrict_namespaces(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_bind_paths(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_exec_keyring_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/core/unit.c b/src/core/unit.c
index 9eda9643f6..df89f3d01f 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -162,9 +162,13 @@ static void unit_init(Unit *u) {
}
ec = unit_get_exec_context(u);
- if (ec)
+ if (ec) {
exec_context_init(ec);
+ ec->keyring_mode = MANAGER_IS_SYSTEM(u->manager) ?
+ EXEC_KEYRING_PRIVATE : EXEC_KEYRING_INHERIT;
+ }
+
kc = unit_get_kill_context(u);
if (kc)
kill_context_init(kc);
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 40f1d74d8a..d6b119987c 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -273,7 +273,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
"Description", "Slice", "Type", "WorkingDirectory",
"RootDirectory", "SyslogIdentifier", "ProtectSystem",
"ProtectHome", "SELinuxContext", "Restart", "RootImage",
- "NotifyAccess", "RuntimeDirectoryPreserve", "Personality"))
+ "NotifyAccess", "RuntimeDirectoryPreserve", "Personality",
+ "KeyringMode"))
r = sd_bus_message_append(m, "v", "s", eq);
else if (STR_IN_SET(field, "AppArmorProfile", "SmackProcessLabel")) {