#include <linux/fs.h>
#include <linux/oom.h>
#include <sys/poll.h>
+#include <glob.h>
+#include <sys/personality.h>
+#include <libgen.h>
+#undef basename
#ifdef HAVE_PAM
#include <security/pam_appl.h>
#endif
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#ifdef HAVE_SECCOMP
+#include <seccomp.h>
+#endif
+
+#ifdef HAVE_APPARMOR
+#include <sys/apparmor.h>
+#endif
+
#include "execute.h"
#include "strv.h"
#include "macro.h"
#include "capability.h"
#include "util.h"
#include "log.h"
+#include "sd-messages.h"
#include "ioprio.h"
#include "securebits.h"
-#include "cgroup.h"
#include "namespace.h"
-#include "tcpwrap.h"
#include "exit-status.h"
#include "missing.h"
#include "utmp-wtmp.h"
#include "def.h"
-#include "loopback-setup.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 "apparmor-util.h"
+
+#ifdef HAVE_SECCOMP
+#include "seccomp-util.h"
+#endif
+
+#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 ((nfd = fcntl(fds[i], F_DUPFD, i+3)) < 0)
return -errno;
- close_nointr_nofail(fds[i]);
+ safe_close(fds[i]);
fds[i] = nfd;
/* Hmm, the fd we wanted isn't free? Then
return 0;
}
-static const char *tty_path(const ExecContext *context) {
+_pure_ static const char *tty_path(const ExecContext *context) {
assert(context);
if (context->tty_path)
return "/dev/console";
}
-void exec_context_tty_reset(const ExecContext *context) {
+static void exec_context_tty_reset(const ExecContext *context) {
assert(context);
if (context->tty_vhangup)
vt_disallocate(context->tty_path);
}
+static bool is_terminal_output(ExecOutput o) {
+ return
+ o == EXEC_OUTPUT_TTY ||
+ o == EXEC_OUTPUT_SYSLOG_AND_CONSOLE ||
+ o == EXEC_OUTPUT_KMSG_AND_CONSOLE ||
+ o == EXEC_OUTPUT_JOURNAL_AND_CONSOLE;
+}
+
static int open_null_as(int flags, int nfd) {
int fd, r;
assert(nfd >= 0);
- if ((fd = open("/dev/null", flags|O_NOCTTY)) < 0)
+ fd = open("/dev/null", flags|O_NOCTTY);
+ if (fd < 0)
return -errno;
if (fd != nfd) {
r = dup2(fd, nfd) < 0 ? -errno : nfd;
- close_nointr_nofail(fd);
+ safe_close(fd);
} else
r = nfd;
return r;
}
-static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, int nfd) {
+static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, const char *unit_id, int nfd) {
int fd, r;
- union sockaddr_union sa;
+ union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ .un.sun_path = "/run/systemd/journal/stdout",
+ };
assert(context);
assert(output < _EXEC_OUTPUT_MAX);
if (fd < 0)
return -errno;
- zero(sa);
- sa.un.sun_family = AF_UNIX;
- strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path));
-
r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
if (r < 0) {
- close_nointr_nofail(fd);
+ safe_close(fd);
return -errno;
}
if (shutdown(fd, SHUT_RD) < 0) {
- close_nointr_nofail(fd);
+ safe_close(fd);
return -errno;
}
+ fd_inc_sndbuf(fd, SNDBUF_SIZE);
+
dprintf(fd,
+ "%s\n"
"%s\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,
- output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || output == EXEC_OUTPUT_KMSG_AND_CONSOLE || output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE);
+ is_terminal_output(output));
if (fd != nfd) {
r = dup2(fd, nfd) < 0 ? -errno : nfd;
- close_nointr_nofail(fd);
+ safe_close(fd);
} else
r = nfd;
if (fd != nfd) {
r = dup2(fd, nfd) < 0 ? -errno : nfd;
- close_nointr_nofail(fd);
+ safe_close(fd);
} else
r = nfd;
case EXEC_INPUT_TTY_FAIL: {
int fd, r;
- if ((fd = acquire_terminal(
- tty_path(context),
- i == EXEC_INPUT_TTY_FAIL,
- i == EXEC_INPUT_TTY_FORCE,
- false)) < 0)
+ fd = acquire_terminal(tty_path(context),
+ i == EXEC_INPUT_TTY_FAIL,
+ i == EXEC_INPUT_TTY_FORCE,
+ false,
+ (usec_t) -1);
+ if (fd < 0)
return fd;
if (fd != STDIN_FILENO) {
r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO;
- close_nointr_nofail(fd);
+ safe_close(fd);
} else
r = STDIN_FILENO;
}
}
-static int setup_output(const ExecContext *context, int socket_fd, const char *ident, bool apply_tty_stdin) {
+static int setup_output(const ExecContext *context, int fileno, int socket_fd, const char *ident, const char *unit_id, bool apply_tty_stdin) {
ExecOutput o;
ExecInput i;
+ int r;
assert(context);
assert(ident);
i = fixup_input(context->std_input, socket_fd, apply_tty_stdin);
o = fixup_output(context->std_output, socket_fd);
- /* This expects the input is already set up */
+ if (fileno == STDERR_FILENO) {
+ ExecOutput e;
+ e = fixup_output(context->std_error, socket_fd);
- switch (o) {
+ /* 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;
- case EXEC_OUTPUT_INHERIT:
+ /* Duplicate from stdout if possible */
+ if (e == o || 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(tty_path(context), O_WRONLY, STDOUT_FILENO);
+ return open_terminal_as(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, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO;
+ 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 STDOUT_FILENO;
-
- /* We need to open /dev/null here anew, to get the
- * right access mode. So we fall through */
-
- case EXEC_OUTPUT_NULL:
- return open_null_as(O_WRONLY, STDOUT_FILENO);
-
- case EXEC_OUTPUT_TTY:
- if (is_terminal_input(i))
- return dup2(STDIN_FILENO, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO;
-
- /* We don't reset the terminal if this is just about output */
- return open_terminal_as(tty_path(context), O_WRONLY, STDOUT_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:
- return connect_logger_as(context, o, ident, STDOUT_FILENO);
+ return fileno;
- case EXEC_OUTPUT_SOCKET:
- assert(socket_fd >= 0);
- return dup2(socket_fd, STDOUT_FILENO) < 0 ? -errno : STDOUT_FILENO;
-
- default:
- assert_not_reached("Unknown output type");
+ /* We need to open /dev/null here anew, to get the right access mode. */
+ return open_null_as(O_WRONLY, fileno);
}
-}
-
-static int setup_error(const ExecContext *context, int socket_fd, const char *ident, bool apply_tty_stdin) {
- ExecOutput o, e;
- ExecInput i;
-
- assert(context);
- assert(ident);
-
- i = fixup_input(context->std_input, socket_fd, apply_tty_stdin);
- o = fixup_output(context->std_output, socket_fd);
- 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 STDERR_FILENO;
-
- /* Duplicate from stdout if possible */
- if (e == o || e == EXEC_OUTPUT_INHERIT)
- return dup2(STDOUT_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO;
- switch (e) {
+ switch (o) {
case EXEC_OUTPUT_NULL:
- return open_null_as(O_WRONLY, STDERR_FILENO);
+ return open_null_as(O_WRONLY, fileno);
case EXEC_OUTPUT_TTY:
if (is_terminal_input(i))
- return dup2(STDIN_FILENO, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO;
+ return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno;
/* We don't reset the terminal if this is just about output */
- return open_terminal_as(tty_path(context), O_WRONLY, STDERR_FILENO);
+ return open_terminal_as(tty_path(context), O_WRONLY, fileno);
case EXEC_OUTPUT_SYSLOG:
case EXEC_OUTPUT_SYSLOG_AND_CONSOLE:
case EXEC_OUTPUT_KMSG_AND_CONSOLE:
case EXEC_OUTPUT_JOURNAL:
case EXEC_OUTPUT_JOURNAL_AND_CONSOLE:
- return connect_logger_as(context, e, ident, STDERR_FILENO);
+ r = connect_logger_as(context, o, ident, unit_id, fileno);
+ if (r < 0) {
+ log_struct_unit(LOG_CRIT, unit_id,
+ "MESSAGE=Failed to connect std%s of %s to the journal socket: %s",
+ fileno == STDOUT_FILENO ? "out" : "err",
+ unit_id, strerror(-r),
+ "ERRNO=%d", -r,
+ NULL);
+ r = open_null_as(O_WRONLY, fileno);
+ }
+ return r;
case EXEC_OUTPUT_SOCKET:
assert(socket_fd >= 0);
- return dup2(socket_fd, STDERR_FILENO) < 0 ? -errno : STDERR_FILENO;
+ return dup2(socket_fd, fileno) < 0 ? -errno : fileno;
default:
assert_not_reached("Unknown error type");
return 0;
}
-static int setup_confirm_stdio(const ExecContext *context,
- int *_saved_stdin,
+static int setup_confirm_stdio(int *_saved_stdin,
int *_saved_stdout) {
int fd = -1, saved_stdin, saved_stdout = -1, r;
- assert(context);
assert(_saved_stdin);
assert(_saved_stdout);
- /* This returns positive EXIT_xxx return values instead of
- * negative errno style values! */
-
- if ((saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3)) < 0)
- return EXIT_STDIN;
+ saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3);
+ if (saved_stdin < 0)
+ return -errno;
- if ((saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3)) < 0) {
- r = EXIT_STDOUT;
+ saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3);
+ if (saved_stdout < 0) {
+ r = errno;
goto fail;
}
- if ((fd = acquire_terminal(
- tty_path(context),
- context->std_input == EXEC_INPUT_TTY_FAIL,
- context->std_input == EXEC_INPUT_TTY_FORCE,
- false)) < 0) {
- r = EXIT_STDIN;
+ fd = acquire_terminal(
+ "/dev/console",
+ false,
+ false,
+ false,
+ DEFAULT_CONFIRM_USEC);
+ if (fd < 0) {
+ r = fd;
goto fail;
}
- if (chown_terminal(fd, getuid()) < 0) {
- r = EXIT_STDIN;
+ r = chown_terminal(fd, getuid());
+ if (r < 0)
goto fail;
- }
if (dup2(fd, STDIN_FILENO) < 0) {
- r = EXIT_STDIN;
+ r = -errno;
goto fail;
}
if (dup2(fd, STDOUT_FILENO) < 0) {
- r = EXIT_STDOUT;
+ r = -errno;
goto fail;
}
if (fd >= 2)
- close_nointr_nofail(fd);
+ safe_close(fd);
*_saved_stdin = saved_stdin;
*_saved_stdout = saved_stdout;
return 0;
fail:
- if (saved_stdout >= 0)
- close_nointr_nofail(saved_stdout);
+ safe_close(saved_stdout);
+ safe_close(saved_stdin);
+ safe_close(fd);
+
+ return r;
+}
- if (saved_stdin >= 0)
- close_nointr_nofail(saved_stdin);
+_printf_(1, 2) static int write_confirm_message(const char *format, ...) {
+ _cleanup_close_ int fd = -1;
+ va_list ap;
- if (fd >= 0)
- close_nointr_nofail(fd);
+ assert(format);
- return r;
+ fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ va_start(ap, format);
+ vdprintf(fd, format, ap);
+ va_end(ap);
+
+ return 0;
}
-static int restore_confirm_stdio(const ExecContext *context,
- int *saved_stdin,
- int *saved_stdout,
- bool *keep_stdin,
- bool *keep_stdout) {
+static int restore_confirm_stdio(int *saved_stdin,
+ int *saved_stdout) {
+
+ int r = 0;
- assert(context);
assert(saved_stdin);
- assert(*saved_stdin >= 0);
assert(saved_stdout);
- assert(*saved_stdout >= 0);
- /* This returns positive EXIT_xxx return values instead of
- * negative errno style values! */
+ release_terminal();
- if (is_terminal_input(context->std_input)) {
+ if (*saved_stdin >= 0)
+ if (dup2(*saved_stdin, STDIN_FILENO) < 0)
+ r = -errno;
- /* The service wants terminal input. */
+ if (*saved_stdout >= 0)
+ if (dup2(*saved_stdout, STDOUT_FILENO) < 0)
+ r = -errno;
- *keep_stdin = true;
- *keep_stdout =
- context->std_output == EXEC_OUTPUT_INHERIT ||
- context->std_output == EXEC_OUTPUT_TTY;
+ safe_close(*saved_stdin);
+ safe_close(*saved_stdout);
- } else {
- /* If the service doesn't want a controlling terminal,
- * then we need to get rid entirely of what we have
- * already. */
+ return r;
+}
- if (release_terminal() < 0)
- return EXIT_STDIN;
+static int ask_for_confirmation(char *response, char **argv) {
+ int saved_stdout = -1, saved_stdin = -1, r;
+ char *line;
- if (dup2(*saved_stdin, STDIN_FILENO) < 0)
- return EXIT_STDIN;
+ r = setup_confirm_stdio(&saved_stdin, &saved_stdout);
+ if (r < 0)
+ return r;
- if (dup2(*saved_stdout, STDOUT_FILENO) < 0)
- return EXIT_STDOUT;
+ line = exec_command_line(argv);
+ if (!line)
+ return -ENOMEM;
- *keep_stdout = *keep_stdin = false;
- }
+ r = ask(response, "yns", "Execute %s? [Yes, No, Skip] ", line);
+ free(line);
- return 0;
+ restore_confirm_stdio(&saved_stdin, &saved_stdout);
+
+ return r;
}
static int enforce_groups(const ExecContext *context, const char *username, gid_t gid) {
}
static int enforce_user(const ExecContext *context, uid_t uid) {
- int r;
assert(context);
/* Sets (but doesn't lookup) the uid and make sure we keep the
* capabilities while doing so. */
if (context->capabilities) {
- cap_t d;
+ _cleanup_cap_free_ cap_t d = NULL;
static const cap_value_t bits[] = {
CAP_SETUID, /* Necessary so that we can run setresuid() below */
CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */
/* First step: If we need to keep capabilities but
* drop privileges we need to make sure we keep our
- * caps, whiel we drop privileges. */
+ * caps, while we drop privileges. */
if (uid != 0) {
- int sb = context->secure_bits|SECURE_KEEP_CAPS;
+ int sb = context->secure_bits | 1<<SECURE_KEEP_CAPS;
if (prctl(PR_GET_SECUREBITS) != sb)
if (prctl(PR_SET_SECUREBITS, sb) < 0)
/* Second step: set the capabilities. This will reduce
* the capabilities to the minimum we need. */
- if (!(d = cap_dup(context->capabilities)))
+ d = cap_dup(context->capabilities);
+ if (!d)
return -errno;
if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 ||
- cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) {
- r = -errno;
- cap_free(d);
- return r;
- }
-
- if (cap_set_proc(d) < 0) {
- r = -errno;
- cap_free(d);
- return r;
- }
+ cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0)
+ return -errno;
- cap_free(d);
+ if (cap_set_proc(d) < 0)
+ return -errno;
}
/* Third step: actually set the uids */
static int setup_pam(
const char *name,
const char *user,
+ uid_t uid,
const char *tty,
char ***pam_env,
int fds[], unsigned n_fds) {
char **e = NULL;
bool close_session = false;
pid_t pam_pid = 0, parent_pid;
+ int flags = 0;
assert(name);
assert(user);
* daemon. We do things this way to ensure that the main PID
* of the daemon is the one we initially fork()ed. */
- if ((pam_code = pam_start(name, user, &conv, &handle)) != PAM_SUCCESS) {
+ if (log_get_max_level() < LOG_PRI(LOG_DEBUG))
+ flags |= PAM_SILENT;
+
+ pam_code = pam_start(name, user, &conv, &handle);
+ if (pam_code != PAM_SUCCESS) {
handle = NULL;
goto fail;
}
- if (tty)
- if ((pam_code = pam_set_item(handle, PAM_TTY, tty)) != PAM_SUCCESS)
+ if (tty) {
+ pam_code = pam_set_item(handle, PAM_TTY, tty);
+ if (pam_code != PAM_SUCCESS)
goto fail;
+ }
- if ((pam_code = pam_acct_mgmt(handle, PAM_SILENT)) != PAM_SUCCESS)
+ pam_code = pam_acct_mgmt(handle, flags);
+ if (pam_code != PAM_SUCCESS)
goto fail;
- if ((pam_code = pam_open_session(handle, PAM_SILENT)) != PAM_SUCCESS)
+ pam_code = pam_open_session(handle, flags);
+ if (pam_code != PAM_SUCCESS)
goto fail;
close_session = true;
- if ((!(e = pam_getenvlist(handle)))) {
+ e = pam_getenvlist(handle);
+ if (!e) {
pam_code = PAM_BUF_ERR;
goto fail;
}
parent_pid = getpid();
- if ((pam_pid = fork()) < 0)
+ pam_pid = fork();
+ if (pam_pid < 0)
goto fail;
if (pam_pid == 0) {
open here that have been opened by PAM. */
close_many(fds, n_fds);
- /* Wait until our parent died. This will most likely
- * not work since the kernel does not allow
- * unprivileged parents kill their privileged children
- * this way. We rely on the control groups kill logic
+ /* 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 */
+ if (setresuid(uid, uid, uid) < 0)
+ log_error("Error: Failed to setresuid() in sd-pam: %s", strerror(-r));
+
+ /* 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;
}
/* If our parent died we'll end the session */
- if (getppid() != parent_pid)
- if ((pam_code = pam_close_session(handle, PAM_DATA_SILENT)) != PAM_SUCCESS)
+ if (getppid() != parent_pid) {
+ pam_code = pam_close_session(handle, flags);
+ if (pam_code != PAM_SUCCESS)
goto child_finish;
+ }
r = 0;
child_finish:
- pam_end(handle, pam_code | PAM_DATA_SILENT);
+ pam_end(handle, pam_code | flags);
_exit(r);
}
return 0;
fail:
- if (pam_code != PAM_SUCCESS)
+ if (pam_code != PAM_SUCCESS) {
+ log_error("PAM failed: %s", pam_strerror(handle, pam_code));
err = -EPERM; /* PAM errors do not map to errno */
- else
+ } else {
+ log_error("PAM failed: %m");
err = -errno;
+ }
if (handle) {
if (close_session)
- pam_code = pam_close_session(handle, PAM_DATA_SILENT);
+ pam_code = pam_close_session(handle, flags);
- pam_end(handle, pam_code | PAM_DATA_SILENT);
+ pam_end(handle, pam_code | flags);
}
strv_free(e);
}
#endif
-static int do_capability_bounding_set_drop(uint64_t drop) {
- unsigned long i;
- cap_t old_cap = NULL, new_cap = NULL;
- cap_flag_value_t fv;
- int r;
-
- /* If we are run as PID 1 we will lack CAP_SETPCAP by default
- * in the effective set (yes, the kernel drops that when
- * executing init!), so get it back temporarily so that we can
- * call PR_CAPBSET_DROP. */
-
- old_cap = cap_get_proc();
- if (!old_cap)
- return -errno;
-
- if (cap_get_flag(old_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) {
- r = -errno;
- goto finish;
- }
-
- if (fv != CAP_SET) {
- static const cap_value_t v = CAP_SETPCAP;
-
- new_cap = cap_dup(old_cap);
- if (!new_cap) {
- r = -errno;
- goto finish;
- }
-
- if (cap_set_flag(new_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) {
- r = -errno;
- goto finish;
- }
-
- if (cap_set_proc(new_cap) < 0) {
- r = -errno;
- goto finish;
- }
- }
-
- for (i = 0; i <= cap_last_cap(); i++)
- if (drop & ((uint64_t) 1ULL << (uint64_t) i)) {
- if (prctl(PR_CAPBSET_DROP, i) < 0) {
- r = -errno;
- goto finish;
- }
- }
-
- r = 0;
-
-finish:
- if (new_cap)
- cap_free(new_cap);
-
- if (old_cap) {
- cap_set_proc(old_cap);
- cap_free(old_cap);
- }
-
- return r;
-}
-
static void rename_process_from_path(const char *path) {
char process_name[11];
const char *p;
/* This resulting string must fit in 10 chars (i.e. the length
* of "/sbin/init") to look pretty in /bin/ps */
- p = file_name_from_path(path);
+ p = basename(path);
if (isempty(p)) {
rename_process("(...)");
return;
rename_process(process_name);
}
-int exec_spawn(ExecCommand *command,
- char **argv,
- const ExecContext *context,
- int fds[], unsigned n_fds,
- char **environment,
- bool apply_permissions,
- bool apply_chroot,
- bool apply_tty_stdin,
- bool confirm_spawn,
- CGroupBonding *cgroup_bondings,
- CGroupAttribute *cgroup_attributes,
- const char *cgroup_suffix,
- int idle_pipe[2],
- pid_t *ret) {
+#ifdef HAVE_SECCOMP
- pid_t pid;
+static int apply_seccomp(ExecContext *c) {
+ uint32_t negative_action, action;
+ scmp_filter_ctx *seccomp;
+ Iterator i;
+ void *id;
int r;
- char *line;
- int socket_fd;
- char **files_env = NULL;
- assert(command);
- assert(context);
- assert(ret);
- assert(fds || n_fds <= 0);
+ assert(c);
- if (context->std_input == EXEC_INPUT_SOCKET ||
- context->std_output == EXEC_OUTPUT_SOCKET ||
- context->std_error == EXEC_OUTPUT_SOCKET) {
+ negative_action = c->syscall_errno == 0 ? SCMP_ACT_KILL : SCMP_ACT_ERRNO(c->syscall_errno);
- if (n_fds != 1)
- return -EINVAL;
+ seccomp = seccomp_init(c->syscall_whitelist ? negative_action : SCMP_ACT_ALLOW);
+ if (!seccomp)
+ return -ENOMEM;
- socket_fd = fds[0];
+ if (c->syscall_archs) {
- fds = NULL;
- n_fds = 0;
- } else
- socket_fd = -1;
+ 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;
+ }
- if ((r = exec_context_load_environment(context, &files_env)) < 0) {
- log_error("Failed to load environment files: %s", strerror(-r));
- return r;
+ } else {
+ r = seccomp_add_secondary_archs(seccomp);
+ if (r < 0)
+ goto finish;
}
- if (!argv)
- argv = command->argv;
-
- if (!(line = exec_command_line(argv))) {
- r = -ENOMEM;
- goto fail_parent;
+ 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;
}
- log_debug("About to execute: %s", line);
- free(line);
-
- r = cgroup_bonding_realize_list(cgroup_bondings);
+ r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0);
if (r < 0)
- goto fail_parent;
-
- cgroup_attribute_apply_list(cgroup_attributes, cgroup_bondings);
+ goto finish;
- if ((pid = fork()) < 0) {
- r = -errno;
- goto fail_parent;
- }
+ r = seccomp_load(seccomp);
- if (pid == 0) {
- int i, err;
- sigset_t ss;
- const char *username = NULL, *home = NULL;
- uid_t uid = (uid_t) -1;
- gid_t gid = (gid_t) -1;
- char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL;
- unsigned n_env = 0;
- int saved_stdout = -1, saved_stdin = -1;
- bool keep_stdout = false, keep_stdin = false, set_access = false;
+finish:
+ seccomp_release(seccomp);
+ return r;
+}
- /* child */
+static int apply_address_families(ExecContext *c) {
+ scmp_filter_ctx *seccomp;
+ Iterator i;
+ int r;
- rename_process_from_path(command->path);
+ assert(c);
- /* 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. */
- default_signals(SIGNALS_CRASH_HANDLER,
- SIGNALS_IGNORE, -1);
+ seccomp = seccomp_init(SCMP_ACT_ALLOW);
+ if (!seccomp)
+ return -ENOMEM;
- if (context->ignore_sigpipe)
- ignore_signals(SIGPIPE, -1);
+ r = seccomp_add_secondary_archs(seccomp);
+ if (r < 0)
+ goto finish;
- assert_se(sigemptyset(&ss) == 0);
- if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) {
- err = -errno;
- r = EXIT_SIGNAL_MASK;
- goto fail_child;
- }
+ if (c->address_families_whitelist) {
+ int af, first = 0, last = 0;
+ void *afp;
- if (idle_pipe) {
- if (idle_pipe[1] >= 0)
- close_nointr_nofail(idle_pipe[1]);
- if (idle_pipe[0] >= 0) {
- fd_wait_for_event(idle_pipe[0], POLLHUP, DEFAULT_TIMEOUT_USEC);
- close_nointr_nofail(idle_pipe[0]);
- }
- }
+ /* 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. */
- /* Close sockets very early to make sure we don't
- * block init reexecution because it cannot bind its
- * sockets */
- log_forget_fds();
- err = close_all_fds(socket_fd >= 0 ? &socket_fd : fds,
- socket_fd >= 0 ? 1 : n_fds);
- if (err < 0) {
- r = EXIT_FDS;
- goto fail_child;
- }
+ SET_FOREACH(afp, c->address_families, i) {
+ af = PTR_TO_INT(afp);
- if (!context->same_pgrp)
- if (setsid() < 0) {
- err = -errno;
- r = EXIT_SETSID;
- goto fail_child;
- }
+ if (af <= 0 || af >= af_max())
+ continue;
- if (context->tcpwrap_name) {
- if (socket_fd >= 0)
- if (!socket_tcpwrap(socket_fd, context->tcpwrap_name)) {
- err = -EACCES;
- r = EXIT_TCPWRAP;
- goto fail_child;
- }
+ if (first == 0 || af < first)
+ first = af;
- for (i = 0; i < (int) n_fds; i++) {
- if (!socket_tcpwrap(fds[i], context->tcpwrap_name)) {
- err = -EACCES;
- r = EXIT_TCPWRAP;
- goto fail_child;
- }
- }
+ if (last == 0 || af > last)
+ last = af;
}
- exec_context_tty_reset(context);
+ assert((first == 0) == (last == 0));
- /* We skip the confirmation step if we shall not apply the TTY */
- if (confirm_spawn &&
- (!is_terminal_input(context->std_input) || apply_tty_stdin)) {
- char response;
+ if (first == 0) {
- /* Set up terminal for the question */
- if ((r = setup_confirm_stdio(context,
- &saved_stdin, &saved_stdout))) {
- err = -errno;
- goto fail_child;
- }
+ /* 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;
- /* Now ask the question. */
- if (!(line = exec_command_line(argv))) {
- err = -ENOMEM;
- r = EXIT_MEMORY;
- goto fail_child;
- }
+ } else {
- r = ask(&response, "yns", "Execute %s? [Yes, No, Skip] ", line);
- free(line);
+ /* 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;
- if (r < 0 || response == 'n') {
- err = -ECANCELED;
- r = EXIT_CONFIRM;
- goto fail_child;
- } else if (response == 's') {
- err = r = 0;
- goto fail_child;
+ /* 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_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0);
+ if (r < 0)
+ goto finish;
+
+ r = seccomp_load(seccomp);
+
+finish:
+ seccomp_release(seccomp);
+ return r;
+}
+
+#endif
+
+static void do_idle_pipe_dance(int idle_pipe[4]) {
+ assert(idle_pipe);
+
+
+ safe_close(idle_pipe[1]);
+ 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 */) {
+ /* Signal systemd that we are bored and want to continue. */
+ write(idle_pipe[3], "x", 1);
+
+ /* Wait for systemd to react to the signal above. */
+ fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT2_USEC);
+ }
+
+ safe_close(idle_pipe[0]);
+
+ }
+
+ safe_close(idle_pipe[3]);
+}
+
+static int build_environment(
+ ExecContext *c,
+ unsigned n_fds,
+ usec_t watchdog_usec,
+ const char *home,
+ const char *username,
+ const char *shell,
+ char ***ret) {
+
+ _cleanup_strv_free_ char **our_env = NULL;
+ unsigned n_env = 0;
+ char *x;
+
+ assert(c);
+ assert(ret);
+
+ our_env = new0(char*, 10);
+ if (!our_env)
+ return -ENOMEM;
+
+ if (n_fds > 0) {
+ 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;
+ }
+
+ if (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, watchdog_usec) < 0)
+ 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 (is_terminal_input(c->std_input) ||
+ c->std_output == EXEC_OUTPUT_TTY ||
+ c->std_error == EXEC_OUTPUT_TTY ||
+ c->tty_path) {
+
+ x = strdup(default_term_for_tty(tty_path(c)));
+ if (!x)
+ return -ENOMEM;
+ our_env[n_env++] = x;
+ }
+
+ our_env[n_env++] = NULL;
+ assert(n_env <= 10);
+
+ *ret = our_env;
+ our_env = NULL;
+
+ return 0;
+}
+
+int exec_spawn(ExecCommand *command,
+ char **argv,
+ ExecContext *context,
+ int fds[], unsigned n_fds,
+ char **environment,
+ bool apply_permissions,
+ bool apply_chroot,
+ bool apply_tty_stdin,
+ bool confirm_spawn,
+ CGroupControllerMask cgroup_supported,
+ const char *cgroup_path,
+ const char *runtime_prefix,
+ const char *unit_id,
+ usec_t watchdog_usec,
+ int idle_pipe[4],
+ ExecRuntime *runtime,
+ pid_t *ret) {
+
+ _cleanup_strv_free_ char **files_env = NULL;
+ int socket_fd;
+ char *line;
+ pid_t pid;
+ int r;
+
+ assert(command);
+ assert(context);
+ assert(ret);
+ assert(fds || n_fds <= 0);
+
+ if (context->std_input == EXEC_INPUT_SOCKET ||
+ context->std_output == EXEC_OUTPUT_SOCKET ||
+ context->std_error == EXEC_OUTPUT_SOCKET) {
+
+ if (n_fds != 1)
+ return -EINVAL;
+
+ socket_fd = fds[0];
+
+ fds = NULL;
+ n_fds = 0;
+ } else
+ socket_fd = -1;
+
+ r = exec_context_load_environment(context, &files_env);
+ if (r < 0) {
+ log_struct_unit(LOG_ERR,
+ unit_id,
+ "MESSAGE=Failed to load environment files: %s", strerror(-r),
+ "ERRNO=%d", -r,
+ NULL);
+ return r;
+ }
+
+ if (!argv)
+ argv = command->argv;
+
+ line = exec_command_line(argv);
+ if (!line)
+ return log_oom();
+
+ log_struct_unit(LOG_DEBUG,
+ unit_id,
+ "EXECUTABLE=%s", command->path,
+ "MESSAGE=About to execute: %s", line,
+ NULL);
+ free(line);
- /* Release terminal for the question */
- if ((r = restore_confirm_stdio(context,
- &saved_stdin, &saved_stdout,
- &keep_stdin, &keep_stdout))) {
+ pid = fork();
+ if (pid < 0)
+ return -errno;
+
+ if (pid == 0) {
+ _cleanup_strv_free_ char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL;
+ const char *username = NULL, *home = NULL, *shell = NULL;
+ unsigned n_dont_close = 0;
+ int dont_close[n_fds + 3];
+ uid_t uid = (uid_t) -1;
+ gid_t gid = (gid_t) -1;
+ sigset_t ss;
+ int i, err;
+
+ /* child */
+
+ 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. */
+ default_signals(SIGNALS_CRASH_HANDLER,
+ SIGNALS_IGNORE, -1);
+
+ if (context->ignore_sigpipe)
+ ignore_signals(SIGPIPE, -1);
+
+ assert_se(sigemptyset(&ss) == 0);
+ if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) {
+ err = -errno;
+ r = EXIT_SIGNAL_MASK;
+ goto fail_child;
+ }
+
+ if (idle_pipe)
+ do_idle_pipe_dance(idle_pipe);
+
+ /* Close sockets very early to make sure we don't
+ * block init reexecution because it cannot bind its
+ * sockets */
+ 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 (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];
+ }
+
+ err = close_all_fds(dont_close, n_dont_close);
+ if (err < 0) {
+ r = EXIT_FDS;
+ goto fail_child;
+ }
+
+ if (!context->same_pgrp)
+ if (setsid() < 0) {
err = -errno;
+ r = EXIT_SETSID;
+ goto fail_child;
+ }
+
+ exec_context_tty_reset(context);
+
+ if (confirm_spawn) {
+ char response;
+
+ err = ask_for_confirmation(&response, argv);
+ if (err == -ETIMEDOUT)
+ write_confirm_message("Confirmation question timed out, assuming positive response.\n");
+ else if (err < 0)
+ write_confirm_message("Couldn't ask confirmation question, assuming positive response: %s\n", strerror(-err));
+ else if (response == 's') {
+ write_confirm_message("Skipping execution.\n");
+ err = -ECANCELED;
+ r = EXIT_CONFIRM;
+ goto fail_child;
+ } else if (response == 'n') {
+ write_confirm_message("Failing execution.\n");
+ err = r = 0;
goto fail_child;
}
}
if (socket_fd >= 0)
fd_nonblock(socket_fd, false);
- if (!keep_stdin) {
- err = setup_input(context, socket_fd, apply_tty_stdin);
- if (err < 0) {
- r = EXIT_STDIN;
- goto fail_child;
- }
+ err = setup_input(context, socket_fd, apply_tty_stdin);
+ if (err < 0) {
+ r = EXIT_STDIN;
+ goto fail_child;
}
- if (!keep_stdout) {
- err = setup_output(context, socket_fd, file_name_from_path(command->path), apply_tty_stdin);
- if (err < 0) {
- r = EXIT_STDOUT;
- goto fail_child;
- }
+ err = setup_output(context, STDOUT_FILENO, socket_fd, basename(command->path), unit_id, apply_tty_stdin);
+ if (err < 0) {
+ r = EXIT_STDOUT;
+ goto fail_child;
}
- err = setup_error(context, socket_fd, file_name_from_path(command->path), apply_tty_stdin);
+ err = setup_output(context, STDERR_FILENO, socket_fd, basename(command->path), unit_id, apply_tty_stdin);
if (err < 0) {
r = EXIT_STDERR;
goto fail_child;
}
- if (cgroup_bondings) {
- err = cgroup_bonding_install_list(cgroup_bondings, 0, cgroup_suffix);
+ if (cgroup_path) {
+ err = cg_attach_everywhere(cgroup_supported, cgroup_path, 0);
if (err < 0) {
r = EXIT_CGROUP;
goto fail_child;
snprintf(t, sizeof(t), "%i", context->oom_score_adjust);
char_array_0(t);
- if (write_one_line_file("/proc/self/oom_score_adj", t) < 0) {
- /* Compatibility with Linux <= 2.6.35 */
-
- int adj;
-
- adj = (context->oom_score_adjust * -OOM_DISABLE) / OOM_SCORE_ADJ_MAX;
- adj = CLAMP(adj, OOM_DISABLE, OOM_ADJUST_MAX);
-
- snprintf(t, sizeof(t), "%i", adj);
- char_array_0(t);
-
- if (write_one_line_file("/proc/self/oom_adj", t) < 0
- && errno != EACCES) {
- err = -errno;
- r = EXIT_OOM_ADJUST;
- goto fail_child;
- }
+ if (write_string_file("/proc/self/oom_score_adj", t) < 0) {
+ err = -errno;
+ r = EXIT_OOM_ADJUST;
+ goto fail_child;
}
}
}
if (context->cpu_sched_set) {
- struct sched_param param;
-
- zero(param);
- param.sched_priority = context->cpu_sched_priority;
-
- if (sched_setscheduler(0, context->cpu_sched_policy |
- (context->cpu_sched_reset_on_fork ? SCHED_RESET_ON_FORK : 0), ¶m) < 0) {
+ 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) {
err = -errno;
r = EXIT_SETSCHEDULER;
goto fail_child;
goto fail_child;
}
- if (context->timer_slack_nsec_set)
+ if (context->timer_slack_nsec != (nsec_t) -1)
if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) {
err = -errno;
r = EXIT_TIMERSLACK;
goto fail_child;
}
+ if (context->personality != 0xffffffffUL)
+ if (personality(context->personality) < 0) {
+ err = -errno;
+ r = EXIT_PERSONALITY;
+ goto fail_child;
+ }
+
if (context->utmp_id)
utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path);
if (context->user) {
username = context->user;
- err = get_user_creds(&username, &uid, &gid, &home);
+ err = get_user_creds(&username, &uid, &gid, &home, &shell);
if (err < 0) {
r = EXIT_USER;
goto fail_child;
goto fail_child;
}
}
+ }
- if (cgroup_bondings && context->control_group_modify) {
- err = cgroup_bonding_set_group_access_list(cgroup_bondings, 0755, uid, gid);
- if (err >= 0)
- err = cgroup_bonding_set_task_access_list(cgroup_bondings, 0644, uid, gid, context->control_group_persistent);
- if (err < 0) {
- r = EXIT_CGROUP;
- goto fail_child;
- }
-
- set_access = true;
+#ifdef HAVE_PAM
+ if (cgroup_path && context->user && context->pam_name) {
+ err = cg_set_task_access(SYSTEMD_CGROUP_CONTROLLER, cgroup_path, 0644, uid, gid);
+ if (err < 0) {
+ r = EXIT_CGROUP;
+ goto fail_child;
}
- }
- if (cgroup_bondings && !set_access && context->control_group_persistent >= 0) {
- err = cgroup_bonding_set_task_access_list(cgroup_bondings, (mode_t) -1, (uid_t) -1, (uid_t) -1, context->control_group_persistent);
+
+ err = cg_set_group_access(SYSTEMD_CGROUP_CONTROLLER, cgroup_path, 0755, uid, gid);
if (err < 0) {
r = EXIT_CGROUP;
goto fail_child;
}
}
+#endif
+
+ if (!strv_isempty(context->runtime_directory) && runtime_prefix) {
+ char **rt;
+
+ STRV_FOREACH(rt, context->runtime_directory) {
+ _cleanup_free_ char *p;
+
+ p = strjoin(runtime_prefix, "/", *rt, NULL);
+ if (!p) {
+ r = EXIT_RUNTIME_DIRECTORY;
+ err = -ENOMEM;
+ goto fail_child;
+ }
+
+ err = mkdir_safe(p, context->runtime_directory_mode, uid, gid);
+ if (err < 0) {
+ r = EXIT_RUNTIME_DIRECTORY;
+ goto fail_child;
+ }
+ }
+ }
if (apply_permissions) {
err = enforce_groups(context, username, gid);
umask(context->umask);
#ifdef HAVE_PAM
- if (context->pam_name && username) {
- err = setup_pam(context->pam_name, username, context->tty_path, &pam_env, fds, n_fds);
+ if (apply_permissions && context->pam_name && username) {
+ err = setup_pam(context->pam_name, username, uid, context->tty_path, &pam_env, fds, n_fds);
if (err < 0) {
r = EXIT_PAM;
goto fail_child;
}
}
#endif
- if (context->private_network) {
- if (unshare(CLONE_NEWNET) < 0) {
- err = -errno;
+ if (context->private_network && runtime && runtime->netns_storage_socket[0] >= 0) {
+ err = setup_netns(runtime->netns_storage_socket);
+ if (err < 0) {
r = EXIT_NETWORK;
goto fail_child;
}
-
- loopback_setup();
}
- if (strv_length(context->read_write_dirs) > 0 ||
- strv_length(context->read_only_dirs) > 0 ||
- strv_length(context->inaccessible_dirs) > 0 ||
- context->mount_flags != MS_SHARED ||
- context->private_tmp) {
- err = setup_namespace(context->read_write_dirs,
- context->read_only_dirs,
- context->inaccessible_dirs,
- context->private_tmp,
- context->mount_flags);
+ if (!strv_isempty(context->read_write_dirs) ||
+ !strv_isempty(context->read_only_dirs) ||
+ !strv_isempty(context->inaccessible_dirs) ||
+ context->mount_flags != 0 ||
+ (context->private_tmp && runtime && (runtime->tmp_dir || runtime->var_tmp_dir)) ||
+ context->private_devices ||
+ context->read_only_system ||
+ context->protected_home != PROTECTED_HOME_NO) {
+
+ char *tmp = NULL, *var = NULL;
+
+ /* 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 = strappenda(runtime->tmp_dir, "/tmp");
+ if (runtime->var_tmp_dir)
+ var = strappenda(runtime->var_tmp_dir, "/tmp");
+ }
+
+ err = setup_namespace(
+ context->read_write_dirs,
+ context->read_only_dirs,
+ context->inaccessible_dirs,
+ tmp,
+ var,
+ context->private_devices,
+ context->protected_home,
+ context->read_only_system,
+ context->mount_flags);
if (err < 0) {
r = EXIT_NAMESPACE;
goto fail_child;
goto fail_child;
}
} else {
-
- char *d;
+ _cleanup_free_ char *d = NULL;
if (asprintf(&d, "%s/%s",
context->root_directory ? context->root_directory : "",
if (chdir(d) < 0) {
err = -errno;
- free(d);
r = EXIT_CHDIR;
goto fail_child;
}
-
- free(d);
}
/* We repeat the fd closing here, to make sure that
if (apply_permissions) {
- for (i = 0; i < RLIMIT_NLIMITS; i++) {
+ for (i = 0; i < _RLIMIT_MAX; i++) {
if (!context->rlimit[i])
continue;
}
if (context->capability_bounding_set_drop) {
- err = do_capability_bounding_set_drop(context->capability_bounding_set_drop);
+ err = capability_bounding_set_drop(context->capability_bounding_set_drop, false);
if (err < 0) {
r = EXIT_CAPABILITIES;
goto fail_child;
r = EXIT_CAPABILITIES;
goto fail_child;
}
- }
- if (!(our_env = new0(char*, 7))) {
- err = -ENOMEM;
- r = EXIT_MEMORY;
- goto fail_child;
- }
+ if (context->no_new_privileges)
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
+ err = -errno;
+ r = EXIT_NO_NEW_PRIVILEGES;
+ goto fail_child;
+ }
- if (n_fds > 0)
- if (asprintf(our_env + n_env++, "LISTEN_PID=%lu", (unsigned long) getpid()) < 0 ||
- asprintf(our_env + n_env++, "LISTEN_FDS=%u", n_fds) < 0) {
- err = -ENOMEM;
- r = EXIT_MEMORY;
- goto fail_child;
+#ifdef HAVE_SECCOMP
+ if (context->address_families_whitelist ||
+ !set_isempty(context->address_families)) {
+ err = apply_address_families(context);
+ if (err < 0) {
+ r = EXIT_ADDRESS_FAMILIES;
+ goto fail_child;
+ }
}
- if (home)
- if (asprintf(our_env + n_env++, "HOME=%s", home) < 0) {
- err = -ENOMEM;
- r = EXIT_MEMORY;
- goto fail_child;
+ if (context->syscall_whitelist ||
+ !set_isempty(context->syscall_filter) ||
+ !set_isempty(context->syscall_archs)) {
+ err = apply_seccomp(context);
+ if (err < 0) {
+ r = EXIT_SECCOMP;
+ goto fail_child;
+ }
}
+#endif
- if (username)
- if (asprintf(our_env + n_env++, "LOGNAME=%s", username) < 0 ||
- asprintf(our_env + n_env++, "USER=%s", username) < 0) {
- err = -ENOMEM;
- r = EXIT_MEMORY;
- goto fail_child;
+#ifdef HAVE_SELINUX
+ if (context->selinux_context && use_selinux()) {
+ err = setexeccon(context->selinux_context);
+ if (err < 0 && !context->selinux_context_ignore) {
+ r = EXIT_SELINUX_CONTEXT;
+ goto fail_child;
+ }
}
+#endif
- if (is_terminal_input(context->std_input) ||
- context->std_output == EXEC_OUTPUT_TTY ||
- context->std_error == EXEC_OUTPUT_TTY)
- if (!(our_env[n_env++] = strdup(default_term_for_tty(tty_path(context))))) {
- err = -ENOMEM;
- r = EXIT_MEMORY;
- goto fail_child;
+#ifdef HAVE_APPARMOR
+ if (context->apparmor_profile && use_apparmor()) {
+ err = aa_change_onexec(context->apparmor_profile);
+ if (err < 0 && !context->apparmor_profile_ignore) {
+ r = EXIT_APPARMOR_PROFILE;
+ goto fail_child;
+ }
}
+#endif
+ }
- assert(n_env <= 7);
+ err = build_environment(context, n_fds, watchdog_usec, home, username, shell, &our_env);
+ if (r < 0) {
+ r = EXIT_MEMORY;
+ goto fail_child;
+ }
- if (!(final_env = strv_env_merge(
- 5,
- environment,
- our_env,
- context->environment,
- files_env,
- pam_env,
- NULL))) {
+ final_env = strv_env_merge(5,
+ environment,
+ our_env,
+ context->environment,
+ files_env,
+ pam_env,
+ NULL);
+ if (!final_env) {
err = -ENOMEM;
r = EXIT_MEMORY;
goto fail_child;
}
- if (!(final_argv = replace_env_argv(argv, final_env))) {
+ final_argv = replace_env_argv(argv, final_env);
+ if (!final_argv) {
err = -ENOMEM;
r = EXIT_MEMORY;
goto fail_child;
final_env = strv_env_clean(final_env);
+ if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
+ line = exec_command_line(final_argv);
+ if (line) {
+ log_open();
+ log_struct_unit(LOG_DEBUG,
+ unit_id,
+ "EXECUTABLE=%s", command->path,
+ "MESSAGE=Executing: %s", line,
+ NULL);
+ log_close();
+ free(line);
+ line = NULL;
+ }
+ }
execve(command->path, final_argv, final_env);
err = -errno;
r = EXIT_EXEC;
fail_child:
if (r != 0) {
log_open();
- log_warning("Failed at step %s spawning %s: %s",
- exit_status_to_string(r, EXIT_STATUS_SYSTEMD),
- command->path, strerror(-err));
+ log_struct(LOG_ERR, MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED),
+ "EXECUTABLE=%s", command->path,
+ "MESSAGE=Failed at step %s spawning %s: %s",
+ exit_status_to_string(r, EXIT_STATUS_SYSTEMD),
+ command->path, strerror(-err),
+ "ERRNO=%d", -err,
+ NULL);
+ log_close();
}
- strv_free(our_env);
- strv_free(final_env);
- strv_free(pam_env);
- strv_free(files_env);
- strv_free(final_argv);
-
- if (saved_stdin >= 0)
- close_nointr_nofail(saved_stdin);
-
- if (saved_stdout >= 0)
- close_nointr_nofail(saved_stdout);
-
_exit(r);
}
- strv_free(files_env);
+ log_struct_unit(LOG_DEBUG,
+ unit_id,
+ "MESSAGE=Forked %s as "PID_FMT,
+ command->path, pid,
+ NULL);
/* 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 (cgroup_bondings)
- cgroup_bonding_install_list(cgroup_bondings, pid, cgroup_suffix);
-
- log_debug("Forked %s as %lu", command->path, (unsigned long) pid);
+ if (cgroup_path)
+ cg_attach(SYSTEMD_CGROUP_CONTROLLER, cgroup_path, pid);
exec_status_start(&command->exec_status, pid);
*ret = pid;
return 0;
-
-fail_parent:
- strv_free(files_env);
-
- return r;
}
void exec_context_init(ExecContext *c) {
c->cpu_sched_policy = SCHED_OTHER;
c->syslog_priority = LOG_DAEMON|LOG_INFO;
c->syslog_level_prefix = true;
- c->mount_flags = MS_SHARED;
- c->kill_signal = SIGTERM;
- c->send_sigkill = true;
- c->control_group_persistent = -1;
c->ignore_sigpipe = true;
+ c->timer_slack_nsec = (nsec_t) -1;
+ c->personality = 0xffffffffUL;
+ c->runtime_directory_mode = 0755;
}
void exec_context_done(ExecContext *c) {
free(c->tty_path);
c->tty_path = NULL;
- free(c->tcpwrap_name);
- c->tcpwrap_name = NULL;
-
free(c->syslog_identifier);
c->syslog_identifier = NULL;
free(c->utmp_id);
c->utmp_id = NULL;
+
+ free(c->selinux_context);
+ c->selinux_context = NULL;
+
+ free(c->apparmor_profile);
+ c->apparmor_profile = NULL;
+
+ set_free(c->syscall_filter);
+ c->syscall_filter = NULL;
+
+ set_free(c->syscall_archs);
+ c->syscall_archs = NULL;
+
+ set_free(c->address_families);
+ c->address_families = NULL;
+
+ strv_free(c->runtime_directory);
+ c->runtime_directory = NULL;
+}
+
+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, NULL);
+ if (!p)
+ return -ENOMEM;
+
+ /* We execute this synchronously, since we need to be
+ * sure this is gone when we start the service
+ * next. */
+ rm_rf_dangerous(p, false, true, false);
+ }
+
+ return 0;
}
void exec_command_done(ExecCommand *c) {
ExecCommand *i;
while ((i = c)) {
- LIST_REMOVE(ExecCommand, command, c, i);
+ LIST_REMOVE(command, c, i);
exec_command_done(i);
free(i);
}
int k;
bool ignore = false;
char **p;
+ _cleanup_globfree_ glob_t pglob = {};
+ int count, n;
fn = *i;
}
if (!path_is_absolute(fn)) {
-
if (ignore)
continue;
return -EINVAL;
}
- if ((k = load_env_file(fn, &p)) < 0) {
-
+ /* Filename supports globbing, take all matching files */
+ errno = 0;
+ if (glob(fn, 0, NULL, &pglob) != 0) {
if (ignore)
continue;
strv_free(r);
- return k;
+ return errno ? -errno : -EINVAL;
}
+ count = pglob.gl_pathc;
+ if (count == 0) {
+ if (ignore)
+ continue;
- 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;
+ return -EINVAL;
+ }
+ for (n = 0; n < count; n++) {
+ k = load_env_file(pglob.gl_pathv[n], NULL, &p);
+ if (k < 0) {
+ if (ignore)
+ continue;
- r = m;
+ strv_free(r);
+ return k;
+ }
+ /* Log invalid environment variables with filename */
+ if (p)
+ p = strv_env_clean_log(p, pglob.gl_pathv[n]);
+
+ 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;
+ }
}
}
return 0;
}
+static bool tty_may_match_dev_console(const char *tty) {
+ char *active = NULL, *console;
+ bool b;
+
+ 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 */
+ b = streq(console, tty) || (streq(console, "tty0") && tty_is_vc(tty));
+ free(active);
+
+ return b;
+}
+
+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(tty_path(ec));
+}
+
static void strv_fprintf(FILE *f, char **l) {
char **g;
}
void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
- char ** e;
+ char **e;
unsigned i;
assert(c);
assert(f);
- if (!prefix)
- prefix = "";
+ prefix = strempty(prefix);
fprintf(f,
"%sUMask: %04o\n"
"%sRootDirectory: %s\n"
"%sNonBlocking: %s\n"
"%sPrivateTmp: %s\n"
- "%sControlGroupModify: %s\n"
- "%sControlGroupPersistent: %s\n"
- "%sPrivateNetwork: %s\n",
+ "%sPrivateNetwork: %s\n"
+ "%sPrivateDevices: %s\n"
+ "%sProtectedHome: %s\n"
+ "%sReadOnlySystem: %s\n"
+ "%sIgnoreSIGPIPE: %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->control_group_modify),
- prefix, yes_no(c->control_group_persistent),
- prefix, yes_no(c->private_network));
+ prefix, yes_no(c->private_network),
+ prefix, yes_no(c->private_devices),
+ prefix, protected_home_to_string(c->protected_home),
+ prefix, yes_no(c->read_only_system),
+ prefix, yes_no(c->ignore_sigpipe));
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);
- if (c->tcpwrap_name)
- fprintf(f,
- "%sTCPWrapName: %s\n",
- prefix, c->tcpwrap_name);
-
if (c->nice_set)
fprintf(f,
"%sNice: %i\n",
for (i = 0; i < RLIM_NLIMITS; i++)
if (c->rlimit[i])
- fprintf(f, "%s%s: %llu\n", prefix, rlimit_to_string(i), (unsigned long long) c->rlimit[i]->rlim_max);
+ fprintf(f, "%s%s: "RLIM_FMT"\n",
+ prefix, rlimit_to_string(i), c->rlimit[i]->rlim_max);
- if (c->ioprio_set)
+ 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, ioprio_class_to_string(IOPRIO_PRIO_CLASS(c->ioprio)),
+ prefix, strna(class_str),
prefix, (int) IOPRIO_PRIO_DATA(c->ioprio));
+ }
+
+ if (c->cpu_sched_set) {
+ _cleanup_free_ char *policy_str = NULL;
- if (c->cpu_sched_set)
+ sched_policy_to_string_alloc(c->cpu_sched_policy, &policy_str);
fprintf(f,
"%sCPUSchedulingPolicy: %s\n"
"%sCPUSchedulingPriority: %i\n"
"%sCPUSchedulingResetOnFork: %s\n",
- prefix, sched_policy_to_string(c->cpu_sched_policy),
+ 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, " %i", i);
+ fprintf(f, " %u", i);
fputs("\n", f);
}
- if (c->timer_slack_nsec_set)
- fprintf(f, "%sTimerSlackNSec: %lu\n", prefix, c->timer_slack_nsec);
+ if (c->timer_slack_nsec != (nsec_t) -1)
+ fprintf(f, "%sTimerSlackNSec: "NSEC_FMT "\n", prefix, c->timer_slack_nsec);
fprintf(f,
"%sStandardInput: %s\n"
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)
+ 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, log_facility_unshifted_to_string(c->syslog_priority >> 3),
- prefix, log_level_to_string(LOG_PRI(c->syslog_priority)));
+ prefix, strna(fac_str),
+ prefix, strna(lvl_str));
+ }
if (c->capabilities) {
- char *t;
- if ((t = cap_to_text(c->capabilities, NULL))) {
- fprintf(f, "%sCapabilities: %s\n",
- prefix, t);
- cap_free(t);
- }
+ _cleanup_cap_free_charp_ char *t;
+
+ t = cap_to_text(c->capabilities, NULL);
+ if (t)
+ fprintf(f, "%sCapabilities: %s\n", prefix, t);
}
if (c->secure_bits)
fprintf(f, "%sSecure Bits:%s%s%s%s%s%s\n",
prefix,
- (c->secure_bits & SECURE_KEEP_CAPS) ? " keep-caps" : "",
- (c->secure_bits & SECURE_KEEP_CAPS_LOCKED) ? " keep-caps-locked" : "",
- (c->secure_bits & SECURE_NO_SETUID_FIXUP) ? " no-setuid-fixup" : "",
- (c->secure_bits & SECURE_NO_SETUID_FIXUP_LOCKED) ? " no-setuid-fixup-locked" : "",
- (c->secure_bits & SECURE_NOROOT) ? " noroot" : "",
- (c->secure_bits & SECURE_NOROOT_LOCKED) ? "noroot-locked" : "");
+ (c->secure_bits & 1<<SECURE_KEEP_CAPS) ? " keep-caps" : "",
+ (c->secure_bits & 1<<SECURE_KEEP_CAPS_LOCKED) ? " keep-caps-locked" : "",
+ (c->secure_bits & 1<<SECURE_NO_SETUID_FIXUP) ? " no-setuid-fixup" : "",
+ (c->secure_bits & 1<<SECURE_NO_SETUID_FIXUP_LOCKED) ? " no-setuid-fixup-locked" : "",
+ (c->secure_bits & 1<<SECURE_NOROOT) ? " noroot" : "",
+ (c->secure_bits & 1<<SECURE_NOROOT_LOCKED) ? "noroot-locked" : "");
if (c->capability_bounding_set_drop) {
unsigned long l;
for (l = 0; l <= cap_last_cap(); l++)
if (!(c->capability_bounding_set_drop & ((uint64_t) 1ULL << (uint64_t) l))) {
- char *t;
+ _cleanup_cap_free_charp_ char *t;
- if ((t = cap_to_name(l))) {
+ t = cap_to_name(l);
+ if (t)
fprintf(f, " %s", t);
- cap_free(t);
- }
}
fputs("\n", f);
fputs("\n", f);
}
- fprintf(f,
- "%sKillMode: %s\n"
- "%sKillSignal: SIG%s\n"
- "%sSendSIGKILL: %s\n"
- "%sIgnoreSIGPIPE: %s\n",
- prefix, kill_mode_to_string(c->kill_mode),
- prefix, signal_to_string(c->kill_signal),
- prefix, yes_no(c->send_sigkill),
- prefix, yes_no(c->ignore_sigpipe));
-
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 != 0xffffffffUL)
+ 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 (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);
}
void exec_status_start(ExecStatus *s, pid_t pid) {
return;
fprintf(f,
- "%sPID: %lu\n",
- prefix, (unsigned long) s->pid);
+ "%sPID: "PID_FMT"\n",
+ prefix, s->pid);
if (s->start_timestamp.realtime > 0)
fprintf(f,
if (*l) {
/* It's kind of important, that we keep the order here */
- LIST_FIND_TAIL(ExecCommand, command, *l, end);
- LIST_INSERT_AFTER(ExecCommand, command, *l, end, e);
+ LIST_FIND_TAIL(command, *l, end);
+ LIST_INSERT_AFTER(command, *l, end, e);
} else
*l = e;
}
if (!l)
return -ENOMEM;
- if (!(p = strdup(path))) {
+ p = strdup(path);
+ if (!p) {
strv_free(l);
return -ENOMEM;
}
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, 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) {
+ free(r->tmp_dir);
+ free(r->var_tmp_dir);
+ safe_close_pair(r->netns_storage_socket);
+ free(r);
+ }
+
+ return NULL;
+}
+
+int exec_runtime_serialize(ExecRuntime *rt, Unit *u, 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(ExecRuntime **rt, Unit *u, 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 r;
+
+ 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 r;
+
+ 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 r;
+
+ if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd))
+ log_debug_unit(u->id, "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 r;
+
+ if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd))
+ log_debug_unit(u->id, "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;
+
+ rm_rf_dangerous(path, false, true, false);
+ 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("Failed to nuke %s: %s", rt->tmp_dir, strerror(-r));
+ 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("Failed to nuke %s: %s", rt->var_tmp_dir, strerror(-r));
+ 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",
};
DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);
-
-static const char* const kill_mode_table[_KILL_MODE_MAX] = {
- [KILL_CONTROL_GROUP] = "control-group",
- [KILL_PROCESS] = "process",
- [KILL_NONE] = "none"
-};
-
-DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode);
-
-static const char* const kill_who_table[_KILL_WHO_MAX] = {
- [KILL_MAIN] = "main",
- [KILL_CONTROL] = "control",
- [KILL_ALL] = "all"
-};
-
-DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);