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")) {