From b1edf4456eabc5951d76b96bc7df2db3feebe669 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 14 Sep 2017 21:19:05 +0200 Subject: [PATCH] core: add new per-unit setting KeyringMode= for controlling kernel keyring setup Usually, it's a good thing that we isolate the kernel session keyring for the various services and disconnect them from the user keyring. However, in case of the cryptsetup key caching we actually want that multiple instances of the cryptsetup service can share the keys in the root user's user keyring, hence we need to be able to disable this logic for them. This adds KeyringMode=inherit|private|shared: inherit: don't do any keyring magic (this is the default in systemd --user) private: a private keyring as before (default in systemd --system) shared: the new setting --- man/systemd.exec.xml | 20 +++++++ src/basic/missing.h | 4 ++ src/core/dbus-execute.c | 25 ++++++++- src/core/execute.c | 77 +++++++++++++++++++++++++-- src/core/execute.h | 13 +++++ src/core/load-fragment-gperf.gperf.m4 | 1 + src/core/load-fragment.c | 2 + src/core/load-fragment.h | 1 + src/core/unit.c | 6 ++- src/shared/bus-unit-util.c | 3 +- 10 files changed, 144 insertions(+), 8 deletions(-) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 53daff0f641..380501e04ac 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 653c5b7766e..eb6c42e1324 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 2c0124229b3..86799b13e78 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 e2d84feb0dd..825a0f9468f 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 f7c20dbcb3b..133eca5846b 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 94e29397f3d..f7d5f248619 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 66ad92d4605..d319934ee24 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 49b3b405df3..50910586ac4 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 9eda9643f60..df89f3d01f1 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 40f1d74d8a0..d6b119987c0 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")) { -- 2.39.2