/*** This file is part of systemd. Copyright 2010 Lennart Poettering systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PAM #include #endif #ifdef HAVE_SELINUX #include #endif #ifdef HAVE_SECCOMP #include #endif #ifdef HAVE_APPARMOR #include #endif #include "sd-messages.h" #include "af-list.h" #include "alloc-util.h" #ifdef HAVE_APPARMOR #include "apparmor-util.h" #endif #include "async.h" #include "barrier.h" #include "cap-list.h" #include "capability-util.h" #include "def.h" #include "env-util.h" #include "errno-list.h" #include "execute.h" #include "exit-status.h" #include "fd-util.h" #include "fileio.h" #include "format-util.h" #include "fs-util.h" #include "glob-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 "parse-util.h" #include "path-util.h" #include "process-util.h" #include "rlimit-util.h" #include "rm-rf.h" #ifdef HAVE_SECCOMP #include "seccomp-util.h" #endif #include "securebits.h" #include "selinux-util.h" #include "signal-util.h" #include "smack-util.h" #include "special.h" #include "string-table.h" #include "string-util.h" #include "strv.h" #include "syslog-util.h" #include "terminal-util.h" #include "unit.h" #include "user-util.h" #include "util.h" #include "utmp-wtmp.h" #define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC) #define IDLE_TIMEOUT2_USEC (1*USEC_PER_SEC) /* This assumes there is a 'tty' group */ #define TTY_MODE 0620 #define SNDBUF_SIZE (8*1024*1024) static int shift_fds(int fds[], unsigned n_fds) { int start, restart_from; if (n_fds <= 0) return 0; /* Modifies the fds array! (sorts it) */ assert(fds); start = 0; for (;;) { int i; restart_from = -1; for (i = start; i < (int) n_fds; i++) { int nfd; /* Already at right index? */ if (fds[i] == i+3) continue; nfd = fcntl(fds[i], F_DUPFD, i + 3); if (nfd < 0) return -errno; safe_close(fds[i]); fds[i] = nfd; /* Hmm, the fd we wanted isn't free? Then * let's remember that and try again from here */ if (nfd != i+3 && restart_from < 0) restart_from = i; } if (restart_from < 0) break; start = restart_from; } return 0; } static int flags_fds(const int fds[], unsigned n_fds, bool nonblock) { unsigned i; int r; if (n_fds <= 0) return 0; assert(fds); /* Drops/Sets O_NONBLOCK and FD_CLOEXEC from the file flags */ for (i = 0; i < n_fds; i++) { r = fd_nonblock(fds[i], nonblock); if (r < 0) return r; /* We unconditionally drop FD_CLOEXEC from the fds, * since after all we want to pass these fds to our * children */ r = fd_cloexec(fds[i], false); if (r < 0) return r; } return 0; } static const char *exec_context_tty_path(const ExecContext *context) { assert(context); if (context->stdio_as_fds) return NULL; if (context->tty_path) return context->tty_path; return "/dev/console"; } static void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) { const char *path; assert(context); path = exec_context_tty_path(context); if (context->tty_vhangup) { if (p && p->stdin_fd >= 0) (void) terminal_vhangup_fd(p->stdin_fd); else if (path) (void) terminal_vhangup(path); } if (context->tty_reset) { if (p && p->stdin_fd >= 0) (void) reset_terminal_fd(p->stdin_fd, true); else if (path) (void) reset_terminal(path); } if (context->tty_vt_disallocate && path) (void) vt_disallocate(path); } static bool is_terminal_input(ExecInput i) { return IN_SET(i, EXEC_INPUT_TTY, EXEC_INPUT_TTY_FORCE, EXEC_INPUT_TTY_FAIL); } static bool is_terminal_output(ExecOutput o) { return IN_SET(o, EXEC_OUTPUT_TTY, EXEC_OUTPUT_SYSLOG_AND_CONSOLE, EXEC_OUTPUT_KMSG_AND_CONSOLE, EXEC_OUTPUT_JOURNAL_AND_CONSOLE); } static bool exec_context_needs_term(const ExecContext *c) { assert(c); /* Return true if the execution context suggests we should set $TERM to something useful. */ if (is_terminal_input(c->std_input)) return true; if (is_terminal_output(c->std_output)) return true; if (is_terminal_output(c->std_error)) return true; return !!c->tty_path; } static int open_null_as(int flags, int nfd) { int fd, r; assert(nfd >= 0); fd = open("/dev/null", flags|O_NOCTTY); if (fd < 0) return -errno; if (fd != nfd) { r = dup2(fd, nfd) < 0 ? -errno : nfd; safe_close(fd); } else r = nfd; return 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_INVALID; gid_t oldgid = GID_INVALID; int r; if (gid != GID_INVALID) { oldgid = getgid(); r = setegid(gid); if (r < 0) return -errno; } if (uid != UID_INVALID) { olduid = getuid(); r = seteuid(uid); if (r < 0) { r = -errno; goto restore_gid; } } r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); 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_INVALID) (void) seteuid(olduid); restore_gid: if (gid != GID_INVALID) (void) setegid(oldgid); return r; } static int connect_logger_as( Unit *unit, const ExecContext *context, ExecOutput output, const char *ident, int nfd, uid_t uid, gid_t gid) { int fd, r; assert(context); assert(output < _EXEC_OUTPUT_MAX); assert(ident); assert(nfd >= 0); fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -errno; r = connect_journal_socket(fd, uid, gid); if (r < 0) return r; if (shutdown(fd, SHUT_RD) < 0) { safe_close(fd); return -errno; } (void) fd_inc_sndbuf(fd, SNDBUF_SIZE); dprintf(fd, "%s\n" "%s\n" "%i\n" "%i\n" "%i\n" "%i\n" "%i\n", context->syslog_identifier ? context->syslog_identifier : ident, unit->id, context->syslog_priority, !!context->syslog_level_prefix, output == EXEC_OUTPUT_SYSLOG || output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE, output == EXEC_OUTPUT_KMSG || output == EXEC_OUTPUT_KMSG_AND_CONSOLE, is_terminal_output(output)); if (fd == nfd) return nfd; r = dup2(fd, nfd) < 0 ? -errno : nfd; safe_close(fd); return r; } static int open_terminal_as(const char *path, mode_t mode, int nfd) { int fd, r; assert(path); assert(nfd >= 0); fd = open_terminal(path, mode | O_NOCTTY); if (fd < 0) return fd; if (fd != nfd) { r = dup2(fd, nfd) < 0 ? -errno : nfd; safe_close(fd); } else r = nfd; return r; } static int fixup_input(ExecInput std_input, int socket_fd, bool apply_tty_stdin) { if (is_terminal_input(std_input) && !apply_tty_stdin) return EXEC_INPUT_NULL; if (std_input == EXEC_INPUT_SOCKET && socket_fd < 0) return EXEC_INPUT_NULL; return std_input; } static int fixup_output(ExecOutput std_output, int socket_fd) { if (std_output == EXEC_OUTPUT_SOCKET && socket_fd < 0) return EXEC_OUTPUT_INHERIT; return std_output; } static int setup_input( const ExecContext *context, const ExecParameters *params, int socket_fd, int named_iofds[3]) { ExecInput i; assert(context); assert(params); 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->flags & EXEC_APPLY_TTY_STDIN); switch (i) { case EXEC_INPUT_NULL: return open_null_as(O_RDONLY, STDIN_FILENO); case EXEC_INPUT_TTY: case EXEC_INPUT_TTY_FORCE: case EXEC_INPUT_TTY_FAIL: { int fd, r; fd = acquire_terminal(exec_context_tty_path(context), i == EXEC_INPUT_TTY_FAIL, i == EXEC_INPUT_TTY_FORCE, false, USEC_INFINITY); if (fd < 0) return fd; if (fd != STDIN_FILENO) { r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; safe_close(fd); } else r = STDIN_FILENO; return r; } case EXEC_INPUT_SOCKET: return dup2(socket_fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; case EXEC_INPUT_NAMED_FD: (void) fd_nonblock(named_iofds[STDIN_FILENO], false); return dup2(named_iofds[STDIN_FILENO], STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; default: assert_not_reached("Unknown input type"); } } static int setup_output( Unit *unit, const ExecContext *context, const ExecParameters *params, int fileno, int socket_fd, int named_iofds[3], const char *ident, uid_t uid, gid_t gid, dev_t *journal_stream_dev, ino_t *journal_stream_ino) { ExecOutput o; ExecInput i; int r; assert(unit); assert(context); assert(params); assert(ident); assert(journal_stream_dev); assert(journal_stream_ino); 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->flags & EXEC_APPLY_TTY_STDIN); o = fixup_output(context->std_output, socket_fd); if (fileno == STDERR_FILENO) { ExecOutput e; e = fixup_output(context->std_error, socket_fd); /* This expects the input and output are already set up */ /* Don't change the stderr file descriptor if we inherit all * the way and are not on a tty */ if (e == EXEC_OUTPUT_INHERIT && o == EXEC_OUTPUT_INHERIT && i == EXEC_INPUT_NULL && !is_terminal_input(context->std_input) && getppid () != 1) return fileno; /* Duplicate from stdout if possible */ if ((e == o && e != EXEC_OUTPUT_NAMED_FD) || e == EXEC_OUTPUT_INHERIT) return dup2(STDOUT_FILENO, fileno) < 0 ? -errno : fileno; o = e; } else if (o == EXEC_OUTPUT_INHERIT) { /* If input got downgraded, inherit the original value */ if (i == EXEC_INPUT_NULL && is_terminal_input(context->std_input)) return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); /* If the input is connected to anything that's not a /dev/null, inherit that... */ if (i != EXEC_INPUT_NULL) return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */ if (getppid() != 1) return fileno; /* We need to open /dev/null here anew, to get the right access mode. */ return open_null_as(O_WRONLY, fileno); } switch (o) { case EXEC_OUTPUT_NULL: return open_null_as(O_WRONLY, fileno); case EXEC_OUTPUT_TTY: if (is_terminal_input(i)) return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; /* We don't reset the terminal if this is just about output */ return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); case EXEC_OUTPUT_SYSLOG: case EXEC_OUTPUT_SYSLOG_AND_CONSOLE: case EXEC_OUTPUT_KMSG: case EXEC_OUTPUT_KMSG_AND_CONSOLE: case EXEC_OUTPUT_JOURNAL: case EXEC_OUTPUT_JOURNAL_AND_CONSOLE: r = connect_logger_as(unit, context, o, ident, fileno, uid, gid); if (r < 0) { log_unit_error_errno(unit, r, "Failed to connect %s to the journal socket, ignoring: %m", fileno == STDOUT_FILENO ? "stdout" : "stderr"); r = open_null_as(O_WRONLY, fileno); } else { struct stat st; /* If we connected this fd to the journal via a stream, patch the device/inode into the passed * parameters, but only then. This is useful so that we can set $JOURNAL_STREAM that permits * services to detect whether they are connected to the journal or not. */ if (fstat(fileno, &st) >= 0) { *journal_stream_dev = st.st_dev; *journal_stream_ino = st.st_ino; } } return r; case EXEC_OUTPUT_SOCKET: assert(socket_fd >= 0); return dup2(socket_fd, fileno) < 0 ? -errno : fileno; case EXEC_OUTPUT_NAMED_FD: (void) fd_nonblock(named_iofds[fileno], false); return dup2(named_iofds[fileno], fileno) < 0 ? -errno : fileno; default: assert_not_reached("Unknown error type"); } } static int chown_terminal(int fd, uid_t uid) { struct stat st; assert(fd >= 0); /* Before we chown/chmod the TTY, let's ensure this is actually a tty */ if (isatty(fd) < 1) return 0; /* This might fail. What matters are the results. */ (void) fchown(fd, uid, -1); (void) fchmod(fd, TTY_MODE); if (fstat(fd, &st) < 0) return -errno; if (st.st_uid != uid || (st.st_mode & 0777) != TTY_MODE) return -EPERM; return 0; } static int setup_confirm_stdio(const char *vc, 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); saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3); if (saved_stdin < 0) return -errno; saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3); if (saved_stdout < 0) return -errno; fd = acquire_terminal(vc, false, false, false, DEFAULT_CONFIRM_USEC); if (fd < 0) return fd; r = chown_terminal(fd, getuid()); if (r < 0) return r; r = reset_terminal_fd(fd, true); if (r < 0) return r; 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; saved_stdin = saved_stdout = -1; return 0; } static void write_confirm_error_fd(int err, int fd) { assert(err < 0); if (err == -ETIMEDOUT) dprintf(fd, "Confirmation question timed out, assuming positive response.\n"); else { errno = -err; dprintf(fd, "Couldn't ask confirmation: %m, assuming positive response.\n"); } } static void write_confirm_error(int err, const char *vc) { _cleanup_close_ int fd = -1; assert(vc); fd = open_terminal(vc, O_WRONLY|O_NOCTTY|O_CLOEXEC); if (fd < 0) return; write_confirm_error_fd(err, fd); } static int restore_confirm_stdio(int *saved_stdin, int *saved_stdout) { int r = 0; assert(saved_stdin); assert(saved_stdout); release_terminal(); if (*saved_stdin >= 0) if (dup2(*saved_stdin, STDIN_FILENO) < 0) r = -errno; if (*saved_stdout >= 0) if (dup2(*saved_stdout, STDOUT_FILENO) < 0) r = -errno; *saved_stdin = safe_close(*saved_stdin); *saved_stdout = safe_close(*saved_stdout); return r; } enum { CONFIRM_PRETEND_FAILURE = -1, CONFIRM_PRETEND_SUCCESS = 0, CONFIRM_EXECUTE = 1, }; static int ask_for_confirmation(const char *vc, const char *cmdline) { int saved_stdout = -1, saved_stdin = -1, r; _cleanup_free_ char *e = NULL; char c; /* For any internal errors, assume a positive response. */ r = setup_confirm_stdio(vc, &saved_stdin, &saved_stdout); if (r < 0) { write_confirm_error(r, vc); return CONFIRM_EXECUTE; } e = ellipsize(cmdline, 60, 100); if (!e) { log_oom(); r = CONFIRM_EXECUTE; goto restore_stdio; } for (;;) { r = ask_char(&c, "yfsh", "Execute %s? [y, f, s – h for help] ", e); if (r < 0) { write_confirm_error_fd(r, STDOUT_FILENO); r = CONFIRM_EXECUTE; goto restore_stdio; } switch (c) { case 'f': printf("Failing execution.\n"); r = CONFIRM_PRETEND_FAILURE; break; case 'h': printf(" f - fail, don't execute the command and pretend it failed\n" " h - help\n" " s - skip, don't execute the command and pretend it succeeded\n" " y - yes, execute the command\n"); continue; case 's': printf("Skipping execution.\n"); r = CONFIRM_PRETEND_SUCCESS; break; case 'y': r = CONFIRM_EXECUTE; break; default: assert_not_reached("Unhandled choice"); } break; } restore_stdio: restore_confirm_stdio(&saved_stdin, &saved_stdout); return r; } static int get_fixed_user(const ExecContext *c, const char **user, uid_t *uid, gid_t *gid, const char **home, const char **shell) { int r; const char *name; assert(c); if (!c->user) return 0; /* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway * (i.e. are "/" or "/bin/nologin"). */ name = c->user; r = get_user_creds_clean(&name, uid, gid, home, shell); if (r < 0) return r; *user = name; return 0; } static int get_fixed_group(const ExecContext *c, const char **group, gid_t *gid) { int r; const char *name; assert(c); if (!c->group) return 0; name = c->group; r = get_group_creds(&name, gid); if (r < 0) return r; *group = name; return 0; } static int get_supplementary_groups(const ExecContext *c, const char *user, const char *group, gid_t gid, gid_t **supplementary_gids, int *ngids) { char **i; int r, k = 0; int ngroups_max; bool keep_groups = false; gid_t *groups = NULL; _cleanup_free_ gid_t *l_gids = NULL; assert(c); /* * If user is given, then lookup GID and supplementary groups list. * We avoid NSS lookups for gid=0. Also we have to initialize groups * here and as early as possible so we keep the list of supplementary * groups of the caller. */ if (user && gid_is_valid(gid) && gid != 0) { /* First step, initialize groups from /etc/groups */ if (initgroups(user, gid) < 0) return -errno; keep_groups = true; } if (!c->supplementary_groups) return 0; /* * If SupplementaryGroups= was passed then NGROUPS_MAX has to * be positive, otherwise fail. */ errno = 0; ngroups_max = (int) sysconf(_SC_NGROUPS_MAX); if (ngroups_max <= 0) { if (errno > 0) return -errno; else return -EOPNOTSUPP; /* For all other values */ } l_gids = new(gid_t, ngroups_max); if (!l_gids) return -ENOMEM; if (keep_groups) { /* * Lookup the list of groups that the user belongs to, we * avoid NSS lookups here too for gid=0. */ k = ngroups_max; if (getgrouplist(user, gid, l_gids, &k) < 0) return -EINVAL; } else k = 0; STRV_FOREACH(i, c->supplementary_groups) { const char *g; if (k >= ngroups_max) return -E2BIG; g = *i; r = get_group_creds(&g, l_gids+k); if (r < 0) return r; k++; } /* * Sets ngids to zero to drop all supplementary groups, happens * when we are under root and SupplementaryGroups= is empty. */ if (k == 0) { *ngids = 0; return 0; } /* Otherwise get the final list of supplementary groups */ groups = memdup(l_gids, sizeof(gid_t) * k); if (!groups) return -ENOMEM; *supplementary_gids = groups; *ngids = k; groups = NULL; return 0; } static int enforce_groups(const ExecContext *context, gid_t gid, gid_t *supplementary_gids, int ngids) { int r; assert(context); /* Handle SupplementaryGroups= even if it is empty */ if (context->supplementary_groups) { r = maybe_setgroups(ngids, supplementary_gids); if (r < 0) return r; } if (gid_is_valid(gid)) { /* Then set our gids */ if (setresgid(gid, gid, gid) < 0) return -errno; } return 0; } static int enforce_user(const ExecContext *context, uid_t uid) { assert(context); if (!uid_is_valid(uid)) return 0; /* Sets (but doesn't look up) the uid and make sure we keep the * capabilities while doing so. */ if (context->capability_ambient_set != 0) { /* First step: If we need to keep capabilities but * drop privileges we need to make sure we keep our * caps, while we drop privileges. */ if (uid != 0) { int sb = context->secure_bits | 1<= 0); parent_pid = getpid(); pam_pid = fork(); if (pam_pid < 0) { r = -errno; goto fail; } if (pam_pid == 0) { int sig, ret = EXIT_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 */ rename_process("(sd-pam)"); /* Make sure we don't keep open the passed fds in this child. We assume that otherwise only those fds are open here that have been opened by PAM. */ close_many(fds, n_fds); /* Drop privileges - we don't need any to pam_close_session * and this will make PR_SET_PDEATHSIG work in most cases. * If this fails, ignore the error - but expect sd-pam threads * to fail to exit normally */ r = maybe_setgroups(0, NULL); if (r < 0) log_warning_errno(r, "Failed to setgroups() in sd-pam: %m"); if (setresgid(gid, gid, gid) < 0) log_warning_errno(errno, "Failed to setresgid() in sd-pam: %m"); if (setresuid(uid, uid, uid) < 0) log_warning_errno(errno, "Failed to setresuid() in sd-pam: %m"); (void) ignore_signals(SIGPIPE, -1); /* Wait until our parent died. This will only work if * the above setresuid() succeeds, otherwise the kernel * will not allow unprivileged parents kill their privileged * children this way. We rely on the control groups kill logic * to do the rest for us. */ 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) { sigset_t ss; assert_se(sigemptyset(&ss) >= 0); assert_se(sigaddset(&ss, SIGTERM) >= 0); for (;;) { if (sigwait(&ss, &sig) < 0) { if (errno == EINTR) continue; goto child_finish; } assert(sig == SIGTERM); break; } } /* If our parent died we'll end the session */ if (getppid() != parent_pid) { pam_code = pam_close_session(handle, flags); if (pam_code != PAM_SUCCESS) goto child_finish; } ret = 0; child_finish: pam_end(handle, pam_code | flags); _exit(ret); } 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; /* Unblock SIGTERM again in the parent */ assert_se(sigprocmask(SIG_SETMASK, &old_ss, NULL) >= 0); /* We close the log explicitly here, since the PAM modules * 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"); strv_free(*env); *env = e; return 0; fail: if (pam_code != PAM_SUCCESS) { log_error("PAM failed: %s", pam_strerror(handle, pam_code)); r = -EPERM; /* PAM errors do not map to errno */ } else log_error_errno(r, "PAM failed: %m"); if (handle) { if (close_session) pam_code = pam_close_session(handle, flags); pam_end(handle, pam_code | flags); } strv_free(e); closelog(); return r; #else return 0; #endif } static void rename_process_from_path(const char *path) { char process_name[11]; const char *p; size_t l; /* This resulting string must fit in 10 chars (i.e. the length * of "/sbin/init") to look pretty in /bin/ps */ p = basename(path); if (isempty(p)) { rename_process("(...)"); return; } l = strlen(p); if (l > 8) { /* The end of the process name is usually more * interesting, since the first bit might just be * "systemd-" */ p = p + l - 8; l = 8; } process_name[0] = '('; memcpy(process_name+1, p, l); process_name[1+l] = ')'; process_name[1+l+1] = 0; rename_process(process_name); } #ifdef HAVE_SECCOMP static bool skip_seccomp_unavailable(const Unit* u, const char* msg) { if (is_seccomp_available()) return false; log_open(); log_unit_debug(u, "SECCOMP features not detected in the kernel, skipping %s", msg); log_close(); return true; } static int apply_seccomp(const Unit* u, const ExecContext *c) { uint32_t negative_action, action; scmp_filter_ctx seccomp; Iterator i; void *id; int r; assert(c); if (skip_seccomp_unavailable(u, "syscall filtering")) return 0; negative_action = c->syscall_errno == 0 ? SCMP_ACT_KILL : SCMP_ACT_ERRNO(c->syscall_errno); seccomp = seccomp_init(c->syscall_whitelist ? negative_action : SCMP_ACT_ALLOW); if (!seccomp) return -ENOMEM; if (c->syscall_archs) { SET_FOREACH(id, c->syscall_archs, i) { r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); if (r == -EEXIST) continue; if (r < 0) goto finish; } } else { r = seccomp_add_secondary_archs(seccomp); if (r < 0) goto finish; } action = c->syscall_whitelist ? SCMP_ACT_ALLOW : negative_action; SET_FOREACH(id, c->syscall_filter, i) { r = seccomp_rule_add(seccomp, action, PTR_TO_INT(id) - 1, 0); if (r < 0) goto finish; } r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); if (r < 0) goto finish; r = seccomp_load(seccomp); finish: seccomp_release(seccomp); return r; } static int apply_address_families(const Unit* u, const ExecContext *c) { scmp_filter_ctx seccomp; Iterator i; int r; assert(c); if (skip_seccomp_unavailable(u, "RestrictAddressFamilies=")) return 0; r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); if (r < 0) return r; if (c->address_families_whitelist) { int af, first = 0, last = 0; void *afp; /* If this is a whitelist, we first block the address * families that are out of range and then everything * that is not in the set. First, we find the lowest * and highest address family in the set. */ SET_FOREACH(afp, c->address_families, i) { af = PTR_TO_INT(afp); if (af <= 0 || af >= af_max()) continue; if (first == 0 || af < first) first = af; if (last == 0 || af > last) last = af; } assert((first == 0) == (last == 0)); if (first == 0) { /* No entries in the valid range, block everything */ r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPROTONOSUPPORT), SCMP_SYS(socket), 0); if (r < 0) goto finish; } else { /* Block everything below the first entry */ r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPROTONOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_LT, first)); if (r < 0) goto finish; /* Block everything above the last entry */ r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPROTONOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_GT, last)); if (r < 0) goto finish; /* Block everything between the first and last * entry */ for (af = 1; af < af_max(); af++) { if (set_contains(c->address_families, INT_TO_PTR(af))) continue; r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPROTONOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_EQ, af)); if (r < 0) goto finish; } } } else { void *af; /* If this is a blacklist, then generate one rule for * each address family that are then combined in OR * checks. */ SET_FOREACH(af, c->address_families, i) { r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPROTONOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_EQ, PTR_TO_INT(af))); if (r < 0) goto finish; } } r = seccomp_load(seccomp); finish: seccomp_release(seccomp); return r; } static int apply_memory_deny_write_execute(const Unit* u, const ExecContext *c) { scmp_filter_ctx seccomp; int r; assert(c); if (skip_seccomp_unavailable(u, "MemoryDenyWriteExecute=")) return 0; r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); if (r < 0) return r; r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(mmap), 1, SCMP_A2(SCMP_CMP_MASKED_EQ, PROT_EXEC|PROT_WRITE, PROT_EXEC|PROT_WRITE)); if (r < 0) goto finish; r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(mprotect), 1, SCMP_A2(SCMP_CMP_MASKED_EQ, PROT_EXEC, PROT_EXEC)); if (r < 0) goto finish; r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(shmat), 1, SCMP_A2(SCMP_CMP_MASKED_EQ, SHM_EXEC, SHM_EXEC)); if (r < 0) goto finish; r = seccomp_load(seccomp); finish: seccomp_release(seccomp); return r; } static int apply_restrict_realtime(const Unit* u, const ExecContext *c) { static const int permitted_policies[] = { SCHED_OTHER, SCHED_BATCH, SCHED_IDLE, }; scmp_filter_ctx seccomp; unsigned i; int r, p, max_policy = 0; assert(c); if (skip_seccomp_unavailable(u, "RestrictRealtime=")) return 0; r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); if (r < 0) return r; /* Determine the highest policy constant we want to allow */ for (i = 0; i < ELEMENTSOF(permitted_policies); i++) if (permitted_policies[i] > max_policy) max_policy = permitted_policies[i]; /* Go through all policies with lower values than that, and block them -- unless they appear in the * whitelist. */ for (p = 0; p < max_policy; p++) { bool good = false; /* Check if this is in the whitelist. */ for (i = 0; i < ELEMENTSOF(permitted_policies); i++) if (permitted_policies[i] == p) { good = true; break; } if (good) continue; /* Deny this policy */ r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(sched_setscheduler), 1, SCMP_A1(SCMP_CMP_EQ, p)); if (r < 0) goto finish; } /* Blacklist all other policies, i.e. the ones with higher values. Note that all comparisons are unsigned here, * hence no need no check for < 0 values. */ r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(sched_setscheduler), 1, SCMP_A1(SCMP_CMP_GT, max_policy)); if (r < 0) goto finish; r = seccomp_load(seccomp); finish: seccomp_release(seccomp); return r; } static int apply_protect_sysctl(const Unit *u, const ExecContext *c) { scmp_filter_ctx seccomp; int r; assert(c); /* Turn off the legacy sysctl() system call. Many distributions turn this off while building the kernel, but * let's protect even those systems where this is left on in the kernel. */ if (skip_seccomp_unavailable(u, "ProtectKernelTunables=")) return 0; r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); if (r < 0) return r; r = seccomp_rule_add( seccomp, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(_sysctl), 0); if (r < 0) goto finish; r = seccomp_load(seccomp); finish: seccomp_release(seccomp); return r; } static int apply_protect_kernel_modules(const Unit *u, const ExecContext *c) { assert(c); /* Turn off module syscalls on ProtectKernelModules=yes */ if (skip_seccomp_unavailable(u, "ProtectKernelModules=")) return 0; return seccomp_load_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_MODULE, SCMP_ACT_ERRNO(EPERM)); } static int apply_private_devices(const Unit *u, const ExecContext *c) { assert(c); /* If PrivateDevices= is set, also turn off iopl and all @raw-io syscalls. */ if (skip_seccomp_unavailable(u, "PrivateDevices=")) return 0; return seccomp_load_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_RAW_IO, SCMP_ACT_ERRNO(EPERM)); } static int apply_restrict_namespaces(Unit *u, const ExecContext *c) { assert(c); if (!exec_context_restrict_namespaces_set(c)) return 0; if (skip_seccomp_unavailable(u, "RestrictNamespaces=")) return 0; return seccomp_restrict_namespaces(c->restrict_namespaces); } #endif static void do_idle_pipe_dance(int idle_pipe[4]) { assert(idle_pipe); idle_pipe[1] = safe_close(idle_pipe[1]); idle_pipe[2] = safe_close(idle_pipe[2]); if (idle_pipe[0] >= 0) { int r; r = fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT_USEC); if (idle_pipe[3] >= 0 && r == 0 /* timeout */) { ssize_t n; /* Signal systemd that we are bored and want to continue. */ n = write(idle_pipe[3], "x", 1); if (n > 0) /* Wait for systemd to react to the signal above. */ fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT2_USEC); } idle_pipe[0] = safe_close(idle_pipe[0]); } idle_pipe[3] = safe_close(idle_pipe[3]); } static int build_environment( Unit *u, const ExecContext *c, const ExecParameters *p, unsigned n_fds, const char *home, const char *username, const char *shell, dev_t journal_stream_dev, ino_t journal_stream_ino, char ***ret) { _cleanup_strv_free_ char **our_env = NULL; unsigned n_env = 0; char *x; assert(u); assert(c); assert(ret); our_env = new0(char*, 14); 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; if (asprintf(&x, "LISTEN_FDS=%u", n_fds) < 0) return -ENOMEM; our_env[n_env++] = x; joined = strv_join(p->fd_names, ":"); if (!joined) return -ENOMEM; x = strjoin("LISTEN_FDNAMES=", joined); if (!x) return -ENOMEM; our_env[n_env++] = x; } if ((p->flags & EXEC_SET_WATCHDOG) && p->watchdog_usec > 0) { if (asprintf(&x, "WATCHDOG_PID="PID_FMT, getpid()) < 0) return -ENOMEM; our_env[n_env++] = x; if (asprintf(&x, "WATCHDOG_USEC="USEC_FMT, p->watchdog_usec) < 0) return -ENOMEM; our_env[n_env++] = x; } /* If this is D-Bus, tell the nss-systemd module, since it relies on being able to use D-Bus look up dynamic * users via PID 1, possibly dead-locking the dbus daemon. This way it will not use D-Bus to resolve names, but * check the database directly. */ if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) { x = strdup("SYSTEMD_NSS_BYPASS_BUS=1"); if (!x) return -ENOMEM; our_env[n_env++] = x; } if (home) { x = strappend("HOME=", home); if (!x) return -ENOMEM; our_env[n_env++] = x; } if (username) { x = strappend("LOGNAME=", username); if (!x) return -ENOMEM; our_env[n_env++] = x; x = strappend("USER=", username); if (!x) return -ENOMEM; our_env[n_env++] = x; } if (shell) { x = strappend("SHELL=", shell); if (!x) return -ENOMEM; our_env[n_env++] = x; } if (!sd_id128_is_null(u->invocation_id)) { if (asprintf(&x, "INVOCATION_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(u->invocation_id)) < 0) return -ENOMEM; our_env[n_env++] = x; } if (exec_context_needs_term(c)) { const char *tty_path, *term = NULL; tty_path = exec_context_tty_path(c); /* If we are forked off PID 1 and we are supposed to operate on /dev/console, then let's try to inherit * the $TERM set for PID 1. This is useful for containers so that the $TERM the container manager * passes to PID 1 ends up all the way in the console login shown. */ if (path_equal(tty_path, "/dev/console") && getppid() == 1) term = getenv("TERM"); if (!term) term = default_term_for_tty(tty_path); x = strappend("TERM=", term); if (!x) return -ENOMEM; our_env[n_env++] = x; } if (journal_stream_dev != 0 && journal_stream_ino != 0) { if (asprintf(&x, "JOURNAL_STREAM=" DEV_FMT ":" INO_FMT, journal_stream_dev, journal_stream_ino) < 0) return -ENOMEM; our_env[n_env++] = x; } our_env[n_env++] = NULL; assert(n_env <= 12); *ret = our_env; our_env = NULL; return 0; } static int build_pass_environment(const ExecContext *c, char ***ret) { _cleanup_strv_free_ char **pass_env = NULL; size_t n_env = 0, n_bufsize = 0; char **i; STRV_FOREACH(i, c->pass_environment) { _cleanup_free_ char *x = NULL; char *v; v = getenv(*i); if (!v) continue; x = strjoin(*i, "=", v); if (!x) return -ENOMEM; if (!GREEDY_REALLOC(pass_env, n_bufsize, n_env + 2)) return -ENOMEM; pass_env[n_env++] = x; pass_env[n_env] = NULL; x = NULL; } *ret = pass_env; pass_env = NULL; return 0; } static bool exec_needs_mount_namespace( const ExecContext *context, const ExecParameters *params, ExecRuntime *runtime) { assert(context); assert(params); if (!strv_isempty(context->read_write_paths) || !strv_isempty(context->read_only_paths) || !strv_isempty(context->inaccessible_paths)) return true; if (context->mount_flags != 0) return true; if (context->private_tmp && runtime && (runtime->tmp_dir || runtime->var_tmp_dir)) return true; if (context->private_devices || context->protect_system != PROTECT_SYSTEM_NO || context->protect_home != PROTECT_HOME_NO || context->protect_kernel_tunables || context->protect_kernel_modules || context->protect_control_groups) return true; return false; } static int setup_private_users(uid_t uid, gid_t gid) { _cleanup_free_ char *uid_map = NULL, *gid_map = NULL; _cleanup_close_pair_ int errno_pipe[2] = { -1, -1 }; _cleanup_close_ int unshare_ready_fd = -1; _cleanup_(sigkill_waitp) pid_t pid = 0; uint64_t c = 1; siginfo_t si; ssize_t n; int r; /* Set up a user namespace and map root to root, the selected UID/GID to itself, and everything else to * nobody. In order to be able to write this mapping we need CAP_SETUID in the original user namespace, which * we however lack after opening the user namespace. To work around this we fork() a temporary child process, * which waits for the parent to create the new user namespace while staying in the original namespace. The * child then writes the UID mapping, under full privileges. The parent waits for the child to finish and * continues execution normally. */ if (uid != 0 && uid_is_valid(uid)) asprintf(&uid_map, "0 0 1\n" /* Map root → root */ UID_FMT " " UID_FMT " 1\n", /* Map $UID → $UID */ uid, uid); else uid_map = strdup("0 0 1\n"); /* The case where the above is the same */ if (!uid_map) return -ENOMEM; if (gid != 0 && gid_is_valid(gid)) asprintf(&gid_map, "0 0 1\n" /* Map root → root */ GID_FMT " " GID_FMT " 1\n", /* Map $GID → $GID */ gid, gid); else gid_map = strdup("0 0 1\n"); /* The case where the above is the same */ if (!gid_map) return -ENOMEM; /* Create a communication channel so that the parent can tell the child when it finished creating the user * namespace. */ unshare_ready_fd = eventfd(0, EFD_CLOEXEC); if (unshare_ready_fd < 0) return -errno; /* Create a communication channel so that the child can tell the parent a proper error code in case it * failed. */ if (pipe2(errno_pipe, O_CLOEXEC) < 0) return -errno; pid = fork(); if (pid < 0) return -errno; if (pid == 0) { _cleanup_close_ int fd = -1; const char *a; pid_t ppid; /* Child process, running in the original user namespace. Let's update the parent's UID/GID map from * here, after the parent opened its own user namespace. */ ppid = getppid(); errno_pipe[0] = safe_close(errno_pipe[0]); /* Wait until the parent unshared the user namespace */ if (read(unshare_ready_fd, &c, sizeof(c)) < 0) { r = -errno; goto child_fail; } /* Disable the setgroups() system call in the child user namespace, for good. */ a = procfs_file_alloca(ppid, "setgroups"); fd = open(a, O_WRONLY|O_CLOEXEC); if (fd < 0) { if (errno != ENOENT) { r = -errno; goto child_fail; } /* If the file is missing the kernel is too old, let's continue anyway. */ } else { if (write(fd, "deny\n", 5) < 0) { r = -errno; goto child_fail; } fd = safe_close(fd); } /* First write the GID map */ a = procfs_file_alloca(ppid, "gid_map"); fd = open(a, O_WRONLY|O_CLOEXEC); if (fd < 0) { r = -errno; goto child_fail; } if (write(fd, gid_map, strlen(gid_map)) < 0) { r = -errno; goto child_fail; } fd = safe_close(fd); /* The write the UID map */ a = procfs_file_alloca(ppid, "uid_map"); fd = open(a, O_WRONLY|O_CLOEXEC); if (fd < 0) { r = -errno; goto child_fail; } if (write(fd, uid_map, strlen(uid_map)) < 0) { r = -errno; goto child_fail; } _exit(EXIT_SUCCESS); child_fail: (void) write(errno_pipe[1], &r, sizeof(r)); _exit(EXIT_FAILURE); } errno_pipe[1] = safe_close(errno_pipe[1]); if (unshare(CLONE_NEWUSER) < 0) return -errno; /* Let the child know that the namespace is ready now */ if (write(unshare_ready_fd, &c, sizeof(c)) < 0) return -errno; /* Try to read an error code from the child */ n = read(errno_pipe[0], &r, sizeof(r)); if (n < 0) return -errno; if (n == sizeof(r)) { /* an error code was sent to us */ if (r < 0) return r; return -EIO; } if (n != 0) /* on success we should have read 0 bytes */ return -EIO; r = wait_for_terminate(pid, &si); if (r < 0) return r; pid = 0; /* If something strange happened with the child, let's consider this fatal, too */ if (si.si_code != CLD_EXITED || si.si_status != 0) return -EIO; return 0; } static int setup_runtime_directory( const ExecContext *context, const ExecParameters *params, uid_t uid, gid_t gid) { char **rt; int r; assert(context); assert(params); STRV_FOREACH(rt, context->runtime_directory) { _cleanup_free_ char *p; p = strjoin(params->runtime_prefix, "/", *rt); if (!p) return -ENOMEM; r = mkdir_p_label(p, context->runtime_directory_mode); if (r < 0) return r; r = chmod_and_chown(p, context->runtime_directory_mode, uid, gid); if (r < 0) return r; } return 0; } static int setup_smack( const ExecContext *context, const ExecCommand *command) { #ifdef HAVE_SMACK int r; assert(context); assert(command); if (!mac_smack_use()) return 0; if (context->smack_process_label) { r = mac_smack_apply_pid(0, context->smack_process_label); if (r < 0) 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) return r; r = mac_smack_apply_pid(0, exec_label ? : SMACK_DEFAULT_PROCESS_LABEL); if (r < 0) return r; } #endif #endif return 0; } static int compile_read_write_paths( const ExecContext *context, const ExecParameters *params, char ***ret) { _cleanup_strv_free_ char **l = NULL; char **rt; /* Compile the list of writable paths. This is the combination of the explicitly configured paths, plus all * runtime directories. */ if (strv_isempty(context->read_write_paths) && strv_isempty(context->runtime_directory)) { *ret = NULL; /* NOP if neither is set */ return 0; } l = strv_copy(context->read_write_paths); if (!l) return -ENOMEM; STRV_FOREACH(rt, context->runtime_directory) { char *s; s = strjoin(params->runtime_prefix, "/", *rt); if (!s) return -ENOMEM; if (strv_consume(&l, s) < 0) return -ENOMEM; } *ret = l; l = NULL; return 0; } static int apply_mount_namespace(Unit *u, const ExecContext *context, const ExecParameters *params, ExecRuntime *runtime) { int r; _cleanup_free_ char **rw = NULL; char *tmp = NULL, *var = NULL; const char *root_dir = NULL; NameSpaceInfo ns_info = { .ignore_protect_paths = false, .private_dev = context->private_devices, .protect_control_groups = context->protect_control_groups, .protect_kernel_tunables = context->protect_kernel_tunables, .protect_kernel_modules = context->protect_kernel_modules, }; assert(context); /* The runtime struct only contains the parent of the private /tmp, * which is non-accessible to world users. Inside of it there's a /tmp * that is sticky, and that's the one we want to use here. */ if (context->private_tmp && runtime) { if (runtime->tmp_dir) tmp = strjoina(runtime->tmp_dir, "/tmp"); if (runtime->var_tmp_dir) var = strjoina(runtime->var_tmp_dir, "/tmp"); } r = compile_read_write_paths(context, params, &rw); if (r < 0) return r; if (params->flags & EXEC_APPLY_CHROOT) root_dir = context->root_directory; /* * If DynamicUser=no and RootDirectory= is set then lets pass a relaxed * sandbox info, otherwise enforce it, don't ignore protected paths and * fail if we are enable to apply the sandbox inside the mount namespace. */ if (!context->dynamic_user && root_dir) ns_info.ignore_protect_paths = true; r = setup_namespace(root_dir, &ns_info, rw, context->read_only_paths, context->inaccessible_paths, tmp, var, context->protect_home, context->protect_system, context->mount_flags); /* If we couldn't set up the namespace this is probably due to a * missing capability. In this case, silently proceeed. */ if (IN_SET(r, -EPERM, -EACCES)) { log_open(); log_unit_debug_errno(u, r, "Failed to set up namespace, assuming containerized execution, ignoring: %m"); log_close(); r = 0; } return r; } static int apply_working_directory(const ExecContext *context, const ExecParameters *params, const char *home, const bool needs_mount_ns) { const char *d; const char *wd; assert(context); if (context->working_directory_home) wd = home; else if (context->working_directory) wd = context->working_directory; else wd = "/"; if (params->flags & EXEC_APPLY_CHROOT) { if (!needs_mount_ns && context->root_directory) if (chroot(context->root_directory) < 0) return -errno; d = wd; } else d = strjoina(strempty(context->root_directory), "/", strempty(wd)); if (chdir(d) < 0 && !context->working_directory_missing_ok) return -errno; return 0; } static void append_socket_pair(int *array, unsigned *n, int pair[2]) { assert(array); assert(n); if (!pair) return; if (pair[0] >= 0) array[(*n)++] = pair[0]; if (pair[1] >= 0) array[(*n)++] = pair[1]; } static int close_remaining_fds( const ExecParameters *params, ExecRuntime *runtime, DynamicCreds *dcreds, int user_lookup_fd, int socket_fd, int *fds, unsigned n_fds) { unsigned n_dont_close = 0; int dont_close[n_fds + 12]; 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 (runtime) append_socket_pair(dont_close, &n_dont_close, runtime->netns_storage_socket); if (dcreds) { if (dcreds->user) append_socket_pair(dont_close, &n_dont_close, dcreds->user->storage_socket); if (dcreds->group) append_socket_pair(dont_close, &n_dont_close, dcreds->group->storage_socket); } if (user_lookup_fd >= 0) dont_close[n_dont_close++] = user_lookup_fd; return close_all_fds(dont_close, n_dont_close); } static bool context_has_address_families(const ExecContext *c) { assert(c); return c->address_families_whitelist || !set_isempty(c->address_families); } static bool context_has_syscall_filters(const ExecContext *c) { assert(c); return c->syscall_whitelist || !set_isempty(c->syscall_filter) || !set_isempty(c->syscall_archs); } static bool context_has_no_new_privileges(const ExecContext *c) { assert(c); if (c->no_new_privileges) return true; if (have_effective_cap(CAP_SYS_ADMIN)) /* if we are privileged, we don't need NNP */ return false; return context_has_address_families(c) || /* we need NNP if we have any form of seccomp and are unprivileged */ c->memory_deny_write_execute || c->restrict_realtime || exec_context_restrict_namespaces_set(c) || c->protect_kernel_tunables || c->protect_kernel_modules || c->private_devices || context_has_syscall_filters(c); } static int send_user_lookup( Unit *unit, int user_lookup_fd, uid_t uid, gid_t gid) { assert(unit); /* Send the resolved UID/GID to PID 1 after we learnt it. We send a single datagram, containing the UID/GID * data as well as the unit name. Note that we suppress sending this if no user/group to resolve was * specified. */ if (user_lookup_fd < 0) return 0; if (!uid_is_valid(uid) && !gid_is_valid(gid)) return 0; if (writev(user_lookup_fd, (struct iovec[]) { { .iov_base = &uid, .iov_len = sizeof(uid) }, { .iov_base = &gid, .iov_len = sizeof(gid) }, { .iov_base = unit->id, .iov_len = strlen(unit->id) }}, 3) < 0) return -errno; return 0; } static int exec_child( Unit *unit, ExecCommand *command, const ExecContext *context, const ExecParameters *params, ExecRuntime *runtime, DynamicCreds *dcreds, char **argv, int socket_fd, int named_iofds[3], int *fds, unsigned n_fds, char **files_env, int user_lookup_fd, int *exit_status) { _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **final_argv = NULL; _cleanup_free_ char *mac_selinux_context_net = NULL; _cleanup_free_ gid_t *supplementary_gids = NULL; const char *username = NULL, *groupname = NULL; const char *home = NULL, *shell = NULL; dev_t journal_stream_dev = 0; ino_t journal_stream_ino = 0; bool needs_mount_namespace; uid_t uid = UID_INVALID; gid_t gid = GID_INVALID; int i, r, ngids = 0; assert(unit); assert(command); assert(context); assert(params); assert(exit_status); rename_process_from_path(command->path); /* We reset exactly these signals, since they are the * only ones we set to SIG_IGN in the main daemon. All * others we leave untouched because we set them to * SIG_DFL or a valid handler initially, both of which * will be demoted to SIG_DFL. */ (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); if (context->ignore_sigpipe) (void) ignore_signals(SIGPIPE, -1); r = reset_signal_mask(); if (r < 0) { *exit_status = EXIT_SIGNAL_MASK; return r; } if (params->idle_pipe) do_idle_pipe_dance(params->idle_pipe); /* Close sockets very early to make sure we don't * block init reexecution because it cannot bind its * sockets */ log_forget_fds(); r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, fds, n_fds); if (r < 0) { *exit_status = EXIT_FDS; return r; } if (!context->same_pgrp) if (setsid() < 0) { *exit_status = EXIT_SETSID; return -errno; } exec_context_tty_reset(context, params); if (params->confirm_spawn) { const char *vc = params->confirm_spawn; _cleanup_free_ char *cmdline = NULL; cmdline = exec_command_line(argv); if (!cmdline) { *exit_status = EXIT_CONFIRM; return -ENOMEM; } r = ask_for_confirmation(vc, cmdline); if (r != CONFIRM_EXECUTE) { if (r == CONFIRM_PRETEND_SUCCESS) { *exit_status = EXIT_SUCCESS; return 0; } *exit_status = EXIT_CONFIRM; return -ECANCELED; } } if (context->dynamic_user && dcreds) { /* Make sure we bypass our own NSS module for any NSS checks */ if (putenv((char*) "SYSTEMD_NSS_DYNAMIC_BYPASS=1") != 0) { *exit_status = EXIT_USER; return -errno; } r = dynamic_creds_realize(dcreds, &uid, &gid); if (r < 0) { *exit_status = EXIT_USER; return r; } if (!uid_is_valid(uid) || !gid_is_valid(gid)) { *exit_status = EXIT_USER; return -ESRCH; } if (dcreds->user) username = dcreds->user->name; } else { r = get_fixed_user(context, &username, &uid, &gid, &home, &shell); if (r < 0) { *exit_status = EXIT_USER; return r; } r = get_fixed_group(context, &groupname, &gid); if (r < 0) { *exit_status = EXIT_GROUP; return r; } } /* Initialize user supplementary groups and get SupplementaryGroups= ones */ r = get_supplementary_groups(context, username, groupname, gid, &supplementary_gids, &ngids); if (r < 0) { *exit_status = EXIT_GROUP; return r; } r = send_user_lookup(unit, user_lookup_fd, uid, gid); if (r < 0) { *exit_status = EXIT_USER; return r; } user_lookup_fd = safe_close(user_lookup_fd); /* If a socket is connected to STDIN/STDOUT/STDERR, we * must sure to drop O_NONBLOCK */ if (socket_fd >= 0) (void) fd_nonblock(socket_fd, false); r = setup_input(context, params, socket_fd, named_iofds); if (r < 0) { *exit_status = EXIT_STDIN; return r; } r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, named_iofds, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino); if (r < 0) { *exit_status = EXIT_STDOUT; return r; } r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, named_iofds, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino); if (r < 0) { *exit_status = EXIT_STDERR; return r; } if (params->cgroup_path) { r = cg_attach_everywhere(params->cgroup_supported, params->cgroup_path, 0, NULL, NULL); if (r < 0) { *exit_status = EXIT_CGROUP; return r; } } if (context->oom_score_adjust_set) { char t[DECIMAL_STR_MAX(context->oom_score_adjust)]; /* When we can't make this change due to EPERM, then * let's silently skip over it. User namespaces * prohibit write access to this file, and we * shouldn't trip up over that. */ sprintf(t, "%i", context->oom_score_adjust); r = write_string_file("/proc/self/oom_score_adj", t, 0); if (r == -EPERM || r == -EACCES) { log_open(); log_unit_debug_errno(unit, r, "Failed to adjust OOM setting, assuming containerized execution, ignoring: %m"); log_close(); } else if (r < 0) { *exit_status = EXIT_OOM_ADJUST; return -errno; } } if (context->nice_set) if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) { *exit_status = EXIT_NICE; return -errno; } if (context->cpu_sched_set) { struct sched_param param = { .sched_priority = context->cpu_sched_priority, }; r = sched_setscheduler(0, context->cpu_sched_policy | (context->cpu_sched_reset_on_fork ? SCHED_RESET_ON_FORK : 0), ¶m); if (r < 0) { *exit_status = EXIT_SETSCHEDULER; return -errno; } } if (context->cpuset) if (sched_setaffinity(0, CPU_ALLOC_SIZE(context->cpuset_ncpus), context->cpuset) < 0) { *exit_status = EXIT_CPUAFFINITY; return -errno; } if (context->ioprio_set) if (ioprio_set(IOPRIO_WHO_PROCESS, 0, context->ioprio) < 0) { *exit_status = EXIT_IOPRIO; return -errno; } if (context->timer_slack_nsec != NSEC_INFINITY) if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) { *exit_status = EXIT_TIMERSLACK; return -errno; } if (context->personality != PERSONALITY_INVALID) if (personality(context->personality) < 0) { *exit_status = EXIT_PERSONALITY; return -errno; } if (context->utmp_id) utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path, context->utmp_mode == EXEC_UTMP_INIT ? INIT_PROCESS : context->utmp_mode == EXEC_UTMP_LOGIN ? LOGIN_PROCESS : USER_PROCESS, username ? "root" : context->user); if (context->user) { r = chown_terminal(STDIN_FILENO, uid); if (r < 0) { *exit_status = EXIT_STDIN; return r; } } /* If delegation is enabled we'll pass ownership of the cgroup * (but only in systemd's own controller hierarchy!) to the * user of the new process. */ if (params->cgroup_path && context->user && params->cgroup_delegate) { r = cg_set_task_access(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, 0644, uid, gid); if (r < 0) { *exit_status = EXIT_CGROUP; return r; } r = cg_set_group_access(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, 0755, uid, gid); if (r < 0) { *exit_status = EXIT_CGROUP; return r; } } if (!strv_isempty(context->runtime_directory) && params->runtime_prefix) { r = setup_runtime_directory(context, params, uid, gid); if (r < 0) { *exit_status = EXIT_RUNTIME_DIRECTORY; return r; } } r = build_environment( unit, context, params, n_fds, home, username, shell, journal_stream_dev, journal_stream_ino, &our_env); if (r < 0) { *exit_status = EXIT_MEMORY; return r; } r = build_pass_environment(context, &pass_env); if (r < 0) { *exit_status = EXIT_MEMORY; return r; } accum_env = strv_env_merge(5, params->environment, our_env, pass_env, context->environment, files_env, NULL); if (!accum_env) { *exit_status = EXIT_MEMORY; return -ENOMEM; } accum_env = strv_env_clean(accum_env); (void) umask(context->umask); 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); if (r < 0) { *exit_status = EXIT_PAM; return r; } } } if (context->private_network && runtime && runtime->netns_storage_socket[0] >= 0) { r = setup_netns(runtime->netns_storage_socket); if (r < 0) { *exit_status = EXIT_NETWORK; return r; } } needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime); if (needs_mount_namespace) { r = apply_mount_namespace(unit, context, params, runtime); if (r < 0) { *exit_status = EXIT_NAMESPACE; return r; } } /* Apply just after mount namespace setup */ r = apply_working_directory(context, params, home, needs_mount_namespace); if (r < 0) { *exit_status = EXIT_CHROOT; return r; } /* Drop groups as early as possbile */ if ((params->flags & EXEC_APPLY_PERMISSIONS) && !command->privileged) { r = enforce_groups(context, gid, supplementary_gids, ngids); if (r < 0) { *exit_status = EXIT_GROUP; return r; } } #ifdef HAVE_SELINUX if ((params->flags & EXEC_APPLY_PERMISSIONS) && mac_selinux_use() && params->selinux_context_net && socket_fd >= 0 && !command->privileged) { r = mac_selinux_get_child_mls_label(socket_fd, command->path, context->selinux_context, &mac_selinux_context_net); if (r < 0) { *exit_status = EXIT_SELINUX_CONTEXT; return r; } } #endif if ((params->flags & EXEC_APPLY_PERMISSIONS) && context->private_users) { r = setup_private_users(uid, gid); if (r < 0) { *exit_status = EXIT_USER; return r; } } /* We repeat the fd closing here, to make sure that * nothing is leaked from the PAM modules. Note that * we are more aggressive this time since socket_fd * and the netns fds we don't need anymore. The custom * endpoint fd was needed to upload the policy and can * now be closed as well. */ r = close_all_fds(fds, n_fds); if (r >= 0) r = shift_fds(fds, n_fds); if (r >= 0) r = flags_fds(fds, n_fds, context->non_blocking); if (r < 0) { *exit_status = EXIT_FDS; return r; } if ((params->flags & EXEC_APPLY_PERMISSIONS) && !command->privileged) { int secure_bits = context->secure_bits; for (i = 0; i < _RLIMIT_MAX; i++) { if (!context->rlimit[i]) continue; r = setrlimit_closest(i, context->rlimit[i]); if (r < 0) { *exit_status = EXIT_LIMITS; return r; } } /* Set the RTPRIO resource limit to 0, but only if nothing else was explicitly requested. */ if (context->restrict_realtime && !context->rlimit[RLIMIT_RTPRIO]) { if (setrlimit(RLIMIT_RTPRIO, &RLIMIT_MAKE_CONST(0)) < 0) { *exit_status = EXIT_LIMITS; return -errno; } } if (!cap_test_all(context->capability_bounding_set)) { r = capability_bounding_set_drop(context->capability_bounding_set, false); if (r < 0) { *exit_status = EXIT_CAPABILITIES; return r; } } /* This is done before enforce_user, but ambient set * does not survive over setresuid() if keep_caps is not set. */ if (context->capability_ambient_set != 0) { r = capability_ambient_set_apply(context->capability_ambient_set, true); if (r < 0) { *exit_status = EXIT_CAPABILITIES; return r; } } if (context->user) { r = enforce_user(context, uid); if (r < 0) { *exit_status = EXIT_USER; return r; } if (context->capability_ambient_set != 0) { /* Fix the ambient capabilities after user change. */ r = capability_ambient_set_apply(context->capability_ambient_set, false); if (r < 0) { *exit_status = EXIT_CAPABILITIES; return r; } /* If we were asked to change user and ambient capabilities * were requested, we had to add keep-caps to the securebits * so that we would maintain the inherited capability set * through the setresuid(). Make sure that the bit is added * also to the context secure_bits so that we don't try to * drop the bit away next. */ secure_bits |= 1<selinux_context; if (exec_context) { r = setexeccon(exec_context); if (r < 0) { *exit_status = EXIT_SELINUX_CONTEXT; return r; } } } #endif r = setup_smack(context, command); if (r < 0) { *exit_status = EXIT_SMACK_PROCESS_LABEL; return r; } #ifdef HAVE_APPARMOR if (context->apparmor_profile && mac_apparmor_use()) { r = aa_change_onexec(context->apparmor_profile); if (r < 0 && !context->apparmor_profile_ignore) { *exit_status = EXIT_APPARMOR_PROFILE; return -errno; } } #endif /* PR_GET_SECUREBITS is not privileged, while * PR_SET_SECUREBITS is. So to suppress * potential EPERMs we'll try not to call * PR_SET_SECUREBITS unless necessary. */ if (prctl(PR_GET_SECUREBITS) != secure_bits) if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) { *exit_status = EXIT_SECUREBITS; return -errno; } if (context_has_no_new_privileges(context)) if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { *exit_status = EXIT_NO_NEW_PRIVILEGES; return -errno; } #ifdef HAVE_SECCOMP if (context_has_address_families(context)) { r = apply_address_families(unit, context); if (r < 0) { *exit_status = EXIT_ADDRESS_FAMILIES; return r; } } if (context->memory_deny_write_execute) { r = apply_memory_deny_write_execute(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; return r; } } if (context->restrict_realtime) { r = apply_restrict_realtime(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; return r; } } r = apply_restrict_namespaces(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; return r; } if (context->protect_kernel_tunables) { r = apply_protect_sysctl(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; return r; } } if (context->protect_kernel_modules) { r = apply_protect_kernel_modules(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; return r; } } if (context->private_devices) { r = apply_private_devices(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; return r; } } /* This really should remain the last step before the execve(), to make sure our own code is unaffected * by the filter as little as possible. */ if (context_has_syscall_filters(context)) { r = apply_seccomp(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; return r; } } #endif } final_argv = replace_env_argv(argv, accum_env); if (!final_argv) { *exit_status = EXIT_MEMORY; return -ENOMEM; } if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { _cleanup_free_ char *line; line = exec_command_line(final_argv); if (line) { log_open(); log_struct(LOG_DEBUG, LOG_UNIT_ID(unit), "EXECUTABLE=%s", command->path, LOG_UNIT_MESSAGE(unit, "Executing: %s", line), NULL); log_close(); } } execve(command->path, final_argv, accum_env); *exit_status = EXIT_EXEC; return -errno; } int exec_spawn(Unit *unit, ExecCommand *command, const ExecContext *context, const ExecParameters *params, ExecRuntime *runtime, DynamicCreds *dcreds, pid_t *ret) { _cleanup_strv_free_ char **files_env = NULL; int *fds = NULL; unsigned n_fds = 0; _cleanup_free_ char *line = NULL; int socket_fd, r; int named_iofds[3] = { -1, -1, -1 }; char **argv; pid_t pid; assert(unit); assert(command); assert(context); assert(ret); assert(params); assert(params->fds || params->n_fds <= 0); if (context->std_input == EXEC_INPUT_SOCKET || context->std_output == EXEC_OUTPUT_SOCKET || context->std_error == EXEC_OUTPUT_SOCKET) { if (params->n_fds != 1) { log_unit_error(unit, "Got more than one socket."); return -EINVAL; } socket_fd = params->fds[0]; } else { socket_fd = -1; fds = params->fds; n_fds = params->n_fds; } r = exec_context_named_iofds(unit, context, params, named_iofds); if (r < 0) return log_unit_error_errno(unit, r, "Failed to load a named file descriptor: %m"); r = exec_context_load_environment(unit, context, &files_env); if (r < 0) return log_unit_error_errno(unit, r, "Failed to load environment files: %m"); argv = params->argv ?: command->argv; line = exec_command_line(argv); if (!line) return log_oom(); log_struct(LOG_DEBUG, LOG_UNIT_ID(unit), LOG_UNIT_MESSAGE(unit, "About to execute: %s", line), "EXECUTABLE=%s", command->path, NULL); pid = fork(); if (pid < 0) return log_unit_error_errno(unit, errno, "Failed to fork: %m"); if (pid == 0) { int exit_status; r = exec_child(unit, command, context, params, runtime, dcreds, argv, socket_fd, named_iofds, fds, n_fds, files_env, unit->manager->user_lookup_fds[1], &exit_status); if (r < 0) { log_open(); log_struct_errno(LOG_ERR, r, LOG_MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED), LOG_UNIT_ID(unit), LOG_UNIT_MESSAGE(unit, "Failed at step %s spawning %s: %m", exit_status_to_string(exit_status, EXIT_STATUS_SYSTEMD), command->path), "EXECUTABLE=%s", command->path, NULL); } _exit(exit_status); } log_unit_debug(unit, "Forked %s as "PID_FMT, command->path, pid); /* We add the new process to the cgroup both in the child (so * that we can be sure that no user code is ever executed * outside of the cgroup) and in the parent (so that we can be * sure that when we kill the cgroup the process will be * killed too). */ if (params->cgroup_path) (void) cg_attach(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, pid); exec_status_start(&command->exec_status, pid); *ret = pid; return 0; } void exec_context_init(ExecContext *c) { assert(c); c->umask = 0022; c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0); c->cpu_sched_policy = SCHED_OTHER; c->syslog_priority = LOG_DAEMON|LOG_INFO; c->syslog_level_prefix = true; c->ignore_sigpipe = true; c->timer_slack_nsec = NSEC_INFINITY; c->personality = PERSONALITY_INVALID; c->runtime_directory_mode = 0755; c->capability_bounding_set = CAP_ALL; c->restrict_namespaces = NAMESPACE_FLAGS_ALL; } void exec_context_done(ExecContext *c) { unsigned l; assert(c); c->environment = strv_free(c->environment); c->environment_files = strv_free(c->environment_files); c->pass_environment = strv_free(c->pass_environment); for (l = 0; l < ELEMENTSOF(c->rlimit); l++) c->rlimit[l] = mfree(c->rlimit[l]); for (l = 0; l < 3; l++) c->stdio_fdname[l] = mfree(c->stdio_fdname[l]); c->working_directory = mfree(c->working_directory); c->root_directory = mfree(c->root_directory); c->tty_path = mfree(c->tty_path); c->syslog_identifier = mfree(c->syslog_identifier); c->user = mfree(c->user); c->group = mfree(c->group); c->supplementary_groups = strv_free(c->supplementary_groups); c->pam_name = mfree(c->pam_name); c->read_only_paths = strv_free(c->read_only_paths); c->read_write_paths = strv_free(c->read_write_paths); c->inaccessible_paths = strv_free(c->inaccessible_paths); if (c->cpuset) CPU_FREE(c->cpuset); c->utmp_id = mfree(c->utmp_id); c->selinux_context = mfree(c->selinux_context); c->apparmor_profile = mfree(c->apparmor_profile); c->syscall_filter = set_free(c->syscall_filter); c->syscall_archs = set_free(c->syscall_archs); c->address_families = set_free(c->address_families); c->runtime_directory = strv_free(c->runtime_directory); } int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_prefix) { char **i; assert(c); if (!runtime_prefix) return 0; STRV_FOREACH(i, c->runtime_directory) { _cleanup_free_ char *p; p = strjoin(runtime_prefix, "/", *i); if (!p) return -ENOMEM; /* We execute this synchronously, since we need to be * sure this is gone when we start the service * next. */ (void) rm_rf(p, REMOVE_ROOT); } return 0; } void exec_command_done(ExecCommand *c) { assert(c); c->path = mfree(c->path); c->argv = strv_free(c->argv); } void exec_command_done_array(ExecCommand *c, unsigned n) { unsigned i; for (i = 0; i < n; i++) exec_command_done(c+i); } ExecCommand* exec_command_free_list(ExecCommand *c) { ExecCommand *i; while ((i = c)) { LIST_REMOVE(command, c, i); exec_command_done(i); free(i); } return NULL; } void exec_command_free_array(ExecCommand **c, unsigned n) { unsigned i; for (i = 0; i < n; i++) c[i] = exec_command_free_list(c[i]); } typedef struct InvalidEnvInfo { Unit *unit; const char *path; } InvalidEnvInfo; static void invalid_env(const char *p, void *userdata) { InvalidEnvInfo *info = userdata; log_unit_error(info->unit, "Ignoring invalid environment assignment '%s': %s", p, info->path); } const char* exec_context_fdname(const ExecContext *c, int fd_index) { assert(c); switch (fd_index) { case STDIN_FILENO: if (c->std_input != EXEC_INPUT_NAMED_FD) return NULL; return c->stdio_fdname[STDIN_FILENO] ?: "stdin"; case STDOUT_FILENO: if (c->std_output != EXEC_OUTPUT_NAMED_FD) return NULL; return c->stdio_fdname[STDOUT_FILENO] ?: "stdout"; case STDERR_FILENO: if (c->std_error != EXEC_OUTPUT_NAMED_FD) return NULL; return c->stdio_fdname[STDERR_FILENO] ?: "stderr"; default: return NULL; } } int exec_context_named_iofds(Unit *unit, const ExecContext *c, const ExecParameters *p, int named_iofds[3]) { unsigned i, targets; const char *stdio_fdname[3]; assert(c); assert(p); targets = (c->std_input == EXEC_INPUT_NAMED_FD) + (c->std_output == EXEC_OUTPUT_NAMED_FD) + (c->std_error == EXEC_OUTPUT_NAMED_FD); for (i = 0; i < 3; i++) stdio_fdname[i] = exec_context_fdname(c, i); for (i = 0; i < p->n_fds && targets > 0; i++) if (named_iofds[STDIN_FILENO] < 0 && c->std_input == EXEC_INPUT_NAMED_FD && stdio_fdname[STDIN_FILENO] && streq(p->fd_names[i], stdio_fdname[STDIN_FILENO])) { named_iofds[STDIN_FILENO] = p->fds[i]; targets--; } else if (named_iofds[STDOUT_FILENO] < 0 && c->std_output == EXEC_OUTPUT_NAMED_FD && stdio_fdname[STDOUT_FILENO] && streq(p->fd_names[i], stdio_fdname[STDOUT_FILENO])) { named_iofds[STDOUT_FILENO] = p->fds[i]; targets--; } else if (named_iofds[STDERR_FILENO] < 0 && c->std_error == EXEC_OUTPUT_NAMED_FD && stdio_fdname[STDERR_FILENO] && streq(p->fd_names[i], stdio_fdname[STDERR_FILENO])) { named_iofds[STDERR_FILENO] = p->fds[i]; targets--; } return (targets == 0 ? 0 : -ENOENT); } int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l) { char **i, **r = NULL; assert(c); assert(l); STRV_FOREACH(i, c->environment_files) { char *fn; int k; bool ignore = false; char **p; _cleanup_globfree_ glob_t pglob = {}; int count, n; fn = *i; if (fn[0] == '-') { ignore = true; fn++; } if (!path_is_absolute(fn)) { if (ignore) continue; strv_free(r); return -EINVAL; } /* Filename supports globbing, take all matching files */ errno = 0; if (glob(fn, 0, NULL, &pglob) != 0) { if (ignore) continue; strv_free(r); return errno > 0 ? -errno : -EINVAL; } count = pglob.gl_pathc; if (count == 0) { if (ignore) continue; strv_free(r); return -EINVAL; } for (n = 0; n < count; n++) { k = load_env_file(NULL, pglob.gl_pathv[n], NULL, &p); if (k < 0) { if (ignore) continue; strv_free(r); return k; } /* Log invalid environment variables with filename */ if (p) { InvalidEnvInfo info = { .unit = unit, .path = pglob.gl_pathv[n] }; p = strv_env_clean_with_callback(p, invalid_env, &info); } if (r == NULL) r = p; else { char **m; m = strv_env_merge(2, r, p); strv_free(r); strv_free(p); if (!m) return -ENOMEM; r = m; } } } *l = r; return 0; } static bool tty_may_match_dev_console(const char *tty) { _cleanup_free_ char *active = NULL; char *console; if (!tty) return true; if (startswith(tty, "/dev/")) tty += 5; /* trivial identity? */ if (streq(tty, "console")) return true; console = resolve_dev_console(&active); /* if we could not resolve, assume it may */ if (!console) return true; /* "tty0" means the active VC, so it may be the same sometimes */ return streq(console, tty) || (streq(console, "tty0") && tty_is_vc(tty)); } bool exec_context_may_touch_console(ExecContext *ec) { return (ec->tty_reset || ec->tty_vhangup || ec->tty_vt_disallocate || is_terminal_input(ec->std_input) || is_terminal_output(ec->std_output) || is_terminal_output(ec->std_error)) && tty_may_match_dev_console(exec_context_tty_path(ec)); } static void strv_fprintf(FILE *f, char **l) { char **g; assert(f); STRV_FOREACH(g, l) fprintf(f, " %s", *g); } void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { char **e, **d; unsigned i; int r; assert(c); assert(f); prefix = strempty(prefix); fprintf(f, "%sUMask: %04o\n" "%sWorkingDirectory: %s\n" "%sRootDirectory: %s\n" "%sNonBlocking: %s\n" "%sPrivateTmp: %s\n" "%sPrivateDevices: %s\n" "%sProtectKernelTunables: %s\n" "%sProtectKernelModules: %s\n" "%sProtectControlGroups: %s\n" "%sPrivateNetwork: %s\n" "%sPrivateUsers: %s\n" "%sProtectHome: %s\n" "%sProtectSystem: %s\n" "%sIgnoreSIGPIPE: %s\n" "%sMemoryDenyWriteExecute: %s\n" "%sRestrictRealtime: %s\n", prefix, c->umask, prefix, c->working_directory ? c->working_directory : "/", prefix, c->root_directory ? c->root_directory : "/", prefix, yes_no(c->non_blocking), prefix, yes_no(c->private_tmp), prefix, yes_no(c->private_devices), prefix, yes_no(c->protect_kernel_tunables), prefix, yes_no(c->protect_kernel_modules), prefix, yes_no(c->protect_control_groups), prefix, yes_no(c->private_network), prefix, yes_no(c->private_users), prefix, protect_home_to_string(c->protect_home), prefix, protect_system_to_string(c->protect_system), prefix, yes_no(c->ignore_sigpipe), prefix, yes_no(c->memory_deny_write_execute), prefix, yes_no(c->restrict_realtime)); STRV_FOREACH(e, c->environment) fprintf(f, "%sEnvironment: %s\n", prefix, *e); STRV_FOREACH(e, c->environment_files) fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e); STRV_FOREACH(e, c->pass_environment) fprintf(f, "%sPassEnvironment: %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", prefix, c->nice); if (c->oom_score_adjust_set) fprintf(f, "%sOOMScoreAdjust: %i\n", prefix, c->oom_score_adjust); for (i = 0; i < RLIM_NLIMITS; i++) if (c->rlimit[i]) { fprintf(f, "%s%s: " RLIM_FMT "\n", prefix, rlimit_to_string(i), c->rlimit[i]->rlim_max); fprintf(f, "%s%sSoft: " RLIM_FMT "\n", prefix, rlimit_to_string(i), c->rlimit[i]->rlim_cur); } if (c->ioprio_set) { _cleanup_free_ char *class_str = NULL; ioprio_class_to_string_alloc(IOPRIO_PRIO_CLASS(c->ioprio), &class_str); fprintf(f, "%sIOSchedulingClass: %s\n" "%sIOPriority: %i\n", prefix, strna(class_str), prefix, (int) IOPRIO_PRIO_DATA(c->ioprio)); } if (c->cpu_sched_set) { _cleanup_free_ char *policy_str = NULL; sched_policy_to_string_alloc(c->cpu_sched_policy, &policy_str); fprintf(f, "%sCPUSchedulingPolicy: %s\n" "%sCPUSchedulingPriority: %i\n" "%sCPUSchedulingResetOnFork: %s\n", prefix, strna(policy_str), prefix, c->cpu_sched_priority, prefix, yes_no(c->cpu_sched_reset_on_fork)); } if (c->cpuset) { fprintf(f, "%sCPUAffinity:", prefix); for (i = 0; i < c->cpuset_ncpus; i++) if (CPU_ISSET_S(i, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset)) fprintf(f, " %u", i); fputs("\n", f); } if (c->timer_slack_nsec != NSEC_INFINITY) fprintf(f, "%sTimerSlackNSec: "NSEC_FMT "\n", prefix, c->timer_slack_nsec); fprintf(f, "%sStandardInput: %s\n" "%sStandardOutput: %s\n" "%sStandardError: %s\n", prefix, exec_input_to_string(c->std_input), prefix, exec_output_to_string(c->std_output), prefix, exec_output_to_string(c->std_error)); if (c->tty_path) fprintf(f, "%sTTYPath: %s\n" "%sTTYReset: %s\n" "%sTTYVHangup: %s\n" "%sTTYVTDisallocate: %s\n", prefix, c->tty_path, prefix, yes_no(c->tty_reset), prefix, yes_no(c->tty_vhangup), prefix, yes_no(c->tty_vt_disallocate)); if (c->std_output == EXEC_OUTPUT_SYSLOG || c->std_output == EXEC_OUTPUT_KMSG || c->std_output == EXEC_OUTPUT_JOURNAL || c->std_output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || c->std_output == EXEC_OUTPUT_KMSG_AND_CONSOLE || c->std_output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE || c->std_error == EXEC_OUTPUT_SYSLOG || c->std_error == EXEC_OUTPUT_KMSG || c->std_error == EXEC_OUTPUT_JOURNAL || c->std_error == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || c->std_error == EXEC_OUTPUT_KMSG_AND_CONSOLE || c->std_error == EXEC_OUTPUT_JOURNAL_AND_CONSOLE) { _cleanup_free_ char *fac_str = NULL, *lvl_str = NULL; log_facility_unshifted_to_string_alloc(c->syslog_priority >> 3, &fac_str); log_level_to_string_alloc(LOG_PRI(c->syslog_priority), &lvl_str); fprintf(f, "%sSyslogFacility: %s\n" "%sSyslogLevel: %s\n", prefix, strna(fac_str), prefix, strna(lvl_str)); } if (c->secure_bits) fprintf(f, "%sSecure Bits:%s%s%s%s%s%s\n", prefix, (c->secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<capability_bounding_set != CAP_ALL) { unsigned long l; fprintf(f, "%sCapabilityBoundingSet:", prefix); for (l = 0; l <= cap_last_cap(); l++) if (c->capability_bounding_set & (UINT64_C(1) << l)) fprintf(f, " %s", strna(capability_to_name(l))); fputs("\n", f); } if (c->capability_ambient_set != 0) { unsigned long l; fprintf(f, "%sAmbientCapabilities:", prefix); for (l = 0; l <= cap_last_cap(); l++) if (c->capability_ambient_set & (UINT64_C(1) << l)) fprintf(f, " %s", strna(capability_to_name(l))); fputs("\n", f); } if (c->user) fprintf(f, "%sUser: %s\n", prefix, c->user); if (c->group) fprintf(f, "%sGroup: %s\n", prefix, c->group); fprintf(f, "%sDynamicUser: %s\n", prefix, yes_no(c->dynamic_user)); if (strv_length(c->supplementary_groups) > 0) { fprintf(f, "%sSupplementaryGroups:", prefix); strv_fprintf(f, c->supplementary_groups); fputs("\n", f); } if (c->pam_name) fprintf(f, "%sPAMName: %s\n", prefix, c->pam_name); if (strv_length(c->read_write_paths) > 0) { fprintf(f, "%sReadWritePaths:", prefix); strv_fprintf(f, c->read_write_paths); fputs("\n", f); } if (strv_length(c->read_only_paths) > 0) { fprintf(f, "%sReadOnlyPaths:", prefix); strv_fprintf(f, c->read_only_paths); fputs("\n", f); } if (strv_length(c->inaccessible_paths) > 0) { fprintf(f, "%sInaccessiblePaths:", prefix); strv_fprintf(f, c->inaccessible_paths); fputs("\n", f); } if (c->utmp_id) fprintf(f, "%sUtmpIdentifier: %s\n", prefix, c->utmp_id); if (c->selinux_context) fprintf(f, "%sSELinuxContext: %s%s\n", prefix, c->selinux_context_ignore ? "-" : "", c->selinux_context); if (c->personality != PERSONALITY_INVALID) fprintf(f, "%sPersonality: %s\n", prefix, strna(personality_to_string(c->personality))); if (c->syscall_filter) { #ifdef HAVE_SECCOMP Iterator j; void *id; bool first = true; #endif fprintf(f, "%sSystemCallFilter: ", prefix); if (!c->syscall_whitelist) fputc('~', f); #ifdef HAVE_SECCOMP SET_FOREACH(id, c->syscall_filter, j) { _cleanup_free_ char *name = NULL; if (first) first = false; else fputc(' ', f); name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); fputs(strna(name), f); } #endif fputc('\n', f); } if (c->syscall_archs) { #ifdef HAVE_SECCOMP Iterator j; void *id; #endif fprintf(f, "%sSystemCallArchitectures:", prefix); #ifdef HAVE_SECCOMP SET_FOREACH(id, c->syscall_archs, j) fprintf(f, " %s", strna(seccomp_arch_to_string(PTR_TO_UINT32(id) - 1))); #endif fputc('\n', f); } if (exec_context_restrict_namespaces_set(c)) { _cleanup_free_ char *s = NULL; r = namespace_flag_to_string_many(c->restrict_namespaces, &s); if (r >= 0) fprintf(f, "%sRestrictNamespaces: %s\n", prefix, s); } if (c->syscall_errno > 0) fprintf(f, "%sSystemCallErrorNumber: %s\n", prefix, strna(errno_to_name(c->syscall_errno))); if (c->apparmor_profile) fprintf(f, "%sAppArmorProfile: %s%s\n", prefix, c->apparmor_profile_ignore ? "-" : "", c->apparmor_profile); } bool exec_context_maintains_privileges(ExecContext *c) { assert(c); /* Returns true if the process forked off would run under * an unchanged UID or as root. */ if (!c->user) return true; if (streq(c->user, "root") || streq(c->user, "0")) return true; return false; } void exec_status_start(ExecStatus *s, pid_t pid) { assert(s); zero(*s); s->pid = pid; dual_timestamp_get(&s->start_timestamp); } void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status) { assert(s); if (s->pid && s->pid != pid) zero(*s); s->pid = pid; dual_timestamp_get(&s->exit_timestamp); s->code = code; s->status = status; if (context) { if (context->utmp_id) utmp_put_dead_process(context->utmp_id, pid, code, status); exec_context_tty_reset(context, NULL); } } void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { char buf[FORMAT_TIMESTAMP_MAX]; assert(s); assert(f); if (s->pid <= 0) return; prefix = strempty(prefix); fprintf(f, "%sPID: "PID_FMT"\n", prefix, s->pid); if (dual_timestamp_is_set(&s->start_timestamp)) fprintf(f, "%sStart Timestamp: %s\n", prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp.realtime)); if (dual_timestamp_is_set(&s->exit_timestamp)) fprintf(f, "%sExit Timestamp: %s\n" "%sExit Code: %s\n" "%sExit Status: %i\n", prefix, format_timestamp(buf, sizeof(buf), s->exit_timestamp.realtime), prefix, sigchld_code_to_string(s->code), prefix, s->status); } char *exec_command_line(char **argv) { size_t k; char *n, *p, **a; bool first = true; assert(argv); k = 1; STRV_FOREACH(a, argv) k += strlen(*a)+3; n = new(char, k); if (!n) return NULL; p = n; STRV_FOREACH(a, argv) { if (!first) *(p++) = ' '; else first = false; if (strpbrk(*a, WHITESPACE)) { *(p++) = '\''; p = stpcpy(p, *a); *(p++) = '\''; } else p = stpcpy(p, *a); } *p = 0; /* FIXME: this doesn't really handle arguments that have * spaces and ticks in them */ return n; } void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix) { _cleanup_free_ char *cmd = NULL; const char *prefix2; assert(c); assert(f); prefix = strempty(prefix); prefix2 = strjoina(prefix, "\t"); cmd = exec_command_line(c->argv); fprintf(f, "%sCommand Line: %s\n", prefix, cmd ? cmd : strerror(ENOMEM)); exec_status_dump(&c->exec_status, f, prefix2); } void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix) { assert(f); prefix = strempty(prefix); LIST_FOREACH(command, c, c) exec_command_dump(c, f, prefix); } void exec_command_append_list(ExecCommand **l, ExecCommand *e) { ExecCommand *end; assert(l); assert(e); if (*l) { /* It's kind of important, that we keep the order here */ LIST_FIND_TAIL(command, *l, end); LIST_INSERT_AFTER(command, *l, end, e); } else *l = e; } int exec_command_set(ExecCommand *c, const char *path, ...) { va_list ap; char **l, *p; assert(c); assert(path); va_start(ap, path); l = strv_new_ap(path, ap); va_end(ap); if (!l) return -ENOMEM; p = strdup(path); if (!p) { strv_free(l); return -ENOMEM; } free(c->path); c->path = p; strv_free(c->argv); c->argv = l; return 0; } int exec_command_append(ExecCommand *c, const char *path, ...) { _cleanup_strv_free_ char **l = NULL; va_list ap; int r; assert(c); assert(path); va_start(ap, path); l = strv_new_ap(path, ap); va_end(ap); if (!l) return -ENOMEM; r = strv_extend_strv(&c->argv, l, false); if (r < 0) return r; return 0; } static int exec_runtime_allocate(ExecRuntime **rt) { if (*rt) return 0; *rt = new0(ExecRuntime, 1); if (!*rt) return -ENOMEM; (*rt)->n_ref = 1; (*rt)->netns_storage_socket[0] = (*rt)->netns_storage_socket[1] = -1; return 0; } int exec_runtime_make(ExecRuntime **rt, ExecContext *c, const char *id) { int r; assert(rt); assert(c); assert(id); if (*rt) return 1; if (!c->private_network && !c->private_tmp) return 0; r = exec_runtime_allocate(rt); if (r < 0) return r; if (c->private_network && (*rt)->netns_storage_socket[0] < 0) { if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, (*rt)->netns_storage_socket) < 0) return -errno; } if (c->private_tmp && !(*rt)->tmp_dir) { r = setup_tmp_dirs(id, &(*rt)->tmp_dir, &(*rt)->var_tmp_dir); if (r < 0) return r; } return 1; } ExecRuntime *exec_runtime_ref(ExecRuntime *r) { assert(r); assert(r->n_ref > 0); r->n_ref++; return r; } ExecRuntime *exec_runtime_unref(ExecRuntime *r) { if (!r) return NULL; assert(r->n_ref > 0); r->n_ref--; if (r->n_ref > 0) return NULL; free(r->tmp_dir); free(r->var_tmp_dir); safe_close_pair(r->netns_storage_socket); return mfree(r); } int exec_runtime_serialize(Unit *u, ExecRuntime *rt, FILE *f, FDSet *fds) { assert(u); assert(f); assert(fds); if (!rt) return 0; if (rt->tmp_dir) unit_serialize_item(u, f, "tmp-dir", rt->tmp_dir); if (rt->var_tmp_dir) unit_serialize_item(u, f, "var-tmp-dir", rt->var_tmp_dir); if (rt->netns_storage_socket[0] >= 0) { int copy; copy = fdset_put_dup(fds, rt->netns_storage_socket[0]); if (copy < 0) return copy; unit_serialize_item_format(u, f, "netns-socket-0", "%i", copy); } if (rt->netns_storage_socket[1] >= 0) { int copy; copy = fdset_put_dup(fds, rt->netns_storage_socket[1]); if (copy < 0) return copy; unit_serialize_item_format(u, f, "netns-socket-1", "%i", copy); } return 0; } int exec_runtime_deserialize_item(Unit *u, ExecRuntime **rt, const char *key, const char *value, FDSet *fds) { int r; assert(rt); assert(key); assert(value); if (streq(key, "tmp-dir")) { char *copy; r = exec_runtime_allocate(rt); if (r < 0) return log_oom(); copy = strdup(value); if (!copy) return log_oom(); free((*rt)->tmp_dir); (*rt)->tmp_dir = copy; } else if (streq(key, "var-tmp-dir")) { char *copy; r = exec_runtime_allocate(rt); if (r < 0) return log_oom(); copy = strdup(value); if (!copy) return log_oom(); free((*rt)->var_tmp_dir); (*rt)->var_tmp_dir = copy; } else if (streq(key, "netns-socket-0")) { int fd; r = exec_runtime_allocate(rt); if (r < 0) return log_oom(); if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse netns socket value: %s", value); else { safe_close((*rt)->netns_storage_socket[0]); (*rt)->netns_storage_socket[0] = fdset_remove(fds, fd); } } else if (streq(key, "netns-socket-1")) { int fd; r = exec_runtime_allocate(rt); if (r < 0) return log_oom(); if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd)) log_unit_debug(u, "Failed to parse netns socket value: %s", value); else { safe_close((*rt)->netns_storage_socket[1]); (*rt)->netns_storage_socket[1] = fdset_remove(fds, fd); } } else return 0; return 1; } static void *remove_tmpdir_thread(void *p) { _cleanup_free_ char *path = p; (void) rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL); return NULL; } void exec_runtime_destroy(ExecRuntime *rt) { int r; if (!rt) return; /* If there are multiple users of this, let's leave the stuff around */ if (rt->n_ref > 1) return; if (rt->tmp_dir) { log_debug("Spawning thread to nuke %s", rt->tmp_dir); r = asynchronous_job(remove_tmpdir_thread, rt->tmp_dir); if (r < 0) { log_warning_errno(r, "Failed to nuke %s: %m", rt->tmp_dir); free(rt->tmp_dir); } rt->tmp_dir = NULL; } if (rt->var_tmp_dir) { log_debug("Spawning thread to nuke %s", rt->var_tmp_dir); r = asynchronous_job(remove_tmpdir_thread, rt->var_tmp_dir); if (r < 0) { log_warning_errno(r, "Failed to nuke %s: %m", rt->var_tmp_dir); free(rt->var_tmp_dir); } rt->var_tmp_dir = NULL; } safe_close_pair(rt->netns_storage_socket); } static const char* const exec_input_table[_EXEC_INPUT_MAX] = { [EXEC_INPUT_NULL] = "null", [EXEC_INPUT_TTY] = "tty", [EXEC_INPUT_TTY_FORCE] = "tty-force", [EXEC_INPUT_TTY_FAIL] = "tty-fail", [EXEC_INPUT_SOCKET] = "socket", [EXEC_INPUT_NAMED_FD] = "fd", }; DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput); static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = { [EXEC_OUTPUT_INHERIT] = "inherit", [EXEC_OUTPUT_NULL] = "null", [EXEC_OUTPUT_TTY] = "tty", [EXEC_OUTPUT_SYSLOG] = "syslog", [EXEC_OUTPUT_SYSLOG_AND_CONSOLE] = "syslog+console", [EXEC_OUTPUT_KMSG] = "kmsg", [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console", [EXEC_OUTPUT_JOURNAL] = "journal", [EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console", [EXEC_OUTPUT_SOCKET] = "socket", [EXEC_OUTPUT_NAMED_FD] = "fd", }; DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput); static const char* const exec_utmp_mode_table[_EXEC_UTMP_MODE_MAX] = { [EXEC_UTMP_INIT] = "init", [EXEC_UTMP_LOGIN] = "login", [EXEC_UTMP_USER] = "user", }; DEFINE_STRING_TABLE_LOOKUP(exec_utmp_mode, ExecUtmpMode);