Based on 524daa8c3e2bd63ad9dbc24711cdcfb45a65b2db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Thu, 1 Jan 2015 04:40:41 +0100
Subject: journal: call connect() with dropped privileges

When systemd starts a service, it first opened /run/systemd/journal/stdout
socket, and only later switched to the right user.group (if they are
specified). Later on, journald looked at the credentials, and saw
root.root, because credentials are stored at the time the socket is
opened. As a result, all messages passed over _TRANSPORT=stdout were
logged with _UID=0, _GID=0.

Drop real uid and gid temporarily to fix the issue.

---
 src/core/execute.c |   82 +++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 61 insertions(+), 21 deletions(-)

--- src/core/execute.c
+++ src/core/execute.c	2015-01-22 11:53:55.845518536 +0000
@@ -215,12 +215,52 @@ static int open_null_as(int flags, int n
         return r;
 }
 
-static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, const char *unit_id, int nfd) {
-        int fd, r;
+static int connect_journal_socket(int fd, uid_t uid, gid_t gid) {
         union sockaddr_union sa = {
                 .un.sun_family = AF_UNIX,
                 .un.sun_path = "/run/systemd/journal/stdout",
         };
+        uid_t olduid = ((uid_t) -1);
+        gid_t oldgid = ((gid_t) -1);
+        int r;
+
+        if (gid != ((gid_t) -1)) {
+                oldgid = getgid();
+
+                r = setegid(gid);
+                if (r < 0)
+                        return -errno;
+        }
+
+        if (uid != ((uid_t) -1)) {
+                olduid = getuid();
+
+                r = seteuid(uid);
+                if (r < 0) {
+                        r = -errno;
+                        goto restore_gid;
+                }
+        }
+
+        r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
+        if (r < 0)
+                r = -errno;
+
+        /* If we fail to restore the uid or gid, things will likely
+           fail later on. This should only happen if an LSM interferes. */
+
+        if (uid != ((uid_t) -1))
+                (void) seteuid(olduid);
+
+ restore_gid:
+        if (gid != ((gid_t) -1))
+                (void) setegid(oldgid);
+
+        return r;
+}
+
+static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, const char *unit_id, int nfd, uid_t uid, gid_t gid) {
+        int fd, r;
 
         assert(context);
         assert(output < _EXEC_OUTPUT_MAX);
@@ -231,10 +271,10 @@ static int connect_logger_as(const ExecC
         if (fd < 0)
                 return -errno;
 
-        r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
+        r = connect_journal_socket(fd, uid, gid);
         if (r < 0) {
                 close_nointr_nofail(fd);
-                return -errno;
+                return r;
         }
 
         if (shutdown(fd, SHUT_RD) < 0) {
@@ -354,7 +394,7 @@ static int setup_input(const ExecContext
         }
 }
 
-static int setup_output(const ExecContext *context, int fileno, int socket_fd, const char *ident, const char *unit_id, bool apply_tty_stdin) {
+static int setup_output(const ExecContext *context, int fileno, int socket_fd, const char *ident, const char *unit_id, bool apply_tty_stdin, uid_t uid, gid_t gid) {
         ExecOutput o;
         ExecInput i;
         int r;
@@ -421,9 +461,9 @@ static int setup_output(const ExecContex
         case EXEC_OUTPUT_KMSG_AND_CONSOLE:
         case EXEC_OUTPUT_JOURNAL:
         case EXEC_OUTPUT_JOURNAL_AND_CONSOLE:
-                r = connect_logger_as(context, o, ident, unit_id, fileno);
+                r = connect_logger_as(context, o, ident, unit_id, fileno, uid, gid);
                 if (r < 0) {
-                        log_struct_unit(LOG_CRIT, unit_id,
+                        log_struct_unit(LOG_ERR, unit_id,
                                 "MESSAGE=Failed to connect std%s of %s to the journal socket: %s",
                                 fileno == STDOUT_FILENO ? "out" : "err",
                                 unit_id, strerror(-r),
@@ -1291,6 +1331,15 @@ int exec_spawn(ExecCommand *command,
                         }
                 }
 
+                if (context->user) {
+                        username = context->user;
+                        err = get_user_creds(&username, &uid, &gid, &home, &shell);
+                        if (err < 0) {
+                                r = EXIT_USER;
+                                goto fail_child;
+                        }
+                }
+
                 /* If a socket is connected to STDIN/STDOUT/STDERR, we
                  * must sure to drop O_NONBLOCK */
                 if (socket_fd >= 0)
@@ -1302,13 +1351,13 @@ int exec_spawn(ExecCommand *command,
                         goto fail_child;
                 }
 
-                err = setup_output(context, STDOUT_FILENO, socket_fd, basename(command->path), unit_id, apply_tty_stdin);
+                err = setup_output(context, STDOUT_FILENO, socket_fd, basename(command->path), unit_id, apply_tty_stdin, uid, gid);
                 if (err < 0) {
                         r = EXIT_STDOUT;
                         goto fail_child;
                 }
 
-                err = setup_output(context, STDERR_FILENO, socket_fd, basename(command->path), unit_id, apply_tty_stdin);
+                err = setup_output(context, STDERR_FILENO, socket_fd, basename(command->path), unit_id, apply_tty_stdin, uid, gid);
                 if (err < 0) {
                         r = EXIT_STDERR;
                         goto fail_child;
@@ -1390,21 +1439,12 @@ int exec_spawn(ExecCommand *command,
                 if (context->utmp_id)
                         utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path);
 
-                if (context->user) {
-                        username = context->user;
-                        err = get_user_creds(&username, &uid, &gid, &home, &shell);
+                if (context->user && is_terminal_input(context->std_input)) {
+                        err = chown_terminal(STDIN_FILENO, uid);
                         if (err < 0) {
-                                r = EXIT_USER;
+                                r = EXIT_STDIN;
                                 goto fail_child;
                         }
-
-                        if (is_terminal_input(context->std_input)) {
-                                err = chown_terminal(STDIN_FILENO, uid);
-                                if (err < 0) {
-                                        r = EXIT_STDIN;
-                                        goto fail_child;
-                                }
-                        }
                 }
 
 #ifdef HAVE_PAM
