]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/core/execute.c
util-lib: split out IO related calls to io-util.[ch]
[thirdparty/systemd.git] / src / core / execute.c
index 6e14848cd43d163ae83ac47a2181026bcf1ed84b..55da8ba4b72122f837315ab36fd81cc9500143b8 100644 (file)
 
 #include <errno.h>
 #include <fcntl.h>
-#include <unistd.h>
-#include <string.h>
+#include <glob.h>
+#include <grp.h>
+#include <poll.h>
 #include <signal.h>
-#include <sys/socket.h>
-#include <sys/un.h>
+#include <string.h>
+#include <sys/personality.h>
 #include <sys/prctl.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
-#include <grp.h>
-#include <poll.h>
-#include <glob.h>
+#include <sys/un.h>
+#include <unistd.h>
 #include <utmpx.h>
-#include <sys/personality.h>
 
 #ifdef HAVE_PAM
 #include <security/pam_appl.h>
 #endif
 
 #include "sd-messages.h"
-#include "rm-rf.h"
-#include "strv.h"
-#include "macro.h"
+
+#include "af-list.h"
+#ifdef HAVE_APPARMOR
+#include "apparmor-util.h"
+#endif
+#include "async.h"
+#include "barrier.h"
+#include "bus-endpoint.h"
+#include "cap-list.h"
 #include "capability.h"
-#include "util.h"
-#include "log.h"
-#include "ioprio.h"
-#include "securebits.h"
-#include "namespace.h"
-#include "exit-status.h"
-#include "missing.h"
-#include "utmp-wtmp.h"
 #include "def.h"
-#include "path-util.h"
 #include "env-util.h"
-#include "fileio.h"
-#include "unit.h"
-#include "async.h"
-#include "selinux-util.h"
 #include "errno-list.h"
-#include "af-list.h"
-#include "mkdir.h"
-#include "smack-util.h"
-#include "bus-endpoint.h"
-#include "cap-list.h"
+#include "execute.h"
+#include "exit-status.h"
+#include "fd-util.h"
+#include "fileio.h"
 #include "formats-util.h"
+#include "io-util.h"
+#include "ioprio.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "mkdir.h"
+#include "namespace.h"
+#include "path-util.h"
 #include "process-util.h"
-#include "terminal-util.h"
-#include "signal-util.h"
-
-#ifdef HAVE_APPARMOR
-#include "apparmor-util.h"
-#endif
-
+#include "rm-rf.h"
 #ifdef HAVE_SECCOMP
 #include "seccomp-util.h"
 #endif
-
-#include "execute.h"
+#include "securebits.h"
+#include "selinux-util.h"
+#include "signal-util.h"
+#include "smack-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "unit.h"
+#include "util.h"
+#include "utmp-wtmp.h"
 
 #define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC)
 #define IDLE_TIMEOUT2_USEC (1*USEC_PER_SEC)
@@ -357,12 +359,28 @@ static int fixup_output(ExecOutput std_output, int socket_fd) {
         return std_output;
 }
 
