#include <sys/mman.h>
#include <sys/personality.h>
#include <sys/prctl.h>
+#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <utmpx.h>
#include "exit-status.h"
#include "fd-util.h"
#include "fileio.h"
-#include "formats-util.h"
+#include "format-util.h"
#include "fs-util.h"
#include "glob-util.h"
#include "io-util.h"
return 0;
}
-static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) {
+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;
if (saved_stdout < 0)
return -errno;
- fd = acquire_terminal(
- "/dev/console",
- false,
- false,
- false,
- DEFAULT_CONFIRM_USEC);
+ fd = acquire_terminal(vc, false, false, false, DEFAULT_CONFIRM_USEC);
if (fd < 0)
return fd;
return 0;
}
-_printf_(1, 2) static int write_confirm_message(const char *format, ...) {
+static void write_confirm_error_fd(int err, int fd, const Unit *u) {
+ assert(err < 0);
+
+ if (err == -ETIMEDOUT)
+ dprintf(fd, "Confirmation question timed out for %s, assuming positive response.\n", u->id);
+ else {
+ errno = -err;
+ dprintf(fd, "Couldn't ask confirmation for %s: %m, assuming positive response.\n", u->id);
+ }
+}
+
+static void write_confirm_error(int err, const char *vc, const Unit *u) {
_cleanup_close_ int fd = -1;
- va_list ap;
- assert(format);
+ assert(vc);
- fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
+ fd = open_terminal(vc, O_WRONLY|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
- return fd;
-
- va_start(ap, format);
- vdprintf(fd, format, ap);
- va_end(ap);
+ return;
- return 0;
+ write_confirm_error_fd(err, fd, u);
}
static int restore_confirm_stdio(int *saved_stdin, int *saved_stdout) {
return r;
}
-static int ask_for_confirmation(char *response, char **argv) {
+enum {
+ CONFIRM_PRETEND_FAILURE = -1,
+ CONFIRM_PRETEND_SUCCESS = 0,
+ CONFIRM_EXECUTE = 1,
+};
+
+static int ask_for_confirmation(const char *vc, Unit *u, const char *cmdline) {
int saved_stdout = -1, saved_stdin = -1, r;
- _cleanup_free_ char *line = NULL;
+ _cleanup_free_ char *e = NULL;
+ char c;
- r = setup_confirm_stdio(&saved_stdin, &saved_stdout);
- if (r < 0)
- return r;
+ /* 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, u);
+ return CONFIRM_EXECUTE;
+ }
- line = exec_command_line(argv);
- if (!line)
- return -ENOMEM;
+ /* confirm_spawn might have been disabled while we were sleeping. */
+ if (manager_is_confirm_spawn_disabled(u->manager)) {
+ r = 1;
+ goto restore_stdio;
+ }
- r = ask_char(response, "yns", "Execute %s? [Yes, No, Skip] ", line);
+ e = ellipsize(cmdline, 60, 100);
+ if (!e) {
+ log_oom();
+ r = CONFIRM_EXECUTE;
+ goto restore_stdio;
+ }
- restore_confirm_stdio(&saved_stdin, &saved_stdout);
+ for (;;) {
+ r = ask_char(&c, "yfshiDjcn", "Execute %s? [y, f, s – h for help] ", e);
+ if (r < 0) {
+ write_confirm_error_fd(r, STDOUT_FILENO, u);
+ r = CONFIRM_EXECUTE;
+ goto restore_stdio;
+ }
+ switch (c) {
+ case 'c':
+ printf("Resuming normal execution.\n");
+ manager_disable_confirm_spawn();
+ r = 1;
+ break;
+ case 'D':
+ unit_dump(u, stdout, " ");
+ continue; /* ask again */
+ case 'f':
+ printf("Failing execution.\n");
+ r = CONFIRM_PRETEND_FAILURE;
+ break;
+ case 'h':
+ printf(" c - continue, proceed without asking anymore\n"
+ " D - dump, show the state of the unit\n"
+ " f - fail, don't execute the command and pretend it failed\n"
+ " h - help\n"
+ " i - info, show a short summary of the unit\n"
+ " j - jobs, show jobs that are in progress\n"
+ " s - skip, don't execute the command and pretend it succeeded\n"
+ " y - yes, execute the command\n");
+ continue; /* ask again */
+ case 'i':
+ printf(" Description: %s\n"
+ " Unit: %s\n"
+ " Command: %s\n",
+ u->id, u->description, cmdline);
+ continue; /* ask again */
+ case 'j':
+ manager_dump_jobs(u->manager, stdout, " ");
+ continue; /* ask again */
+ case 'n':
+ /* 'n' was removed in favor of 'f'. */
+ printf("Didn't understand 'n', did you mean 'f'?\n");
+ continue; /* ask again */
+ 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 enforce_groups(const ExecContext *context, const char *username, gid_t gid) {
- bool keep_groups = false;
+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(context);
+ assert(c);
+
+ /* 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 ?: "root";
+ 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;
- /* Lookup and set GID and supplementary group list. Here too
- * we avoid NSS lookups for gid=0. */
+ assert(c);
- if (context->group || username) {
+ 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 (username && gid != 0) {
- if (initgroups(username, gid) < 0)
- return -errno;
+ if (initgroups(user, gid) < 0)
+ return -errno;
- keep_groups = true;
- }
+ keep_groups = true;
+ }
- /* Second step, set our gids */
- if (setresgid(gid, gid, gid) < 0)
+ 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 */
}
- if (context->supplementary_groups) {
- int ngroups_max, k;
- gid_t *gids;
- char **i;
+ l_gids = new(gid_t, ngroups_max);
+ if (!l_gids)
+ return -ENOMEM;
- /* Final step, initialize any manually set supplementary groups */
- assert_se((ngroups_max = (int) sysconf(_SC_NGROUPS_MAX)) > 0);
+ 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;
- if (!(gids = new(gid_t, ngroups_max)))
- return -ENOMEM;
+ STRV_FOREACH(i, c->supplementary_groups) {
+ const char *g;
- if (keep_groups) {
- k = getgroups(ngroups_max, gids);
- if (k < 0) {
- free(gids);
- return -errno;
- }
- } else
- k = 0;
+ if (k >= ngroups_max)
+ return -E2BIG;
- STRV_FOREACH(i, context->supplementary_groups) {
- const char *g;
+ g = *i;
+ r = get_group_creds(&g, l_gids+k);
+ if (r < 0)
+ return r;
- if (k >= ngroups_max) {
- free(gids);
- return -E2BIG;
- }
+ k++;
+ }
- g = *i;
- r = get_group_creds(&g, gids+k);
- if (r < 0) {
- free(gids);
- return r;
- }
+ /*
+ * 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;
+ }
- k++;
- }
+ /* Otherwise get the final list of supplementary groups */
+ groups = memdup(l_gids, sizeof(gid_t) * k);
+ if (!groups)
+ return -ENOMEM;
- r = maybe_setgroups(k, gids);
- if (r < 0) {
- free(gids);
+ *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;
- }
+ }
- free(gids);
+ 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. */
rename_process(process_name);
}
-#ifdef HAVE_SECCOMP
+static bool context_has_address_families(const ExecContext *c) {
+ assert(c);
-static bool skip_seccomp_unavailable(const Unit* u, const char* msg) {
- if (!is_seccomp_available()) {
- log_open();
- log_unit_debug(u, "SECCOMP features not detected in the kernel, skipping %s", msg);
- log_close();
- return true;
- }
- return false;
+ return c->address_families_whitelist ||
+ !set_isempty(c->address_families);
}
-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;
-
+static bool context_has_syscall_filters(const ExecContext *c) {
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);
+ return c->syscall_whitelist ||
+ !set_isempty(c->syscall_filter);
+}
- seccomp = seccomp_init(c->syscall_whitelist ? negative_action : SCMP_ACT_ALLOW);
- if (!seccomp)
- return -ENOMEM;
+static bool context_has_no_new_privileges(const ExecContext *c) {
+ assert(c);
- if (c->syscall_archs) {
+ if (c->no_new_privileges)
+ return true;
- 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 (have_effective_cap(CAP_SYS_ADMIN)) /* if we are privileged, we don't need NNP */
+ return false;
- } else {
- r = seccomp_add_secondary_archs(seccomp);
- if (r < 0)
- goto finish;
- }
+ /* We need NNP if we have any form of seccomp and are unprivileged */
+ return context_has_address_families(c) ||
+ 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) ||
+ !set_isempty(c->syscall_archs);
+}
- 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;
- }
+#ifdef HAVE_SECCOMP
- r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0);
- if (r < 0)
- goto finish;
+static bool skip_seccomp_unavailable(const Unit* u, const char* msg) {
- r = seccomp_load(seccomp);
+ if (is_seccomp_available())
+ return false;
-finish:
- seccomp_release(seccomp);
- return r;
+ log_open();
+ log_unit_debug(u, "SECCOMP features not detected in the kernel, skipping %s", msg);
+ log_close();
+ return true;
}
-static int apply_address_families(const Unit* u, const ExecContext *c) {
- scmp_filter_ctx *seccomp;
- Iterator i;
- int r;
+static int apply_syscall_filter(const Unit* u, const ExecContext *c) {
+ uint32_t negative_action, default_action, action;
+ assert(u);
assert(c);
- if (skip_seccomp_unavailable(u, "RestrictAddressFamilies="))
+ if (!context_has_syscall_filters(c))
return 0;
- seccomp = seccomp_init(SCMP_ACT_ALLOW);
- if (!seccomp)
- return -ENOMEM;
-
- r = seccomp_add_secondary_archs(seccomp);
- if (r < 0)
- goto finish;
-
- 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) {
+ if (skip_seccomp_unavailable(u, "SystemCallFilter="))
+ return 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;
+ negative_action = c->syscall_errno == 0 ? SCMP_ACT_KILL : SCMP_ACT_ERRNO(c->syscall_errno);
- } else {
+ if (c->syscall_whitelist) {
+ default_action = negative_action;
+ action = SCMP_ACT_ALLOW;
+ } else {
+ default_action = SCMP_ACT_ALLOW;
+ action = negative_action;
+ }
- /* 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;
+ return seccomp_load_syscall_filter_set_raw(default_action, c->syscall_filter, action);
+}
- r = seccomp_rule_add(
- seccomp,
- SCMP_ACT_ERRNO(EPROTONOSUPPORT),
- SCMP_SYS(socket),
- 1,
- SCMP_A0(SCMP_CMP_EQ, af));
- if (r < 0)
- goto finish;
- }
- }
+static int apply_syscall_archs(const Unit *u, const ExecContext *c) {
+ assert(u);
+ assert(c);
- } else {
- void *af;
+ if (set_isempty(c->syscall_archs))
+ return 0;
- /* If this is a blacklist, then generate one rule for
- * each address family that are then combined in OR
- * checks. */
+ if (skip_seccomp_unavailable(u, "SystemCallArchitectures="))
+ return 0;
- SET_FOREACH(af, c->address_families, i) {
+ return seccomp_restrict_archs(c->syscall_archs);
+}
- 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;
- }
- }
+static int apply_address_families(const Unit* u, const ExecContext *c) {
+ assert(u);
+ assert(c);
- r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0);
- if (r < 0)
- goto finish;
+ if (!context_has_address_families(c))
+ return 0;
- r = seccomp_load(seccomp);
+ if (skip_seccomp_unavailable(u, "RestrictAddressFamilies="))
+ return 0;
-finish:
- seccomp_release(seccomp);
- return r;
+ return seccomp_restrict_address_families(c->address_families, c->address_families_whitelist);
}
static int apply_memory_deny_write_execute(const Unit* u, const ExecContext *c) {
- scmp_filter_ctx *seccomp;
- int r;
-
+ assert(u);
assert(c);
- if (skip_seccomp_unavailable(u, "MemoryDenyWriteExecute="))
+ if (!c->memory_deny_write_execute)
return 0;
- seccomp = seccomp_init(SCMP_ACT_ALLOW);
- if (!seccomp)
- return -ENOMEM;
-
- r = seccomp_add_secondary_archs(seccomp);
- if (r < 0)
- goto finish;
-
- 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_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0);
- if (r < 0)
- goto finish;
-
- r = seccomp_load(seccomp);
+ if (skip_seccomp_unavailable(u, "MemoryDenyWriteExecute="))
+ return 0;
-finish:
- seccomp_release(seccomp);
- return r;
+ return seccomp_memory_deny_write_execute();
}
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(u);
assert(c);
- if (skip_seccomp_unavailable(u, "RestrictRealtime="))
+ if (!c->restrict_realtime)
return 0;
- seccomp = seccomp_init(SCMP_ACT_ALLOW);
- if (!seccomp)
- return -ENOMEM;
-
- r = seccomp_add_secondary_archs(seccomp);
- if (r < 0)
- goto finish;
-
- /* 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_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0);
- if (r < 0)
- goto finish;
-
- r = seccomp_load(seccomp);
+ if (skip_seccomp_unavailable(u, "RestrictRealtime="))
+ return 0;
-finish:
- seccomp_release(seccomp);
- return r;
+ return seccomp_restrict_realtime();
}
-static int apply_protect_sysctl(Unit *u, const ExecContext *c) {
- scmp_filter_ctx *seccomp;
- int r;
-
+static int apply_protect_sysctl(const Unit *u, const ExecContext *c) {
+ assert(u);
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="))
+ if (!c->protect_kernel_tunables)
return 0;
- seccomp = seccomp_init(SCMP_ACT_ALLOW);
- if (!seccomp)
- return -ENOMEM;
-
- r = seccomp_add_secondary_archs(seccomp);
- if (r < 0)
- goto finish;
-
- r = seccomp_rule_add(
- seccomp,
- SCMP_ACT_ERRNO(EPERM),
- SCMP_SYS(_sysctl),
- 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);
+ if (skip_seccomp_unavailable(u, "ProtectKernelTunables="))
+ return 0;
-finish:
- seccomp_release(seccomp);
- return r;
+ return seccomp_protect_sysctl();
}
-static int apply_protect_kernel_modules(Unit *u, const ExecContext *c) {
- static const int module_syscalls[] = {
- SCMP_SYS(delete_module),
- SCMP_SYS(finit_module),
- SCMP_SYS(init_module),
- };
-
- scmp_filter_ctx *seccomp;
- unsigned i;
- int r;
-
+static int apply_protect_kernel_modules(const Unit *u, const ExecContext *c) {
+ assert(u);
assert(c);
- /* Turn of module syscalls on ProtectKernelModules=yes */
+ /* Turn off module syscalls on ProtectKernelModules=yes */
- if (skip_seccomp_unavailable(u, "ProtectKernelModules="))
+ if (!c->protect_kernel_modules)
return 0;
- seccomp = seccomp_init(SCMP_ACT_ALLOW);
- if (!seccomp)
- return -ENOMEM;
-
- r = seccomp_add_secondary_archs(seccomp);
- if (r < 0)
- goto finish;
-
- for (i = 0; i < ELEMENTSOF(module_syscalls); i++) {
- r = seccomp_rule_add(seccomp, SCMP_ACT_ERRNO(EPERM),
- module_syscalls[i], 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);
+ if (skip_seccomp_unavailable(u, "ProtectKernelModules="))
+ return 0;
-finish:
- seccomp_release(seccomp);
- return r;
+ return seccomp_load_syscall_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_MODULE, SCMP_ACT_ERRNO(EPERM));
}
-static int apply_private_devices(Unit *u, const ExecContext *c) {
- const SystemCallFilterSet *set;
- scmp_filter_ctx *seccomp;
- const char *sys;
- bool syscalls_found = false;
- int r;
-
+static int apply_private_devices(const Unit *u, const ExecContext *c) {
+ assert(u);
assert(c);
/* If PrivateDevices= is set, also turn off iopl and all @raw-io syscalls. */
- if (skip_seccomp_unavailable(u, "PrivateDevices="))
+ if (!c->private_devices)
return 0;
- seccomp = seccomp_init(SCMP_ACT_ALLOW);
- if (!seccomp)
- return -ENOMEM;
-
- r = seccomp_add_secondary_archs(seccomp);
- if (r < 0)
- goto finish;
-
- for (set = syscall_filter_sets; set->set_name; set++)
- if (streq(set->set_name, "@raw-io")) {
- syscalls_found = true;
- break;
- }
-
- /* We should never fail here */
- if (!syscalls_found) {
- r = -EOPNOTSUPP;
- goto finish;
- }
-
- NULSTR_FOREACH(sys, set->value) {
- int id;
- bool add = true;
-
-#ifndef __NR_s390_pci_mmio_read
- if (streq(sys, "s390_pci_mmio_read"))
- add = false;
-#endif
-#ifndef __NR_s390_pci_mmio_write
- if (streq(sys, "s390_pci_mmio_write"))
- add = false;
-#endif
-
- if (!add)
- continue;
+ if (skip_seccomp_unavailable(u, "PrivateDevices="))
+ return 0;
- id = seccomp_syscall_resolve_name(sys);
+ return seccomp_load_syscall_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_RAW_IO, SCMP_ACT_ERRNO(EPERM));
+}
- r = seccomp_rule_add(
- seccomp,
- SCMP_ACT_ERRNO(EPERM),
- id, 0);
- if (r < 0)
- goto finish;
- }
+static int apply_restrict_namespaces(Unit *u, const ExecContext *c) {
+ assert(u);
+ assert(c);
- r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0);
- if (r < 0)
- goto finish;
+ if (!exec_context_restrict_namespaces_set(c))
+ return 0;
- r = seccomp_load(seccomp);
+ if (skip_seccomp_unavailable(u, "RestrictNamespaces="))
+ return 0;
-finish:
- seccomp_release(seccomp);
- return r;
+ return seccomp_restrict_namespaces(c->restrict_namespaces);
}
#endif
if (!joined)
return -ENOMEM;
- x = strjoin("LISTEN_FDNAMES=", joined, NULL);
+ x = strjoin("LISTEN_FDNAMES=", joined);
if (!x)
return -ENOMEM;
our_env[n_env++] = x;
v = getenv(*i);
if (!v)
continue;
- x = strjoin(*i, "=", v, NULL);
+ x = strjoin(*i, "=", v);
if (!x)
return -ENOMEM;
if (!GREEDY_REALLOC(pass_env, n_bufsize, n_env + 2))
assert(context);
assert(params);
+ if (context->root_image)
+ return true;
+
if (!strv_isempty(context->read_write_paths) ||
!strv_isempty(context->read_only_paths) ||
!strv_isempty(context->inaccessible_paths))
return true;
+ if (context->n_bind_mounts > 0)
+ return true;
+
if (context->mount_flags != 0)
return true;
context->protect_control_groups)
return true;
+ if (context->mount_apivfs)
+ return true;
+
return false;
}
* 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); /* The case where the above is the same */
- else
- uid_map = strdup("0 0 1\n");
- if (!uid_map)
- return -ENOMEM;
+ if (uid != 0 && uid_is_valid(uid)) {
+ r = asprintf(&uid_map,
+ "0 0 1\n" /* Map root → root */
+ UID_FMT " " UID_FMT " 1\n", /* Map $UID → $UID */
+ uid, uid);
+ if (r < 0)
+ return -ENOMEM;
+ } 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
+ if (gid != 0 && gid_is_valid(gid)) {
+ r = asprintf(&gid_map,
+ "0 0 1\n" /* Map root → root */
+ GID_FMT " " GID_FMT " 1\n", /* Map $GID → $GID */
+ gid, gid);
+ if (r < 0)
+ return -ENOMEM;
+ } else {
gid_map = strdup("0 0 1\n"); /* The case where the above is the same */
- if (!gid_map)
- return -ENOMEM;
+ if (!gid_map)
+ return -ENOMEM;
+ }
/* Create a communication channel so that the parent can tell the child when it finished creating the user
* namespace. */
STRV_FOREACH(rt, context->runtime_directory) {
_cleanup_free_ char *p;
- p = strjoin(params->runtime_prefix, "/", *rt, NULL);
+ p = strjoin(params->runtime_prefix, "/", *rt);
if (!p)
return -ENOMEM;
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_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_strv_free_ char **rw = NULL;
+ char *tmp = NULL, *var = NULL;
+ const char *root_dir = NULL, *root_image = 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,
+ .mount_apivfs = context->mount_apivfs,
+ };
+
+ 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_image = context->root_image;
+
+ if (!root_image)
+ 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, root_image,
+ &ns_info, rw,
+ context->read_only_paths,
+ context->inaccessible_paths,
+ context->bind_mounts,
+ context->n_bind_mounts,
+ tmp,
+ var,
+ context->protect_home,
+ context->protect_system,
+ context->mount_flags,
+ DISSECT_IMAGE_DISCARD_ON_LOOP);
+
+ /* 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;
- r = mac_smack_apply_pid(0, exec_label ? : SMACK_DEFAULT_PROCESS_LABEL);
- if (r < 0)
- return r;
- }
-#endif
-#endif
+ 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 int compile_read_write_paths(
- const ExecContext *context,
- const ExecParameters *params,
- char ***ret) {
+static int setup_keyring(Unit *u, const ExecParameters *p, uid_t uid, gid_t gid) {
+ key_serial_t keyring;
- _cleanup_strv_free_ char **l = NULL;
- char **rt;
+ assert(u);
+ assert(p);
- /* Compile the list of writable paths. This is the combination of the explicitly configured paths, plus all
- * runtime directories. */
+ /* Let's set up a new per-service "session" kernel keyring for each system service. This has the benefit that
+ * each service runs with its own keyring shared among all processes of the service, but with no hook-up beyond
+ * that scope, and in particular no link to the per-UID keyring. If we don't do this the keyring will be
+ * automatically created on-demand and then linked to the per-UID keyring, by the kernel. The kernel's built-in
+ * on-demand behaviour is very appropriate for login users, but probably not so much for system services, where
+ * UIDs are not necessarily specific to a service but reused (at least in the case of UID 0). */
- if (strv_isempty(context->read_write_paths) &&
- strv_isempty(context->runtime_directory)) {
- *ret = NULL; /* NOP if neither is set */
+ if (!(p->flags & EXEC_NEW_KEYRING))
return 0;
- }
- l = strv_copy(context->read_write_paths);
- if (!l)
- return -ENOMEM;
+ keyring = keyctl(KEYCTL_JOIN_SESSION_KEYRING, 0, 0, 0, 0);
+ if (keyring == -1) {
+ if (errno == ENOSYS)
+ log_debug_errno(errno, "Kernel keyring not supported, ignoring.");
+ else if (IN_SET(errno, EACCES, EPERM))
+ log_debug_errno(errno, "Kernel keyring access prohibited, ignoring.");
+ else if (errno == EDQUOT)
+ log_debug_errno(errno, "Out of kernel keyrings to allocate, ignoring.");
+ else
+ return log_error_errno(errno, "Setting up kernel keyring failed: %m");
- STRV_FOREACH(rt, context->runtime_directory) {
- char *s;
+ return 0;
+ }
- s = strjoin(params->runtime_prefix, "/", *rt, NULL);
- if (!s)
- return -ENOMEM;
+ /* Populate they keyring with the invocation ID by default. */
+ if (!sd_id128_is_null(u->invocation_id)) {
+ key_serial_t key;
- if (strv_consume(&l, s) < 0)
- return -ENOMEM;
+ key = add_key("user", "invocation_id", &u->invocation_id, sizeof(u->invocation_id), KEY_SPEC_SESSION_KEYRING);
+ if (key == -1)
+ log_debug_errno(errno, "Failed to add invocation ID to keyring, ignoring: %m");
+ else {
+ if (keyctl(KEYCTL_SETPERM, key,
+ KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH|
+ KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH, 0, 0) < 0)
+ return log_error_errno(errno, "Failed to restrict invocation ID permission: %m");
+ }
}
- *ret = l;
- l = NULL;
+ /* And now, make the keyring owned by the service's user */
+ if (uid_is_valid(uid) || gid_is_valid(gid))
+ if (keyctl(KEYCTL_CHOWN, keyring, uid, gid, 0) < 0)
+ return log_error_errno(errno, "Failed to change ownership of session keyring: %m");
return 0;
}
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 ||
- 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,
int *fds, unsigned n_fds,
char **files_env,
int user_lookup_fd,
- int *exit_status) {
+ int *exit_status,
+ char **error_message) {
_cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **final_argv = NULL;
_cleanup_free_ char *mac_selinux_context_net = NULL;
- const char *username = NULL, *home = NULL, *shell = NULL, *wd;
+ _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;
+ int i, r, ngids = 0;
assert(unit);
assert(command);
assert(context);
assert(params);
assert(exit_status);
+ assert(error_message);
+ /* We don't always set error_message, hence it must be initialized */
+ assert(*error_message == NULL);
rename_process_from_path(command->path);
r = reset_signal_mask();
if (r < 0) {
*exit_status = EXIT_SIGNAL_MASK;
+ *error_message = strdup("Failed to reset signal mask");
+ /* If strdup fails, here and below, we will just print the generic error message. */
return r;
}
r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, fds, n_fds);
if (r < 0) {
*exit_status = EXIT_FDS;
+ *error_message = strdup("Failed to close remaining fds");
return r;
}
exec_context_tty_reset(context, params);
- if (params->flags & EXEC_CONFIRM_SPAWN) {
- char response;
+ if (unit_shall_confirm_spawn(unit)) {
+ 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(&response, argv);
- if (r == -ETIMEDOUT)
- write_confirm_message("Confirmation question timed out, assuming positive response.\n");
- else if (r < 0)
- write_confirm_message("Couldn't ask confirmation question, assuming positive response: %s\n", strerror(-r));
- else if (response == 's') {
- write_confirm_message("Skipping execution.\n");
+ r = ask_for_confirmation(vc, unit, cmdline);
+ if (r != CONFIRM_EXECUTE) {
+ if (r == CONFIRM_PRETEND_SUCCESS) {
+ *exit_status = EXIT_SUCCESS;
+ return 0;
+ }
*exit_status = EXIT_CONFIRM;
+ *error_message = strdup("Execution cancelled");
return -ECANCELED;
- } else if (response == 'n') {
- write_confirm_message("Failing execution.\n");
- *exit_status = 0;
- return 0;
}
}
/* 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;
+ *error_message = strdup("Failed to update environment");
return -errno;
}
r = dynamic_creds_realize(dcreds, &uid, &gid);
if (r < 0) {
*exit_status = EXIT_USER;
+ *error_message = strdup("Failed to update dynamic user credentials");
return r;
}
- if (!uid_is_valid(uid) || !gid_is_valid(gid)) {
+ if (!uid_is_valid(uid)) {
+ *exit_status = EXIT_USER;
+ (void) asprintf(error_message, "UID validation failed for \""UID_FMT"\"", uid);
+ /* If asprintf fails, here and below, we will just print the generic error message. */
+ return -ESRCH;
+ }
+
+ if (!gid_is_valid(gid)) {
*exit_status = EXIT_USER;
+ (void) asprintf(error_message, "GID validation failed for \""GID_FMT"\"", gid);
return -ESRCH;
}
username = dcreds->user->name;
} else {
- if (context->user) {
- username = context->user;
- r = get_user_creds_clean(&username, &uid, &gid, &home, &shell);
- if (r < 0) {
- *exit_status = EXIT_USER;
- return r;
- }
-
- /* Note that we don't set $HOME or $SHELL if they are not particularly enlightening anyway
- * (i.e. are "/" or "/bin/nologin"). */
+ r = get_fixed_user(context, &username, &uid, &gid, &home, &shell);
+ if (r < 0) {
+ *exit_status = EXIT_USER;
+ *error_message = strdup("Failed to determine user credentials");
+ return r;
}
- if (context->group) {
- const char *g = context->group;
-
- r = get_group_creds(&g, &gid);
- if (r < 0) {
- *exit_status = EXIT_GROUP;
- return r;
- }
+ r = get_fixed_group(context, &groupname, &gid);
+ if (r < 0) {
+ *exit_status = EXIT_GROUP;
+ *error_message = strdup("Failed to determine group credentials");
+ 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;
+ *error_message = strdup("Failed to determine supplementary groups");
+ return r;
+ }
+
r = send_user_lookup(unit, user_lookup_fd, uid, gid);
if (r < 0) {
*exit_status = EXIT_USER;
+ *error_message = strdup("Failed to send user credentials to PID1");
return r;
}
r = setup_input(context, params, socket_fd, named_iofds);
if (r < 0) {
*exit_status = EXIT_STDIN;
+ *error_message = strdup("Failed to set up 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;
+ *error_message = strdup("Failed to set up 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;
+ *error_message = strdup("Failed to set up stderr");
return r;
}
r = cg_attach_everywhere(params->cgroup_supported, params->cgroup_path, 0, NULL, NULL);
if (r < 0) {
*exit_status = EXIT_CGROUP;
+ (void) asprintf(error_message, "Failed to attach to cgroup %s", params->cgroup_path);
return r;
}
}
log_close();
} else if (r < 0) {
*exit_status = EXIT_OOM_ADJUST;
+ *error_message = strdup("Failed to write /proc/self/oom_score_adj");
return -errno;
}
}
}
if (context->utmp_id)
- utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path,
+ 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);
+ username);
if (context->user) {
r = chown_terminal(STDIN_FILENO, uid);
(void) umask(context->umask);
- if ((params->flags & EXEC_APPLY_PERMISSIONS) && !command->privileged) {
- r = setup_smack(context, command);
- if (r < 0) {
- *exit_status = EXIT_SMACK_PROCESS_LABEL;
- return r;
- }
+ r = setup_keyring(unit, params, uid, gid);
+ if (r < 0) {
+ *exit_status = EXIT_KEYRING;
+ return r;
+ }
+ if ((params->flags & EXEC_APPLY_PERMISSIONS) && !command->privileged) {
if (context->pam_name && username) {
r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, fds, n_fds);
if (r < 0) {
needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime);
if (needs_mount_namespace) {
- _cleanup_free_ char **rw = NULL;
- char *tmp = NULL, *var = NULL;
- NameSpaceInfo ns_info = {
- .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,
- };
-
- /* 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);
+ r = apply_mount_namespace(unit, context, params, runtime);
if (r < 0) {
*exit_status = EXIT_NAMESPACE;
return r;
}
+ }
- r = setup_namespace(
- (params->flags & EXEC_APPLY_CHROOT) ? context->root_directory : NULL,
- &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 (r == -EPERM || r == -EACCES) {
- log_open();
- log_unit_debug_errno(unit, r, "Failed to set up namespace, assuming containerized execution, ignoring: %m");
- log_close();
- } else 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, username, gid);
+ r = enforce_groups(context, gid, supplementary_gids, ngids);
if (r < 0) {
*exit_status = EXIT_GROUP;
return r;
}
}
- 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_namespace && context->root_directory)
- if (chroot(context->root_directory) < 0) {
- *exit_status = EXIT_CHROOT;
- return -errno;
- }
-
- if (chdir(wd) < 0 &&
- !context->working_directory_missing_ok) {
- *exit_status = EXIT_CHDIR;
- return -errno;
- }
- } else {
- const char *d;
-
- d = strjoina(strempty(context->root_directory), "/", strempty(wd));
- if (chdir(d) < 0 &&
- !context->working_directory_missing_ok) {
- *exit_status = EXIT_CHDIR;
- return -errno;
- }
- }
-
#ifdef HAVE_SELINUX
if ((params->flags & EXEC_APPLY_PERMISSIONS) &&
mac_selinux_use() &&
r = capability_bounding_set_drop(context->capability_bounding_set, false);
if (r < 0) {
*exit_status = EXIT_CAPABILITIES;
+ *error_message = strdup("Failed to drop capabilities");
return r;
}
}
r = capability_ambient_set_apply(context->capability_ambient_set, true);
if (r < 0) {
*exit_status = EXIT_CAPABILITIES;
+ *error_message = strdup("Failed to apply ambient capabilities (before UID change)");
return r;
}
}
r = enforce_user(context, uid);
if (r < 0) {
*exit_status = EXIT_USER;
+ (void) asprintf(error_message, "Failed to change UID to "UID_FMT, uid);
return r;
}
if (context->capability_ambient_set != 0) {
r = capability_ambient_set_apply(context->capability_ambient_set, false);
if (r < 0) {
*exit_status = EXIT_CAPABILITIES;
+ *error_message = strdup("Failed to apply ambient capabilities (after UID change)");
return r;
}
}
}
+ /* Apply the MAC contexts late, but before seccomp syscall filtering, as those should really be last to
+ * influence our own codepaths as little as possible. Moreover, applying MAC contexts usually requires
+ * syscalls that are subject to seccomp filtering, hence should probably be applied before the syscalls
+ * are restricted. */
+
+#ifdef HAVE_SELINUX
+ if (mac_selinux_use()) {
+ char *exec_context = mac_selinux_context_net ?: context->selinux_context;
+
+ if (exec_context) {
+ r = setexeccon(exec_context);
+ if (r < 0) {
+ *exit_status = EXIT_SELINUX_CONTEXT;
+ (void) asprintf(error_message, "Failed to set SELinux context to %s", exec_context);
+ return r;
+ }
+ }
+ }
+#endif
+
+ r = setup_smack(context, command);
+ if (r < 0) {
+ *exit_status = EXIT_SMACK_PROCESS_LABEL;
+ *error_message = strdup("Failed to set 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;
+ (void) asprintf(error_message,
+ "Failed to prepare AppArmor profile change to %s",
+ context->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
if (prctl(PR_GET_SECUREBITS) != secure_bits)
if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) {
*exit_status = EXIT_SECUREBITS;
+ *error_message = strdup("Failed to set secure bits");
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;
+ *error_message = strdup("Failed to disable 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;
- }
+ r = apply_address_families(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_ADDRESS_FAMILIES;
+ *error_message = strdup("Failed to restrict 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;
- }
+ r = apply_memory_deny_write_execute(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_SECCOMP;
+ *error_message = strdup("Failed to disable writing to executable memory");
+ return r;
}
- if (context->restrict_realtime) {
- r = apply_restrict_realtime(unit, context);
- if (r < 0) {
- *exit_status = EXIT_SECCOMP;
- return r;
- }
+ r = apply_restrict_realtime(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_SECCOMP;
+ *error_message = strdup("Failed to apply realtime restrictions");
+ return r;
}
- if (context->protect_kernel_tunables) {
- r = apply_protect_sysctl(unit, context);
- if (r < 0) {
- *exit_status = EXIT_SECCOMP;
- return r;
- }
+ r = apply_restrict_namespaces(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_SECCOMP;
+ *error_message = strdup("Failed to apply namespace restrictions");
+ return r;
}
- if (context->protect_kernel_modules) {
- r = apply_protect_kernel_modules(unit, context);
- if (r < 0) {
- *exit_status = EXIT_SECCOMP;
- return r;
- }
+ r = apply_protect_sysctl(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_SECCOMP;
+ *error_message = strdup("Failed to apply sysctl restrictions");
+ return r;
}
- if (context->private_devices) {
- r = apply_private_devices(unit, context);
- if (r < 0) {
- *exit_status = EXIT_SECCOMP;
- return r;
- }
+ r = apply_protect_kernel_modules(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_SECCOMP;
+ *error_message = strdup("Failed to apply module loading restrictions");
+ return r;
}
- if (context_has_syscall_filters(context)) {
- r = apply_seccomp(unit, context);
- if (r < 0) {
- *exit_status = EXIT_SECCOMP;
- return r;
- }
+ r = apply_private_devices(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_SECCOMP;
+ *error_message = strdup("Failed to set up private devices");
+ return r;
}
-#endif
-
-#ifdef HAVE_SELINUX
- if (mac_selinux_use()) {
- char *exec_context = mac_selinux_context_net ?: context->selinux_context;
- if (exec_context) {
- r = setexeccon(exec_context);
- if (r < 0) {
- *exit_status = EXIT_SELINUX_CONTEXT;
- return r;
- }
- }
+ r = apply_syscall_archs(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_SECCOMP;
+ *error_message = strdup("Failed to apply syscall architecture restrictions");
+ return r;
}
-#endif
-#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;
- }
+ /* 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. */
+ r = apply_syscall_filter(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_SECCOMP;
+ *error_message = strdup("Failed to apply syscall filters");
+ return r;
}
#endif
}
final_argv = replace_env_argv(argv, accum_env);
if (!final_argv) {
*exit_status = EXIT_MEMORY;
+ *error_message = strdup("Failed to prepare process arguments");
return -ENOMEM;
}
if (pid == 0) {
int exit_status;
+ _cleanup_free_ char *error_message = NULL;
r = exec_child(unit,
command,
fds, n_fds,
files_env,
unit->manager->user_lookup_fds[1],
- &exit_status);
+ &exit_status,
+ &error_message);
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);
+ if (error_message)
+ log_struct_errno(LOG_ERR, r,
+ LOG_MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED),
+ LOG_UNIT_ID(unit),
+ LOG_UNIT_MESSAGE(unit, "%s: %m",
+ error_message),
+ "EXECUTABLE=%s", command->path,
+ NULL);
+ else
+ 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);
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) {
c->working_directory = mfree(c->working_directory);
c->root_directory = mfree(c->root_directory);
+ c->root_image = mfree(c->root_image);
c->tty_path = mfree(c->tty_path);
c->syslog_identifier = mfree(c->syslog_identifier);
c->user = mfree(c->user);
c->read_write_paths = strv_free(c->read_write_paths);
c->inaccessible_paths = strv_free(c->inaccessible_paths);
+ bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
+
if (c->cpuset)
CPU_FREE(c->cpuset);
STRV_FOREACH(i, c->runtime_directory) {
_cleanup_free_ char *p;
- p = strjoin(runtime_prefix, "/", *i, NULL);
+ p = strjoin(runtime_prefix, "/", *i);
if (!p)
return -ENOMEM;
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];
+ const char* stdio_fdname[3];
assert(c);
assert(p);
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])) {
+ 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])) {
+
+ } 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])) {
+
+ } 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);
+ return targets == 0 ? 0 : -ENOENT;
}
int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l) {
void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
char **e, **d;
unsigned i;
+ int r;
assert(c);
assert(f);
"%sPrivateUsers: %s\n"
"%sProtectHome: %s\n"
"%sProtectSystem: %s\n"
+ "%sMountAPIVFS: %s\n"
"%sIgnoreSIGPIPE: %s\n"
"%sMemoryDenyWriteExecute: %s\n"
"%sRestrictRealtime: %s\n",
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->mount_apivfs),
prefix, yes_no(c->ignore_sigpipe),
prefix, yes_no(c->memory_deny_write_execute),
prefix, yes_no(c->restrict_realtime));
+ if (c->root_image)
+ fprintf(f, "%sRootImage: %s\n", prefix, c->root_image);
+
STRV_FOREACH(e, c->environment)
fprintf(f, "%sEnvironment: %s\n", prefix, *e);
fputs("\n", f);
}
+ if (c->n_bind_mounts > 0)
+ for (i = 0; i < c->n_bind_mounts; i++) {
+ fprintf(f, "%s%s: %s:%s:%s\n", prefix,
+ c->bind_mounts[i].read_only ? "BindReadOnlyPaths" : "BindPaths",
+ c->bind_mounts[i].source,
+ c->bind_mounts[i].destination,
+ c->bind_mounts[i].recursive ? "rbind" : "norbind");
+ }
+
if (c->utmp_id)
fprintf(f,
"%sUtmpIdentifier: %s\n",
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",
STRV_FOREACH(a, argv)
k += strlen(*a)+3;
- if (!(n = new(char, k)))
+ n = new(char, k);
+ if (!n)
return NULL;
p = n;