diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/analyze/analyze.c | 2 | ||||
-rw-r--r-- | src/basic/terminal-util.c | 19 | ||||
-rw-r--r-- | src/core/execute.c | 157 | ||||
-rw-r--r-- | src/core/execute.h | 17 | ||||
-rw-r--r-- | src/core/main.c | 45 | ||||
-rw-r--r-- | src/core/manager.c | 62 | ||||
-rw-r--r-- | src/core/manager.h | 6 | ||||
-rw-r--r-- | src/core/mount.c | 2 | ||||
-rw-r--r-- | src/core/service.c | 2 | ||||
-rw-r--r-- | src/core/socket.c | 2 | ||||
-rw-r--r-- | src/core/swap.c | 2 | ||||
-rw-r--r-- | src/core/unit.c | 11 | ||||
-rw-r--r-- | src/core/unit.h | 2 | ||||
-rw-r--r-- | src/nspawn/nspawn-mount.c | 70 | ||||
-rw-r--r-- | src/nspawn/nspawn-mount.h | 13 | ||||
-rw-r--r-- | src/nspawn/nspawn.c | 53 | ||||
-rw-r--r-- | src/shared/nsflags.c | 1 |
17 files changed, 357 insertions, 109 deletions
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index f744a84501..51d881c5fb 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -36,7 +36,9 @@ #include "log.h" #include "pager.h" #include "parse-util.h" +#ifdef HAVE_SECCOMP #include "seccomp-util.h" +#endif #include "special.h" #include "strv.h" #include "strxcpyx.h" diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index eafdea9eb3..9a8ef825c5 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -144,12 +144,14 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { return 0; } -int ask_char(char *ret, const char *replies, const char *text, ...) { +#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC) + +int ask_char(char *ret, const char *replies, const char *fmt, ...) { int r; assert(ret); assert(replies); - assert(text); + assert(fmt); for (;;) { va_list ap; @@ -159,8 +161,10 @@ int ask_char(char *ret, const char *replies, const char *text, ...) { if (colors_enabled()) fputs(ANSI_HIGHLIGHT, stdout); - va_start(ap, text); - vprintf(text, ap); + putchar('\r'); + + va_start(ap, fmt); + vprintf(fmt, ap); va_end(ap); if (colors_enabled()) @@ -168,9 +172,12 @@ int ask_char(char *ret, const char *replies, const char *text, ...) { fflush(stdout); - r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl); + r = read_one_char(stdin, &c, DEFAULT_ASK_REFRESH_USEC, &need_nl); if (r < 0) { + if (r == -ETIMEDOUT) + continue; + if (r == -EBADMSG) { puts("Bad input, please try again."); continue; @@ -455,7 +462,7 @@ int acquire_terminal( goto fail; } - r = fd_wait_for_event(fd, POLLIN, ts + timeout - n); + r = fd_wait_for_event(notify, POLLIN, ts + timeout - n); if (r < 0) goto fail; diff --git a/src/core/execute.c b/src/core/execute.c index 04c4e511f4..07ab067c05 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -624,7 +624,7 @@ static int chown_terminal(int fd, uid_t uid) { 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; @@ -639,12 +639,7 @@ static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) { 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; @@ -674,21 +669,27 @@ static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) { 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) { @@ -713,22 +714,96 @@ 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; } @@ -2315,22 +2390,24 @@ static int exec_child( 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; - 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"); + cmdline = exec_command_line(argv); + if (!cmdline) { + *exit_status = EXIT_CONFIRM; + return -ENOMEM; + } + + 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; return -ECANCELED; - } else if (response == 'n') { - write_confirm_message("Failing execution.\n"); - *exit_status = 0; - return 0; } } diff --git a/src/core/execute.h b/src/core/execute.h index e52640ee91..951c8f4da3 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -225,16 +225,15 @@ static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) { } typedef enum ExecFlags { - EXEC_CONFIRM_SPAWN = 1U << 0, - EXEC_APPLY_PERMISSIONS = 1U << 1, - EXEC_APPLY_CHROOT = 1U << 2, - EXEC_APPLY_TTY_STDIN = 1U << 3, + EXEC_APPLY_PERMISSIONS = 1U << 0, + EXEC_APPLY_CHROOT = 1U << 1, + EXEC_APPLY_TTY_STDIN = 1U << 2, /* The following are not used by execute.c, but by consumers internally */ - EXEC_PASS_FDS = 1U << 4, - EXEC_IS_CONTROL = 1U << 5, - EXEC_SETENV_RESULT = 1U << 6, - EXEC_SET_WATCHDOG = 1U << 7, + EXEC_PASS_FDS = 1U << 3, + EXEC_IS_CONTROL = 1U << 4, + EXEC_SETENV_RESULT = 1U << 5, + EXEC_SET_WATCHDOG = 1U << 6, } ExecFlags; struct ExecParameters { @@ -254,6 +253,8 @@ struct ExecParameters { const char *runtime_prefix; + const char *confirm_spawn; + usec_t watchdog_usec; int *idle_pipe; diff --git a/src/core/main.c b/src/core/main.c index f5f7df838d..5f9b1acad3 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -68,6 +68,7 @@ #include "mount-setup.h" #include "pager.h" #include "parse-util.h" +#include "path-util.h" #include "proc-cmdline.h" #include "process-util.h" #include "raw-clone.h" @@ -104,7 +105,7 @@ static bool arg_dump_core = true; static int arg_crash_chvt = -1; static bool arg_crash_shell = false; static bool arg_crash_reboot = false; -static bool arg_confirm_spawn = false; +static char *arg_confirm_spawn = NULL; static ShowStatus arg_show_status = _SHOW_STATUS_UNSET; static bool arg_switched_root = false; static bool arg_no_pager = false; @@ -294,6 +295,28 @@ static int parse_crash_chvt(const char *value) { return 0; } +static int parse_confirm_spawn(const char *value, char **console) { + char *s; + int r; + + r = value ? parse_boolean(value) : 1; + if (r == 0) { + *console = NULL; + return 0; + } + + if (r > 0) /* on with default tty */ + s = strdup("/dev/console"); + else if (is_path(value)) /* on with fully qualified path */ + s = strdup(value); + else /* on with only a tty file name, not a fully qualified path */ + s = strjoin("/dev/", value); + if (!s) + return -ENOMEM; + *console = s; + return 0; +} + static int set_machine_id(const char *m) { sd_id128_t t; assert(m); @@ -355,11 +378,11 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat } else if (streq(key, "systemd.confirm_spawn") && value) { - r = parse_boolean(value); + arg_confirm_spawn = mfree(arg_confirm_spawn); + + r = parse_confirm_spawn(value, &arg_confirm_spawn); if (r < 0) - log_warning("Failed to parse confirm spawn switch %s. Ignoring.", value); - else - arg_confirm_spawn = r; + log_warning_errno(r, "Failed to parse confirm_spawn switch %s. Ignoring.", value); } else if (streq(key, "systemd.show_status") && value) { @@ -952,12 +975,11 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_CONFIRM_SPAWN: - r = optarg ? parse_boolean(optarg) : 1; - if (r < 0) { - log_error("Failed to parse confirm spawn boolean %s.", optarg); - return r; - } - arg_confirm_spawn = r; + arg_confirm_spawn = mfree(arg_confirm_spawn); + + r = parse_confirm_spawn(optarg, &arg_confirm_spawn); + if (r < 0) + return log_error_errno(r, "Failed to parse confirm spawn option: %m"); break; case ARG_SHOW_STATUS: @@ -1991,6 +2013,7 @@ finish: arg_default_rlimit[j] = mfree(arg_default_rlimit[j]); arg_default_unit = mfree(arg_default_unit); + arg_confirm_spawn = mfree(arg_confirm_spawn); arg_join_controllers = strv_free_free(arg_join_controllers); arg_default_environment = strv_free(arg_default_environment); arg_syscall_archs = set_free(arg_syscall_archs); diff --git a/src/core/manager.c b/src/core/manager.c index 31770eef3a..1f663d3c1d 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -111,6 +111,12 @@ static void manager_watch_jobs_in_progress(Manager *m) { assert(m); + /* We do not want to show the cylon animation if the user + * needs to confirm service executions otherwise confirmation + * messages will be screwed by the cylon animation. */ + if (!manager_is_confirm_spawn_disabled(m)) + return; + if (m->jobs_in_progress_event_source) return; @@ -2993,7 +2999,7 @@ void manager_check_finished(Manager *m) { manager_close_idle_pipe(m); /* Turn off confirm spawn now */ - m->confirm_spawn = false; + m->confirm_spawn = NULL; /* No need to update ask password status when we're going non-interactive */ manager_close_ask_password(m); @@ -3178,6 +3184,49 @@ static bool manager_get_show_status(Manager *m, StatusType type) { return false; } +const char *manager_get_confirm_spawn(Manager *m) { + static int last_errno = 0; + const char *vc = m->confirm_spawn; + struct stat st; + int r; + + /* Here's the deal: we want to test the validity of the console but don't want + * PID1 to go through the whole console process which might block. But we also + * want to warn the user only once if something is wrong with the console so we + * cannot do the sanity checks after spawning our children. So here we simply do + * really basic tests to hopefully trap common errors. + * + * If the console suddenly disappear at the time our children will really it + * then they will simply fail to acquire it and a positive answer will be + * assumed. New children will fallback to /dev/console though. + * + * Note: TTYs are devices that can come and go any time, and frequently aren't + * available yet during early boot (consider a USB rs232 dongle...). If for any + * reason the configured console is not ready, we fallback to the default + * console. */ + + if (!vc || path_equal(vc, "/dev/console")) + return vc; + + r = stat(vc, &st); + if (r < 0) + goto fail; + + if (!S_ISCHR(st.st_mode)) { + errno = ENOTTY; + goto fail; + } + + last_errno = 0; + return vc; +fail: + if (last_errno != errno) { + last_errno = errno; + log_warning_errno(errno, "Failed to open %s: %m, using default console", vc); + } + return "/dev/console"; +} + void manager_set_first_boot(Manager *m, bool b) { assert(m); @@ -3194,6 +3243,17 @@ void manager_set_first_boot(Manager *m, bool b) { m->first_boot = b; } +void manager_disable_confirm_spawn(void) { + (void) touch("/run/systemd/confirm_spawn_disabled"); +} + +bool manager_is_confirm_spawn_disabled(Manager *m) { + if (!m->confirm_spawn) + return true; + + return access("/run/systemd/confirm_spawn_disabled", F_OK) >= 0; +} + void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) { va_list ap; diff --git a/src/core/manager.h b/src/core/manager.h index d54ca54107..4a9a37caff 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -246,7 +246,7 @@ struct Manager { uint8_t return_value; ShowStatus show_status; - bool confirm_spawn; + char *confirm_spawn; bool no_console_output; ExecOutput default_std_output, default_std_error; @@ -403,3 +403,7 @@ void manager_deserialize_gid_refs_one(Manager *m, const char *value); const char *manager_state_to_string(ManagerState m) _const_; ManagerState manager_state_from_string(const char *s) _pure_; + +const char *manager_get_confirm_spawn(Manager *m); +bool manager_is_confirm_spawn_disabled(Manager *m); +void manager_disable_confirm_spawn(void); diff --git a/src/core/mount.c b/src/core/mount.c index 43e0f1c746..1c2be28d55 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -747,7 +747,7 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { return r; exec_params.environment = UNIT(m)->manager->environment; - exec_params.flags |= UNIT(m)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; + exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(m)->manager); exec_params.cgroup_supported = UNIT(m)->manager->cgroup_supported; exec_params.cgroup_path = UNIT(m)->cgroup_path; exec_params.cgroup_delegate = m->cgroup_context.delegate; diff --git a/src/core/service.c b/src/core/service.c index 7aa1fba572..9ad4cf5070 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -1335,7 +1335,7 @@ static int service_spawn( exec_params.fds = fds; exec_params.fd_names = fd_names; exec_params.n_fds = n_fds; - exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; + exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(s)->manager); exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; exec_params.cgroup_path = path; exec_params.cgroup_delegate = s->cgroup_context.delegate; diff --git a/src/core/socket.c b/src/core/socket.c index ebacd74a47..1a53d47f21 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -1778,7 +1778,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { exec_params.argv = argv; exec_params.environment = UNIT(s)->manager->environment; - exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; + exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(s)->manager); exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; exec_params.cgroup_path = UNIT(s)->cgroup_path; exec_params.cgroup_delegate = s->cgroup_context.delegate; diff --git a/src/core/swap.c b/src/core/swap.c index b870ac88e3..bf404db8c3 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -636,7 +636,7 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { goto fail; exec_params.environment = UNIT(s)->manager->environment; - exec_params.flags |= UNIT(s)->manager->confirm_spawn ? EXEC_CONFIRM_SPAWN : 0; + exec_params.confirm_spawn = manager_get_confirm_spawn(UNIT(s)->manager); exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; exec_params.cgroup_path = UNIT(s)->cgroup_path; exec_params.cgroup_delegate = s->cgroup_context.delegate; diff --git a/src/core/unit.c b/src/core/unit.c index fbb21e4985..cba6342eca 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1511,6 +1511,17 @@ int unit_start_limit_test(Unit *u) { return emergency_action(u->manager, u->start_limit_action, u->reboot_arg, "unit failed"); } +bool unit_shall_confirm_spawn(Unit *u) { + + if (manager_is_confirm_spawn_disabled(u->manager)) + return false; + + /* For some reasons units remaining in the same process group + * as PID 1 fail to acquire the console even if it's not used + * by any process. So skip the confirmation question for them. */ + return !unit_get_exec_context(u)->same_pgrp; +} + /* Errors: * -EBADR: This unit type does not support starting. * -EALREADY: Unit is already started. diff --git a/src/core/unit.h b/src/core/unit.h index 6d6885b487..8052c234fd 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -657,6 +657,8 @@ void unit_notify_user_lookup(Unit *u, uid_t uid, gid_t gid); int unit_set_invocation_id(Unit *u, sd_id128_t id); int unit_acquire_invocation_id(Unit *u); +bool unit_shall_confirm_spawn(Unit *u); + /* Macros which append UNIT= or USER_UNIT= to the message */ #define log_unit_full(unit, level, error, ...) \ diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index 0c24b8e18a..95bb3c09b0 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -225,9 +225,10 @@ static int tmpfs_patch_options( return !!buf; } -int mount_sysfs(const char *dest) { +int mount_sysfs(const char *dest, MountSettingsMask mount_settings) { const char *full, *top, *x; int r; + unsigned long extra_flags = 0; top = prefix_roota(dest, "/sys"); r = path_check_fstype(top, SYSFS_MAGIC); @@ -244,8 +245,11 @@ int mount_sysfs(const char *dest) { (void) mkdir(full, 0755); + if (mount_settings & MOUNT_APPLY_APIVFS_RO) + extra_flags |= MS_RDONLY; + r = mount_verbose(LOG_ERR, "sysfs", full, "sysfs", - MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + MS_NOSUID|MS_NOEXEC|MS_NODEV|extra_flags, NULL); if (r < 0) return r; @@ -267,7 +271,7 @@ int mount_sysfs(const char *dest) { return r; r = mount_verbose(LOG_ERR, NULL, to, NULL, - MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL); + MS_BIND|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT|extra_flags, NULL); if (r < 0) return r; } @@ -291,7 +295,7 @@ int mount_sysfs(const char *dest) { } return mount_verbose(LOG_ERR, NULL, top, NULL, - MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL); + MS_BIND|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT|extra_flags, NULL); } static int mkdir_userns(const char *path, mode_t mode, bool in_userns, uid_t uid_shift) { @@ -348,8 +352,7 @@ static int mkdir_userns_p(const char *prefix, const char *path, mode_t mode, boo } int mount_all(const char *dest, - bool use_userns, bool in_userns, - bool use_netns, + MountSettingsMask mount_settings, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context) { @@ -359,41 +362,52 @@ int mount_all(const char *dest, const char *type; const char *options; unsigned long flags; - bool fatal; - bool in_userns; - bool use_netns; + MountSettingsMask mount_settings; } MountPoint; static const MountPoint mount_table[] = { - { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true, true, false }, - { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, true, true, false }, /* Bind mount first ...*/ - { "/proc/sys/net", "/proc/sys/net", NULL, NULL, MS_BIND, true, true, true }, /* (except for this) */ - { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, true, true, false }, /* ... then, make it r/o */ - { "/proc/sysrq-trigger", "/proc/sysrq-trigger", NULL, NULL, MS_BIND, false, true, false }, /* Bind mount first ...*/ - { NULL, "/proc/sysrq-trigger", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, true, false }, /* ... then, make it r/o */ - { "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, true }, - { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, false }, - { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, true, false, false }, - { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false }, - { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false }, - { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, true, true, false }, + /* inner child mounts */ + { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL|MOUNT_IN_USERNS }, + { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* Bind mount first ...*/ + { "/proc/sys/net", "/proc/sys/net", NULL, NULL, MS_BIND, MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO|MOUNT_APPLY_APIVFS_NETNS }, /* (except for this) */ + { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* ... then, make it r/o */ + { "/proc/sysrq-trigger", "/proc/sysrq-trigger", NULL, NULL, MS_BIND, MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* Bind mount first ...*/ + { NULL, "/proc/sysrq-trigger", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* ... then, make it r/o */ + { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, MOUNT_FATAL|MOUNT_IN_USERNS }, + + /* outer child mounts */ + { "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS }, + { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO }, /* skipped if above was mounted */ + { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL }, /* skipped if above was mounted */ + + { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, MOUNT_FATAL }, + { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, MOUNT_FATAL }, + { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, MOUNT_FATAL }, #ifdef HAVE_SELINUX - { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, false, false, false }, /* Bind mount first */ - { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, false, false }, /* Then, make it r/o */ + { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, 0 }, /* Bind mount first */ + { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, 0 }, /* Then, make it r/o */ #endif }; unsigned k; int r; + bool use_userns = (mount_settings & MOUNT_USE_USERNS); + bool netns = (mount_settings & MOUNT_APPLY_APIVFS_NETNS); + bool ro = (mount_settings & MOUNT_APPLY_APIVFS_RO); + bool in_userns = (mount_settings & MOUNT_IN_USERNS); for (k = 0; k < ELEMENTSOF(mount_table); k++) { _cleanup_free_ char *where = NULL, *options = NULL; const char *o; + bool fatal = (mount_table[k].mount_settings & MOUNT_FATAL); + + if (in_userns != (bool)(mount_table[k].mount_settings & MOUNT_IN_USERNS)) + continue; - if (in_userns != mount_table[k].in_userns) + if (!netns && (bool)(mount_table[k].mount_settings & MOUNT_APPLY_APIVFS_NETNS)) continue; - if (!use_netns && mount_table[k].use_netns) + if (!ro && (bool)(mount_table[k].mount_settings & MOUNT_APPLY_APIVFS_RO)) continue; where = prefix_root(dest, mount_table[k].where); @@ -410,7 +424,7 @@ int mount_all(const char *dest, r = mkdir_userns_p(dest, where, 0755, in_userns, uid_shift); if (r < 0 && r != -EEXIST) { - if (mount_table[k].fatal) + if (fatal) return log_error_errno(r, "Failed to create directory %s: %m", where); log_debug_errno(r, "Failed to create directory %s: %m", where); @@ -429,13 +443,13 @@ int mount_all(const char *dest, o = options; } - r = mount_verbose(mount_table[k].fatal ? LOG_ERR : LOG_DEBUG, + r = mount_verbose(fatal ? LOG_ERR : LOG_DEBUG, mount_table[k].what, where, mount_table[k].type, mount_table[k].flags, o); - if (r < 0 && mount_table[k].fatal) + if (r < 0 && fatal) return r; } diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h index 7307a838a5..74aee7ee7f 100644 --- a/src/nspawn/nspawn-mount.h +++ b/src/nspawn/nspawn-mount.h @@ -23,6 +23,15 @@ #include "cgroup-util.h" +typedef enum MountSettingsMask { + MOUNT_FATAL = 1 << 0, /* if set, a mount error is considered fatal */ + MOUNT_USE_USERNS = 1 << 1, /* if set, mounts are patched considering uid/gid shifts in a user namespace */ + MOUNT_IN_USERNS = 1 << 2, /* if set, the mount is executed in the inner child, otherwise in the outer child */ + MOUNT_APPLY_APIVFS_RO = 1 << 3, /* if set, /proc/sys, and /sysfs will be mounted read-only, otherwise read-write. */ + MOUNT_APPLY_APIVFS_NETNS = 1 << 4, /* if set, /proc/sys/net will be mounted read-write. + Works only if MOUNT_APPLY_APIVFS_RO is also set. */ +} MountSettingsMask; + typedef enum VolatileMode { VOLATILE_NO, VOLATILE_YES, @@ -57,8 +66,8 @@ int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s); int custom_mount_compare(const void *a, const void *b); -int mount_all(const char *dest, bool use_userns, bool in_userns, bool use_netns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); -int mount_sysfs(const char *dest); +int mount_all(const char *dest, MountSettingsMask mount_settings, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); +int mount_sysfs(const char *dest, MountSettingsMask mount_settings); int mount_cgroups(const char *dest, CGroupUnified unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context, bool use_cgns); int mount_systemd_cgroup_writable(const char *dest, CGroupUnified unified_requested); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 50d8aa049c..a6adbbe879 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -197,6 +197,7 @@ static const char *arg_container_service_name = "systemd-nspawn"; static bool arg_notify_ready = false; static bool arg_use_cgns = true; static unsigned long arg_clone_ns_flags = CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS; +static MountSettingsMask arg_mount_settings = MOUNT_APPLY_APIVFS_RO; static void help(void) { printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" @@ -380,6 +381,31 @@ static void parse_share_ns_env(const char *name, unsigned long ns_flag) { arg_clone_ns_flags = (arg_clone_ns_flags & ~ns_flag) | (r > 0 ? 0 : ns_flag); } +static void parse_mount_settings_env(void) { + int r; + const char *e; + + e = getenv("SYSTEMD_NSPAWN_API_VFS_WRITABLE"); + if (!e) + return; + + if (streq(e, "network")) { + arg_mount_settings |= MOUNT_APPLY_APIVFS_RO|MOUNT_APPLY_APIVFS_NETNS; + return; + } + + r = parse_boolean(e); + if (r < 0) { + log_warning_errno(r, "Failed to parse SYSTEMD_NSPAWN_API_VFS_WRITABLE from environment, ignoring."); + return; + } else if (r > 0) + arg_mount_settings &= ~MOUNT_APPLY_APIVFS_RO; + else + arg_mount_settings |= MOUNT_APPLY_APIVFS_RO; + + arg_mount_settings &= ~MOUNT_APPLY_APIVFS_NETNS; +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -1072,6 +1098,14 @@ static int parse_argv(int argc, char *argv[]) { parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_NS_UTS", CLONE_NEWUTS); parse_share_ns_env("SYSTEMD_NSPAWN_SHARE_SYSTEM", CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS); + if (arg_userns_mode != USER_NAMESPACE_NO) + arg_mount_settings |= MOUNT_USE_USERNS; + + if (arg_private_network) + arg_mount_settings |= MOUNT_APPLY_APIVFS_NETNS; + + parse_mount_settings_env(); + if (!(arg_clone_ns_flags & CLONE_NEWPID) || !(arg_clone_ns_flags & CLONE_NEWUTS)) { arg_register = false; @@ -1166,6 +1200,15 @@ static int parse_argv(int argc, char *argv[]) { } static int verify_arguments(void) { + if (arg_userns_mode != USER_NAMESPACE_NO && (arg_mount_settings & MOUNT_APPLY_APIVFS_NETNS) && !arg_private_network) { + log_error("Invalid namespacing settings. Mounting sysfs with --private-users requires --private-network."); + return -EINVAL; + } + + if (arg_userns_mode != USER_NAMESPACE_NO && !(arg_mount_settings & MOUNT_APPLY_APIVFS_RO)) { + log_error("Cannot combine --private-users with read-write mounts."); + return -EINVAL; + } if (arg_volatile_mode != VOLATILE_NO && arg_read_only) { log_error("Cannot combine --read-only with --volatile. Note that --volatile already implies a read-only base hierarchy."); @@ -2702,9 +2745,7 @@ static int inner_child( return log_error_errno(r, "Couldn't become new root: %m"); r = mount_all(NULL, - arg_userns_mode != USER_NAMESPACE_NO, - true, - arg_private_network, + arg_mount_settings | MOUNT_IN_USERNS, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); @@ -2712,7 +2753,7 @@ static int inner_child( if (r < 0) return r; - r = mount_sysfs(NULL); + r = mount_sysfs(NULL, arg_mount_settings); if (r < 0) return r; @@ -3079,9 +3120,7 @@ static int outer_child( } r = mount_all(directory, - arg_userns_mode != USER_NAMESPACE_NO, - false, - arg_private_network, + arg_mount_settings, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); diff --git a/src/shared/nsflags.c b/src/shared/nsflags.c index 911779a28f..aeb79b131e 100644 --- a/src/shared/nsflags.c +++ b/src/shared/nsflags.c @@ -22,7 +22,6 @@ #include "alloc-util.h" #include "extract-word.h" #include "nsflags.h" -#include "seccomp-util.h" #include "string-util.h" const struct namespace_flag_map namespace_flag_map[] = { |