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
This commit is contained in:
Lennart Poettering 2017-09-14 21:19:05 +02:00
parent 4e962627bc
commit b1edf4456e
10 changed files with 144 additions and 8 deletions

View File

@ -1720,6 +1720,26 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
<varname>NoNewPrivileges=yes</varname> is implied.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>KeyringMode=</varname></term>
<listitem><para>Controls how the kernel session keyring is set up for the service (see <citerefentry
project='man-pages'><refentrytitle>session-keyring</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
details on the session keyring). Takes one of <option>inherit</option>, <option>private</option>,
<option>shared</option>. If set to <option>inherit</option> no special keyring setup is done, and the kernel's
default behaviour is applied. If <option>private</option> 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 <option>shared</option> is used a new
session keyring is allocated as for <option>private</option>, but the user keyring of the user configured with
<varname>User=</varname> 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
<option>inherit</option> is selected the unique invocation ID for the unit (see below) is added as a protected
key by the name <literal>invocation_id</literal> to the newly created session keyring. Defaults to
<option>private</option> for the system service manager and to <option>inherit</option> for the user service
manager.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>RuntimeDirectory=</varname></term>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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