]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: run each system service with a fresh session keyring
authorLennart Poettering <lennart@poettering.net>
Fri, 2 Dec 2016 00:54:41 +0000 (01:54 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 13 Dec 2016 19:59:10 +0000 (20:59 +0100)
This patch ensures that each system service gets its own session kernel keyring
automatically, and implicitly. Without this a keyring is allocated for it
on-demand, but is then linked with the user's kernel keyring, which is OK
behaviour for logged in users, but not so much for system services.

With this change each service gets a session keyring that is specific to the
service and ceases to exist when the service is shut down. The session keyring
is not linked up with the user keyring and keys hence only search within the
session boundaries by default.

(This is useful in a later commit to store per-service material in the keyring,
for example the invocation ID)

(With input from David Howells)

src/basic/exit-status.c
src/basic/exit-status.h
src/basic/missing.h
src/core/execute.c
src/core/execute.h
src/core/service.c

index 59557f8afea2cf2816d7389b283816de5c297dc8..1e23c32c3f907cf959022fc60e251bbe476dd751 100644 (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";
                 }
         }
 
index 0cfdfd789199427ff495efdebe5e70f2eef503af..d22b2c00e45f7823552f05edd1e17007e8d712d0 100644 (file)
@@ -82,6 +82,7 @@ enum {
         EXIT_MAKE_STARTER,
         EXIT_CHOWN,
         EXIT_SMACK_PROCESS_LABEL,
+        EXIT_KEYRING,
 };
 
 typedef enum ExitStatusLevel {
index 1502b3f4f43f3876222db252c0397c10208c3a9d..dd4425697f1329406d95727b81f12f4204baece7 100644 (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
index 07ab067c05769bd1d1389037c15abd787296d52e..5ac270aa1242afac6be751c2f79a9ccd2b17f231 100644 (file)
@@ -2196,6 +2196,44 @@ 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;
+        }
+
+        /* 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);
@@ -2638,6 +2676,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);
index 951c8f4da3881057fcb1a027372bc39d838f408a..b376a6db558ca39fc55a9ecdbaa177197b17b0ed 100644 (file)
@@ -228,12 +228,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 {
index 576416ad29117cc6baf4fadb4f27d546198ade6e..73a8104d170c15d9895fd4476b29106a05b503ca 100644 (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;