-static int setup_input(const ExecContext *context, int socket_fd, bool apply_tty_stdin) {
+static int setup_input(
+                const ExecContext *context,
+                const ExecParameters *params,
+                int socket_fd) {
+
         ExecInput i;
 
         assert(context);
+        assert(params);
 
-        i = fixup_input(context->std_input, socket_fd, apply_tty_stdin);
+        if (params->stdin_fd >= 0) {
+                if (dup2(params->stdin_fd, STDIN_FILENO) < 0)
+                        return -errno;
+
+                /* Try to make this the controlling tty, if it is a tty, and reset it */
+                (void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE);
+                (void) reset_terminal_fd(STDIN_FILENO, true);
+
+                return STDIN_FILENO;
+        }
+
+        i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin);
 
         switch (i) {
 
@@ -399,16 +417,40 @@ static int setup_input(const ExecContext *context, int socket_fd, bool apply_tty
         }
 }
 
-static int setup_output(Unit *unit, const ExecContext *context, int fileno, int socket_fd, const char *ident, bool apply_tty_stdin, uid_t uid, gid_t gid) {
+static int setup_output(
+                Unit *unit,
+                const ExecContext *context,
+                const ExecParameters *params,
+                int fileno,
+                int socket_fd,
+                const char *ident,
+                uid_t uid, gid_t gid) {
+
         ExecOutput o;
         ExecInput i;
         int r;
 
         assert(unit);
         assert(context);
+        assert(params);
         assert(ident);
 
-        i = fixup_input(context->std_input, socket_fd, apply_tty_stdin);
+        if (fileno == STDOUT_FILENO && params->stdout_fd >= 0) {
+
+                if (dup2(params->stdout_fd, STDOUT_FILENO) < 0)
+                        return -errno;
+
+                return STDOUT_FILENO;
+        }
+
+        if (fileno == STDERR_FILENO && params->stderr_fd >= 0) {
+                if (dup2(params->stderr_fd, STDERR_FILENO) < 0)
+                        return -errno;
+
+                return STDERR_FILENO;
+        }
+
+        i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin);
         o = fixup_output(context->std_output, socket_fd);
 
         if (fileno == STDERR_FILENO) {
@@ -501,9 +543,9 @@ static int chown_terminal(int fd, uid_t uid) {
         return 0;
 }
 
-static int setup_confirm_stdio(int *_saved_stdin,
-                               int *_saved_stdout) {
-        int fd = -1, saved_stdin, saved_stdout = -1, r;
+static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) {
+        _cleanup_close_ int fd = -1, saved_stdin = -1, saved_stdout = -1;
+        int r;
 
         assert(_saved_stdin);
         assert(_saved_stdout);
@@ -513,10 +555,8 @@ static int setup_confirm_stdio(int *_saved_stdin,
                 return -errno;
 
         saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3);
-        if (saved_stdout < 0) {
-                r = errno;
-                goto fail;
-        }
+        if (saved_stdout < 0)
+                return -errno;
 
         fd = acquire_terminal(
                         "/dev/console",
@@ -524,39 +564,33 @@ static int setup_confirm_stdio(int *_saved_stdin,
                         false,
                         false,
                         DEFAULT_CONFIRM_USEC);
-        if (fd < 0) {
-                r = fd;
-                goto fail;
-        }
+        if (fd < 0)
+                return fd;
 
         r = chown_terminal(fd, getuid());
         if (r < 0)
-                goto fail;
+                return r;
 
-        if (dup2(fd, STDIN_FILENO) < 0) {
-                r = -errno;
-                goto fail;
-        }
+        r = reset_terminal_fd(fd, true);
+        if (r < 0)
+                return r;
 
-        if (dup2(fd, STDOUT_FILENO) < 0) {
-                r = -errno;
-                goto fail;
-        }
+        if (dup2(fd, STDIN_FILENO) < 0)
+                return -errno;
+
+        if (dup2(fd, STDOUT_FILENO) < 0)
+                return -errno;
 
         if (fd >= 2)
                 safe_close(fd);
+        fd = -1;
 
         *_saved_stdin = saved_stdin;
         *_saved_stdout = saved_stdout;
 
-        return 0;
-
-fail:
-        safe_close(saved_stdout);
-        safe_close(saved_stdin);
-        safe_close(fd);
+        saved_stdin = saved_stdout = -1;
 
-        return r;
+        return 0;
 }
 
 _printf_(1, 2) static int write_confirm_message(const char *format, ...) {
@@ -576,9 +610,7 @@ _printf_(1, 2) static int write_confirm_message(const char *format, ...) {
         return 0;
 }
 
-static int restore_confirm_stdio(int *saved_stdin,
-                                 int *saved_stdout) {
-
+static int restore_confirm_stdio(int *saved_stdin, int *saved_stdout) {
         int r = 0;
 
         assert(saved_stdin);
@@ -594,8 +626,8 @@ static int restore_confirm_stdio(int *saved_stdin,
                 if (dup2(*saved_stdout, STDOUT_FILENO) < 0)
                         r = -errno;
 
-        safe_close(*saved_stdin);
-        safe_close(*saved_stdout);
+        *saved_stdin = safe_close(*saved_stdin);
+        *saved_stdout = safe_close(*saved_stdout);
 
         return r;
 }
@@ -768,10 +800,11 @@ static int setup_pam(
                 .appdata_ptr = NULL
         };
 
+        _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL;
         pam_handle_t *handle = NULL;
         sigset_t old_ss;
         int pam_code = PAM_SUCCESS;
-        int err;
+        int err = 0;
         char **e = NULL;
         bool close_session = false;
         pid_t pam_pid = 0, parent_pid;
@@ -788,6 +821,10 @@ static int setup_pam(
          * daemon. We do things this way to ensure that the main PID
          * of the daemon is the one we initially fork()ed. */
 
+        err = barrier_create(&barrier);
+        if (err < 0)
+                goto fail;
+
         if (log_get_max_level() < LOG_DEBUG)
                 flags |= PAM_SILENT;
 
@@ -836,6 +873,7 @@ static int setup_pam(
 
                 /* The child's job is to reset the PAM session on
                  * termination */
+                barrier_set_role(&barrier, BARRIER_CHILD);
 
                 /* This string must fit in 10 chars (i.e. the length
                  * of "/sbin/init"), to look pretty in /bin/ps */
@@ -863,6 +901,11 @@ static int setup_pam(
                 if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
                         goto child_finish;
 
+                /* Tell the parent that our setup is done. This is especially
+                 * important regarding dropping privileges. Otherwise, unit
+                 * setup might race against our setresuid(2) call. */
+                barrier_place(&barrier);
+
                 /* Check if our parent process might already have
                  * died? */
                 if (getppid() == parent_pid) {
@@ -898,6 +941,8 @@ static int setup_pam(
                 _exit(r);
         }
 
+        barrier_set_role(&barrier, BARRIER_PARENT);
+
         /* If the child was forked off successfully it will do all the
          * cleanups, so forget about the handle here. */
         handle = NULL;
@@ -909,6 +954,11 @@ static int setup_pam(
          * might have opened it, but we don't want this fd around. */
         closelog();
 
+        /* Synchronously wait for the child to initialize. We don't care for
+         * errors as we cannot recover. However, warn loudly if it happens. */
+        if (!barrier_place_and_sync(&barrier))
+                log_error("PAM initialization failed");
+
         *pam_env = e;
         e = NULL;
 
@@ -919,7 +969,7 @@ fail:
                 log_error("PAM failed: %s", pam_strerror(handle, pam_code));
                 err = -EPERM;  /* PAM errors do not map to errno */
         } else {
-                err = log_error_errno(errno, "PAM failed: %m");
+                err = log_error_errno(err < 0 ? err : errno, "PAM failed: %m");
         }
 
         if (handle) {
@@ -1179,6 +1229,7 @@ static void do_idle_pipe_dance(int idle_pipe[4]) {
 static int build_environment(
                 const ExecContext *c,
                 unsigned n_fds,
+                char ** fd_names,
                 usec_t watchdog_usec,
                 const char *home,
                 const char *username,
@@ -1192,11 +1243,13 @@ static int build_environment(
         assert(c);
         assert(ret);
 
-        our_env = new0(char*, 10);
+        our_env = new0(char*, 11);
         if (!our_env)
                 return -ENOMEM;
 
         if (n_fds > 0) {
+                _cleanup_free_ char *joined = NULL;
+
                 if (asprintf(&x, "LISTEN_PID="PID_FMT, getpid()) < 0)
                         return -ENOMEM;
                 our_env[n_env++] = x;
@@ -1204,6 +1257,15 @@ static int build_environment(
                 if (asprintf(&x, "LISTEN_FDS=%u", n_fds) < 0)
                         return -ENOMEM;
                 our_env[n_env++] = x;
+
+                joined = strv_join(fd_names, ":");
+                if (!joined)
+                        return -ENOMEM;
+
+                x = strjoin("LISTEN_FDNAMES=", joined, NULL);
+                if (!x)
+                        return -ENOMEM;
+                our_env[n_env++] = x;
         }
 
         if (watchdog_usec > 0) {
@@ -1254,7 +1316,7 @@ static int build_environment(
         }
 
         our_env[n_env++] = NULL;
-        assert(n_env <= 10);
+        assert(n_env <= 11);
 
         *ret = our_env;
         our_env = NULL;
@@ -1292,6 +1354,44 @@ static bool exec_needs_mount_namespace(
         return false;
 }
 
+static int close_remaining_fds(
+                const ExecParameters *params,
+                ExecRuntime *runtime,
+                int socket_fd,
+                int *fds, unsigned n_fds) {
+
+        unsigned n_dont_close = 0;
+        int dont_close[n_fds + 7];
+
+        assert(params);
+
+        if (params->stdin_fd >= 0)
+                dont_close[n_dont_close++] = params->stdin_fd;
+        if (params->stdout_fd >= 0)
+                dont_close[n_dont_close++] = params->stdout_fd;
+        if (params->stderr_fd >= 0)
+                dont_close[n_dont_close++] = params->stderr_fd;
+
+        if (socket_fd >= 0)
+                dont_close[n_dont_close++] = socket_fd;
+        if (n_fds > 0) {
+                memcpy(dont_close + n_dont_close, fds, sizeof(int) * n_fds);
+                n_dont_close += n_fds;
+        }
+
+        if (params->bus_endpoint_fd >= 0)
+                dont_close[n_dont_close++] = params->bus_endpoint_fd;
+
+        if (runtime) {
+                if (runtime->netns_storage_socket[0] >= 0)
+                        dont_close[n_dont_close++] = runtime->netns_storage_socket[0];
+                if (runtime->netns_storage_socket[1] >= 0)
+                        dont_close[n_dont_close++] = runtime->netns_storage_socket[1];
+        }
+
+        return close_all_fds(dont_close, n_dont_close);
+}
+
 static int exec_child(
                 Unit *unit,
                 ExecCommand *command,
@@ -1306,9 +1406,7 @@ static int exec_child(
 
         _cleanup_strv_free_ char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL;
         _cleanup_free_ char *mac_selinux_context_net = NULL;
-        const char *username = NULL, *home = NULL, *shell = NULL;
-        unsigned n_dont_close = 0;
-        int dont_close[n_fds + 4];
+        const char *username = NULL, *home = NULL, *shell = NULL, *wd;
         uid_t uid = UID_INVALID;
         gid_t gid = GID_INVALID;
         int i, r;
@@ -1348,22 +1446,7 @@ static int exec_child(
 
         log_forget_fds();
 
-        if (socket_fd >= 0)
-                dont_close[n_dont_close++] = socket_fd;
-        if (n_fds > 0) {
-                memcpy(dont_close + n_dont_close, fds, sizeof(int) * n_fds);
-                n_dont_close += n_fds;
-        }
-        if (params->bus_endpoint_fd >= 0)
-                dont_close[n_dont_close++] = params->bus_endpoint_fd;
-        if (runtime) {
-                if (runtime->netns_storage_socket[0] >= 0)
-                        dont_close[n_dont_close++] = runtime->netns_storage_socket[0];
-                if (runtime->netns_storage_socket[1] >= 0)
-                        dont_close[n_dont_close++] = runtime->netns_storage_socket[1];
-        }
-
-        r = close_all_fds(dont_close, n_dont_close);
+        r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds);
         if (r < 0) {
                 *exit_status = EXIT_FDS;
                 return r;
@@ -1419,21 +1502,21 @@ static int exec_child(
         /* If a socket is connected to STDIN/STDOUT/STDERR, we
          * must sure to drop O_NONBLOCK */
         if (socket_fd >= 0)
-                fd_nonblock(socket_fd, false);
+                (void) fd_nonblock(socket_fd, false);
 
-        r = setup_input(context, socket_fd, params->apply_tty_stdin);
+        r = setup_input(context, params, socket_fd);
         if (r < 0) {
                 *exit_status = EXIT_STDIN;
                 return r;
         }
 
-        r = setup_output(unit, context, STDOUT_FILENO, socket_fd, basename(command->path), params->apply_tty_stdin, uid, gid);
+        r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, basename(command->path), uid, gid);
         if (r < 0) {
                 *exit_status = EXIT_STDOUT;
                 return r;
         }
 
-        r = setup_output(unit, context, STDERR_FILENO, socket_fd, basename(command->path), params->apply_tty_stdin, uid, gid);
+        r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, basename(command->path), uid, gid);
         if (r < 0) {
                 *exit_status = EXIT_STDERR;
                 return r;
@@ -1582,25 +1665,50 @@ static int exec_child(
                 }
         }
 
+        umask(context->umask);
+
         if (params->apply_permissions) {
                 r = enforce_groups(context, username, gid);
                 if (r < 0) {
                         *exit_status = EXIT_GROUP;
                         return r;
                 }
-        }
+#ifdef HAVE_SMACK
+                if (context->smack_process_label) {
+                        r = mac_smack_apply_pid(0, context->smack_process_label);
+                        if (r < 0) {
+                                *exit_status = EXIT_SMACK_PROCESS_LABEL;
+                                return r;
+                        }
+                }
+#ifdef SMACK_DEFAULT_PROCESS_LABEL
+                else {
+                        _cleanup_free_ char *exec_label = NULL;
 
-        umask(context->umask);
+                        r = mac_smack_read(command->path, SMACK_ATTR_EXEC, &exec_label);
+                        if (r < 0 && r != -ENODATA && r != -EOPNOTSUPP) {
+                                *exit_status = EXIT_SMACK_PROCESS_LABEL;
+                                return r;
+                        }
 
+                        r = mac_smack_apply_pid(0, exec_label ? : SMACK_DEFAULT_PROCESS_LABEL);
+                        if (r < 0) {
+                                *exit_status = EXIT_SMACK_PROCESS_LABEL;
+                                return r;
+                        }
+                }
+#endif
+#endif
 #ifdef HAVE_PAM
-        if (params->apply_permissions && context->pam_name && username) {
-                r = setup_pam(context->pam_name, username, uid, context->tty_path, &pam_env, fds, n_fds);
-                if (r < 0) {
-                        *exit_status = EXIT_PAM;
-                        return r;
+                if (context->pam_name && username) {
+                        r = setup_pam(context->pam_name, username, uid, context->tty_path, &pam_env, fds, n_fds);
+                        if (r < 0) {
+                                *exit_status = EXIT_PAM;
+                                return r;
+                        }
                 }
-        }
 #endif
+        }
 
         if (context->private_network && runtime && runtime->netns_storage_socket[0] >= 0) {
                 r = setup_netns(runtime->netns_storage_socket);
@@ -1654,6 +1762,13 @@ static int exec_child(
                 }
         }
 
+        if (context->working_directory_home)
+                wd = home;
+        else if (context->working_directory)
+                wd = context->working_directory;
+        else
+                wd = "/";
+
         if (params->apply_chroot) {
                 if (!needs_mount_namespace && context->root_directory)
                         if (chroot(context->root_directory) < 0) {
@@ -1661,21 +1776,15 @@ static int exec_child(
                                 return -errno;
                         }
 
-                if (chdir(context->working_directory ?: "/") < 0 &&
+                if (chdir(wd) < 0 &&
                     !context->working_directory_missing_ok) {
                         *exit_status = EXIT_CHDIR;
                         return -errno;
                 }
         } else {
-                _cleanup_free_ char *d = NULL;
-
-                if (asprintf(&d, "%s/%s",
-                             context->root_directory ?: "",
-                             context->working_directory ?: "") < 0) {
-                        *exit_status = EXIT_MEMORY;
-                        return -ENOMEM;
-                }
+                const char *d;
 
+                d = strjoina(strempty(context->root_directory), "/", strempty(wd));
                 if (chdir(d) < 0 &&
                     !context->working_directory_missing_ok) {
                         *exit_status = EXIT_CHDIR;
@@ -1729,33 +1838,6 @@ static int exec_child(
                         }
                 }
 
-#ifdef HAVE_SMACK
-                if (context->smack_process_label) {
-                        r = mac_smack_apply_pid(0, context->smack_process_label);
-                        if (r < 0) {
-                                *exit_status = EXIT_SMACK_PROCESS_LABEL;
-                                return r;
-                        }
-                }
-#ifdef SMACK_DEFAULT_PROCESS_LABEL
-                else {
-                        _cleanup_free_ char *exec_label = NULL;
-
-                        r = mac_smack_read(command->path, SMACK_ATTR_EXEC, &exec_label);
-                        if (r < 0 && r != -ENODATA && r != -EOPNOTSUPP) {
-                                *exit_status = EXIT_SMACK_PROCESS_LABEL;
-                                return r;
-                        }
-
-                        r = mac_smack_apply_pid(0, exec_label ? : SMACK_DEFAULT_PROCESS_LABEL);
-                        if (r < 0) {
-                                *exit_status = EXIT_SMACK_PROCESS_LABEL;
-                                return r;
-                        }
-                }
-#endif
-#endif
-
                 if (context->user) {
                         r = enforce_user(context, uid);
                         if (r < 0) {
@@ -1832,7 +1914,7 @@ static int exec_child(
 #endif
         }
 
-        r = build_environment(context, n_fds, params->watchdog_usec, home, username, shell, &our_env);
+        r = build_environment(context, n_fds, params->fd_names, params->watchdog_usec, home, username, shell, &our_env);
         if (r < 0) {
                 *exit_status = EXIT_MEMORY;
                 return r;
@@ -2232,7 +2314,7 @@ static void strv_fprintf(FILE *f, char **l) {
 }
 
 void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
-        char **e;
+        char **e, **d;
         unsigned i;
 
         assert(c);
@@ -2268,6 +2350,11 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
         STRV_FOREACH(e, c->environment_files)
                 fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e);
 
+        fprintf(f, "%sRuntimeDirectoryMode: %04o\n", prefix, c->runtime_directory_mode);
+
+        STRV_FOREACH(d, c->runtime_directory)
+                fprintf(f, "%sRuntimeDirectory: %s\n", prefix, *d);
+
         if (c->nice_set)
                 fprintf(f,
                         "%sNice: %i\n",
@@ -2694,7 +2781,7 @@ int exec_command_append(ExecCommand *c, const char *path, ...) {
         if (!l)
                 return -ENOMEM;
 
-        r = strv_extend_strv(&c->argv, l);
+        r = strv_extend_strv(&c->argv, l, false);
         if (r < 0)
                 return r;