Merge pull request #4806 from poettering/keyring-init

set up a per-service session kernel keyring, and store the invocation ID in it
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2016-12-13 23:24:42 -05:00 committed by GitHub
commit 4014818d53
9 changed files with 254 additions and 13 deletions

View file

@ -148,6 +148,9 @@ const char* exit_status_to_string(int status, ExitStatusLevel level) {
case EXIT_SMACK_PROCESS_LABEL:
return "SMACK_PROCESS_LABEL";
case EXIT_KEYRING:
return "KEYRING";
}
}

View file

@ -82,6 +82,7 @@ enum {
EXIT_MAKE_STARTER,
EXIT_CHOWN,
EXIT_SMACK_PROCESS_LABEL,
EXIT_KEYRING,
};
typedef enum ExitStatusLevel {

View file

@ -1026,6 +1026,22 @@ struct btrfs_ioctl_quota_ctl_args {
typedef int32_t key_serial_t;
#endif
#ifndef KEYCTL_JOIN_SESSION_KEYRING
#define KEYCTL_JOIN_SESSION_KEYRING 1
#endif
#ifndef KEYCTL_CHOWN
#define KEYCTL_CHOWN 4
#endif
#ifndef KEYCTL_SETPERM
#define KEYCTL_SETPERM 5
#endif
#ifndef KEYCTL_DESCRIBE
#define KEYCTL_DESCRIBE 6
#endif
#ifndef KEYCTL_READ
#define KEYCTL_READ 11
#endif
@ -1034,10 +1050,44 @@ typedef int32_t key_serial_t;
#define KEYCTL_SET_TIMEOUT 15
#endif
#ifndef KEY_POS_VIEW
#define KEY_POS_VIEW 0x01000000
#define KEY_POS_READ 0x02000000
#define KEY_POS_WRITE 0x04000000
#define KEY_POS_SEARCH 0x08000000
#define KEY_POS_LINK 0x10000000
#define KEY_POS_SETATTR 0x20000000
#define KEY_USR_VIEW 0x00010000
#define KEY_USR_READ 0x00020000
#define KEY_USR_WRITE 0x00040000
#define KEY_USR_SEARCH 0x00080000
#define KEY_USR_LINK 0x00100000
#define KEY_USR_SETATTR 0x00200000
#define KEY_GRP_VIEW 0x00000100
#define KEY_GRP_READ 0x00000200
#define KEY_GRP_WRITE 0x00000400
#define KEY_GRP_SEARCH 0x00000800
#define KEY_GRP_LINK 0x00001000
#define KEY_GRP_SETATTR 0x00002000
#define KEY_OTH_VIEW 0x00000001
#define KEY_OTH_READ 0x00000002
#define KEY_OTH_WRITE 0x00000004
#define KEY_OTH_SEARCH 0x00000008
#define KEY_OTH_LINK 0x00000010
#define KEY_OTH_SETATTR 0x00000020
#endif
#ifndef KEY_SPEC_USER_KEYRING
#define KEY_SPEC_USER_KEYRING -4
#endif
#ifndef KEY_SPEC_SESSION_KEYRING
#define KEY_SPEC_SESSION_KEYRING -3
#endif
#ifndef PR_CAP_AMBIENT
#define PR_CAP_AMBIENT 47
#endif

View file

@ -2201,6 +2201,59 @@ static int apply_working_directory(const ExecContext *context,
return 0;
}
static int setup_keyring(Unit *u, const ExecParameters *p, uid_t uid, gid_t gid) {
key_serial_t keyring;
assert(u);
assert(p);
/* Let's set up a new per-service "session" kernel keyring for each system service. This has the benefit that
* each service runs with its own keyring shared among all processes of the service, but with no hook-up beyond
* that scope, and in particular no link to the per-UID keyring. If we don't do this the keyring will be
* automatically created on-demand and then linked to the per-UID keyring, by the kernel. The kernel's built-in
* on-demand behaviour is very appropriate for login users, but probably not so much for system services, where
* UIDs are not necessarily specific to a service but reused (at least in the case of UID 0). */
if (!(p->flags & EXEC_NEW_KEYRING))
return 0;
keyring = keyctl(KEYCTL_JOIN_SESSION_KEYRING, 0, 0, 0, 0);
if (keyring == -1) {
if (errno == ENOSYS)
log_debug_errno(errno, "Kernel keyring not supported, ignoring.");
else if (IN_SET(errno, EACCES, EPERM))
log_debug_errno(errno, "Kernel keyring access prohibited, ignoring.");
else if (errno == EDQUOT)
log_debug_errno(errno, "Out of kernel keyrings to allocate, ignoring.");
else
return log_error_errno(errno, "Setting up kernel keyring failed: %m");
return 0;
}
/* Populate they keyring with the invocation ID by default. */
if (!sd_id128_is_null(u->invocation_id)) {
key_serial_t key;
key = add_key("user", "invocation_id", &u->invocation_id, sizeof(u->invocation_id), KEY_SPEC_SESSION_KEYRING);
if (key == -1)
log_debug_errno(errno, "Failed to add invocation ID to keyring, ignoring: %m");
else {
if (keyctl(KEYCTL_SETPERM, key,
KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH|
KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH, 0, 0) < 0)
return log_error_errno(errno, "Failed to restrict invocation ID permission: %m");
}
}
/* And now, make the keyring owned by the service's user */
if (uid_is_valid(uid) || gid_is_valid(gid))
if (keyctl(KEYCTL_CHOWN, keyring, uid, gid, 0) < 0)
return log_error_errno(errno, "Failed to change ownership of session keyring: %m");
return 0;
}
static void append_socket_pair(int *array, unsigned *n, int pair[2]) {
assert(array);
assert(n);
@ -2643,6 +2696,12 @@ static int exec_child(
(void) umask(context->umask);
r = setup_keyring(unit, params, uid, gid);
if (r < 0) {
*exit_status = EXIT_KEYRING;
return r;
}
if ((params->flags & EXEC_APPLY_PERMISSIONS) && !command->privileged) {
if (context->pam_name && username) {
r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, fds, n_fds);

View file

@ -230,12 +230,13 @@ typedef enum ExecFlags {
EXEC_APPLY_PERMISSIONS = 1U << 0,
EXEC_APPLY_CHROOT = 1U << 1,
EXEC_APPLY_TTY_STDIN = 1U << 2,
EXEC_NEW_KEYRING = 1U << 3,
/* The following are not used by execute.c, but by consumers internally */
EXEC_PASS_FDS = 1U << 3,
EXEC_IS_CONTROL = 1U << 4,
EXEC_SETENV_RESULT = 1U << 5,
EXEC_SET_WATCHDOG = 1U << 6,
EXEC_PASS_FDS = 1U << 4,
EXEC_IS_CONTROL = 1U << 5,
EXEC_SETENV_RESULT = 1U << 6,
EXEC_SET_WATCHDOG = 1U << 7,
} ExecFlags;
struct ExecParameters {

View file

@ -1344,6 +1344,7 @@ static int service_spawn(
} else
path = UNIT(s)->cgroup_path;
exec_params.flags |= MANAGER_IS_SYSTEM(UNIT(s)->manager) ? EXEC_NEW_KEYRING : 0;
exec_params.argv = c->argv;
exec_params.environment = final_env;
exec_params.fds = fds;

View file

@ -23,13 +23,16 @@
#include "sd-id128.h"
#include "alloc-util.h"
#include "fd-util.h"
#include "hexdecoct.h"
#include "id128-util.h"
#include "io-util.h"
#include "khash.h"
#include "macro.h"
#include "missing.h"
#include "random-util.h"
#include "user-util.h"
#include "util.h"
_public_ char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]) {
@ -130,6 +133,105 @@ _public_ int sd_id128_get_boot(sd_id128_t *ret) {
return 0;
}
static int get_invocation_from_keyring(sd_id128_t *ret) {
_cleanup_free_ char *description = NULL;
char *d, *p, *g, *u, *e;
unsigned long perms;
key_serial_t key;
size_t sz = 256;
uid_t uid;
gid_t gid;
int r, c;
#define MAX_PERMS ((unsigned long) (KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH| \
KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH))
assert(ret);
key = request_key("user", "invocation_id", NULL, 0);
if (key == -1) {
/* Keyring support not available? No invocation key stored? */
if (IN_SET(errno, ENOSYS, ENOKEY))
return 0;
return -errno;
}
for (;;) {
description = new(char, sz);
if (!description)
return -ENOMEM;
c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0);
if (c < 0)
return -errno;
if ((size_t) c <= sz)
break;
sz = c;
free(description);
}
/* The kernel returns a final NUL in the string, verify that. */
assert(description[c-1] == 0);
/* Chop off the final description string */
d = strrchr(description, ';');
if (!d)
return -EIO;
*d = 0;
/* Look for the permissions */
p = strrchr(description, ';');
if (!p)
return -EIO;
errno = 0;
perms = strtoul(p + 1, &e, 16);
if (errno > 0)
return -errno;
if (e == p + 1) /* Read at least one character */
return -EIO;
if (e != d) /* Must reached the end */
return -EIO;
if ((perms & ~MAX_PERMS) != 0)
return -EPERM;
*p = 0;
/* Look for the group ID */
g = strrchr(description, ';');
if (!g)
return -EIO;
r = parse_gid(g + 1, &gid);
if (r < 0)
return r;
if (gid != 0)
return -EPERM;
*g = 0;
/* Look for the user ID */
u = strrchr(description, ';');
if (!u)
return -EIO;
r = parse_uid(u + 1, &uid);
if (r < 0)
return r;
if (uid != 0)
return -EPERM;
c = keyctl(KEYCTL_READ, key, (unsigned long) ret, sizeof(sd_id128_t), 0);
if (c < 0)
return -errno;
if (c != sizeof(sd_id128_t))
return -EIO;
return 1;
}
_public_ int sd_id128_get_invocation(sd_id128_t *ret) {
static thread_local sd_id128_t saved_invocation_id = {};
int r;
@ -137,15 +239,31 @@ _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
assert_return(ret, -EINVAL);
if (sd_id128_is_null(saved_invocation_id)) {
const char *e;
e = secure_getenv("INVOCATION_ID");
if (!e)
return -ENXIO;
/* We first try to read the invocation ID from the kernel keyring. This has the benefit that it is not
* fakeable by unprivileged code. If the information is not available in the keyring, we use
* $INVOCATION_ID but ignore the data if our process was called by less privileged code
* (i.e. secure_getenv() instead of getenv()).
*
* The kernel keyring is only relevant for system services (as for user services we don't store the
* invocation ID in the keyring, as there'd be no trust benefit in that). The environment variable is
* primarily relevant for user services, and sufficiently safe as no privilege boundary is involved. */
r = sd_id128_from_string(e, &saved_invocation_id);
r = get_invocation_from_keyring(&saved_invocation_id);
if (r < 0)
return r;
if (r == 0) {
const char *e;
e = secure_getenv("INVOCATION_ID");
if (!e)
return -ENXIO;
r = sd_id128_from_string(e, &saved_invocation_id);
if (r < 0)
return r;
}
}
*ret = saved_invocation_id;

View file

@ -3,10 +3,10 @@
# Used by systemd --user instances.
account required pam_unix.so
m4_ifdef(`HAVE_SELINUX',
session required pam_selinux.so close
session required pam_selinux.so nottys open
session required pam_selinux.so close
session required pam_selinux.so nottys open
)m4_dnl
session required pam_loginuid.so
session required pam_loginuid.so
session optional pam_keyinit.so force revoke
session optional pam_systemd.so

View file

@ -39,6 +39,7 @@ int main(int argc, char *argv[]) {
char t[33], q[37];
_cleanup_free_ char *b = NULL;
_cleanup_close_ int fd = -1;
int r;
assert_se(sd_id128_randomize(&id) == 0);
printf("random: %s\n", sd_id128_to_string(id, t));
@ -159,5 +160,12 @@ int main(int argc, char *argv[]) {
assert_se(sd_id128_get_machine_app_specific(SD_ID128_MAKE(51,df,0b,4b,c3,b0,4c,97,80,e2,99,b9,8c,a3,73,b8), &id2) >= 0);
assert_se(!sd_id128_equal(id, id2));
/* Query the invocation ID */
r = sd_id128_get_invocation(&id);
if (r < 0)
log_warning_errno(r, "Failed to get invocation ID, ignoring: %m");
else
log_info("Invocation ID: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
return 0;
}