diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index 6a73eed913..f156bfdf24 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -68,6 +68,11 @@ static int pidref_namespace_open_by_type_internal(const PidRef *pidref, Namespac if (pidref->fd >= 0) { r = pidfd_get_namespace(pidref->fd, namespace_info[type].pidfd_get_ns_ioctl_cmd); + if (r == -ENOPKG) + return log_debug_errno( + r, + "Cannot open %s namespace for PID "PID_FMT" as the namespace type is not supported by the kernel", + namespace_info[type].proc_name, pidref->pid); if (!ERRNO_IS_NEG_NOT_SUPPORTED(r)) return r; } @@ -83,10 +88,17 @@ static int pidref_namespace_open_by_type_internal(const PidRef *pidref, Namespac if (nsfd == -ENOENT) { r = proc_mounted(); if (r == 0) - return -ENOSYS; /* /proc/ is not available or not set up properly, we're most likely - in some chroot environment. */ + /* /proc/ is not available or not set up properly, we're most likely in some chroot environment. */ + return log_debug_errno( + SYNTHETIC_ERRNO(ENOSYS), + "Cannot open %s namespace for PID "PID_FMT" as /proc is not mounted", + namespace_info[type].proc_name, pidref->pid); if (r > 0) - return -ENOPKG; /* If /proc/ is definitely around then this means the namespace type is not supported */ + /* If /proc/ is definitely around then this means the namespace type is not supported */ + return log_debug_errno( + SYNTHETIC_ERRNO(ENOPKG), + "Cannot open %s namespace for PID "PID_FMT" via /proc as the namespace type is not supported by the kernel", + namespace_info[type].proc_name, pidref->pid); /* can't determine? then propagate original error */ } @@ -216,33 +228,39 @@ int namespace_open( return pidref_namespace_open(&pidref, ret_pidns_fd, ret_mntns_fd, ret_netns_fd, ret_userns_fd, ret_root_fd); } -static int namespace_enter_one_idempotent(int nsfd, NamespaceType type) { - int r; - - /* Join a namespace, but only if we're not part of it already. This is important if we don't necessarily - * own the namespace in question, as kernel would unconditionally return EPERM otherwise. */ - - assert(nsfd >= 0); - assert(type >= 0 && type < _NAMESPACE_TYPE_MAX); - - r = is_our_namespace(nsfd, type); - if (r < 0) - return r; - if (r > 0) - return 0; - - if (setns(nsfd, namespace_info[type].clone_flag) < 0) - return -errno; - - return 1; -} - int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) { int r; /* Block dlopen() now, to avoid us inadvertently loading shared library from another namespace */ block_dlopen(); + /* Join namespaces, but only if we're not part of them already. This is important if we don't + * necessarily own the namespace in question, as kernel would unconditionally return EPERM otherwise. */ + + if (pidns_fd >= 0) { + r = is_our_namespace(pidns_fd, NAMESPACE_PID); + if (r < 0) + return r; + if (r > 0) + pidns_fd = -EBADF; + } + + if (mntns_fd >= 0) { + r = is_our_namespace(mntns_fd, NAMESPACE_MOUNT); + if (r < 0) + return r; + if (r > 0) + mntns_fd = -EBADF; + } + + if (netns_fd >= 0) { + r = is_our_namespace(netns_fd, NAMESPACE_NET); + if (r < 0) + return r; + if (r > 0) + netns_fd = -EBADF; + } + if (userns_fd >= 0) { /* Can't setns to your own userns, since then you could escalate from non-root to root in * your own namespace, so check if namespaces are equal before attempting to enter. */ @@ -275,23 +293,17 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int return -errno; } - if (pidns_fd >= 0) { - r = namespace_enter_one_idempotent(pidns_fd, NAMESPACE_PID); - if (r < 0) - return r; - } + if (pidns_fd >= 0) + if (setns(pidns_fd, CLONE_NEWPID) < 0) + return -errno; - if (mntns_fd >= 0) { - r = namespace_enter_one_idempotent(mntns_fd, NAMESPACE_MOUNT); - if (r < 0) - return r; - } + if (mntns_fd >= 0) + if (setns(mntns_fd, CLONE_NEWNS) < 0) + return -errno; - if (netns_fd >= 0) { - r = namespace_enter_one_idempotent(netns_fd, NAMESPACE_NET); - if (r < 0) - return r; - } + if (netns_fd >= 0) + if (setns(netns_fd, CLONE_NEWNET) < 0) + return -errno; if (userns_fd >= 0 && have_cap_sys_admin) if (setns(userns_fd, CLONE_NEWUSER) < 0) @@ -361,6 +373,42 @@ int is_our_namespace(int fd, NamespaceType type) { return fd_inode_same(fd, our_ns); } +int are_our_namespaces(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) { + int r; + + if (pidns_fd >= 0) { + r = is_our_namespace(pidns_fd, NAMESPACE_PID); + if (r <= 0) + return r; + } + + if (mntns_fd >= 0) { + r = is_our_namespace(mntns_fd, NAMESPACE_MOUNT); + if (r <= 0) + return r; + } + + if (netns_fd >= 0) { + r = is_our_namespace(netns_fd, NAMESPACE_NET); + if (r <= 0) + return r; + } + + if (userns_fd >= 0) { + r = is_our_namespace(userns_fd, NAMESPACE_USER); + if (r <= 0) + return r; + } + + if (root_fd >= 0) { + r = dir_fd_is_root(root_fd); + if (r <= 0) + return r; + } + + return true; +} + int namespace_is_init(NamespaceType type) { int r; diff --git a/src/basic/namespace-util.h b/src/basic/namespace-util.h index 32e64fa9e6..cd2ea78692 100644 --- a/src/basic/namespace-util.h +++ b/src/basic/namespace-util.h @@ -50,6 +50,7 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int int fd_is_namespace(int fd, NamespaceType type); int is_our_namespace(int fd, NamespaceType type); +int are_our_namespaces(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd); int namespace_is_init(NamespaceType type); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 9d77e96b1a..3f2c2d3b23 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -25,13 +25,11 @@ #include "cgroup-util.h" #include "dirent-util.h" #include "dlfcn-util.h" -#include "env-file.h" #include "errno-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" -#include "hostname-util.h" #include "io-util.h" #include "iovec-util.h" #include "locale-util.h" @@ -53,6 +51,7 @@ #include "stdio-util.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "user-util.h" @@ -349,47 +348,6 @@ int pidref_get_cmdline_strv(const PidRef *pid, ProcessCmdlineFlags flags, char * return 0; } -int container_get_leader(const char *machine, pid_t *pid) { - _cleanup_free_ char *s = NULL, *class = NULL; - const char *p; - pid_t leader; - int r; - - assert(machine); - assert(pid); - - if (streq(machine, ".host")) { - *pid = 1; - return 0; - } - - if (!hostname_is_valid(machine, 0)) - return -EINVAL; - - p = strjoina("/run/systemd/machines/", machine); - r = parse_env_file(NULL, p, - "LEADER", &s, - "CLASS", &class); - if (r == -ENOENT) - return -EHOSTDOWN; - if (r < 0) - return r; - if (!s) - return -EIO; - - if (!streq_ptr(class, "container")) - return -EIO; - - r = parse_pid(s, &leader); - if (r < 0) - return r; - if (leader <= 1) - return -EIO; - - *pid = leader; - return 0; -} - int pid_is_kernel_thread(pid_t pid) { int r; diff --git a/src/basic/process-util.h b/src/basic/process-util.h index b3f6120053..9d16055d5e 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -54,8 +54,6 @@ int pid_get_start_time(pid_t pid, usec_t *ret); int pidref_get_start_time(const PidRef *pid, usec_t *ret); int get_process_umask(pid_t pid, mode_t *ret); -int container_get_leader(const char *machine, pid_t *pid); - static inline bool SIGINFO_CODE_IS_DEAD(int code) { return IN_SET(code, CLD_EXITED, CLD_KILLED, CLD_DUMPED); } diff --git a/src/coredump/coredump-send.c b/src/coredump/coredump-send.c index 12c781d05a..0174be69a4 100644 --- a/src/coredump/coredump-send.c +++ b/src/coredump/coredump-send.c @@ -241,7 +241,7 @@ int coredump_send_to_container(CoredumpContext *context) { _cleanup_(pidref_done) PidRef leader_pid = PIDREF_NULL; r = namespace_get_leader(&context->pidref, NAMESPACE_PID, &leader_pid); if (r < 0) - return log_debug_errno(r, "Failed to get namespace leader: %m"); + return log_error_errno(r, "Failed to get namespace leader: %m"); r = can_forward_coredump(&context->pidref, &leader_pid); if (r <= 0) @@ -258,33 +258,33 @@ int coredump_send_to_container(CoredumpContext *context) { r = RET_NERRNO(socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair)); if (r < 0) - return log_debug_errno(r, "Failed to create socket pair: %m"); + return log_error_errno(r, "Failed to create socket pair: %m"); r = setsockopt_int(pair[1], SOL_SOCKET, SO_PASSCRED, true); if (r < 0) - return log_debug_errno(r, "Failed to set SO_PASSCRED: %m"); + return log_error_errno(r, "Failed to set SO_PASSCRED: %m"); r = pidref_namespace_open(&leader_pid, &pidnsfd, &mntnsfd, &netnsfd, &usernsfd, &rootfd); if (r < 0) - return log_debug_errno(r, "Failed to open namespaces of PID " PID_FMT ": %m", leader_pid.pid); + return log_error_errno(r, "Failed to open namespaces of PID " PID_FMT ": %m", leader_pid.pid); r = namespace_fork("(sd-coredumpns)", "(sd-coredump)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, pidnsfd, mntnsfd, netnsfd, usernsfd, rootfd, &child); if (r < 0) - return log_debug_errno(r, "Failed to fork into namespaces of PID " PID_FMT ": %m", leader_pid.pid); + return log_error_errno(r, "Failed to fork into namespaces of PID " PID_FMT ": %m", leader_pid.pid); if (r == 0) { pair[0] = safe_close(pair[0]); r = access_nofollow("/run/systemd/coredump", W_OK); if (r < 0) { - log_debug_errno(r, "Cannot find coredump socket, exiting: %m"); + log_error_errno(r, "Cannot find coredump socket, exiting: %m"); _exit(EXIT_FAILURE); } r = receive_ucred(pair[1], &ucred); if (r < 0) { - log_debug_errno(r, "Failed to receive ucred and fd: %m"); + log_error_errno(r, "Failed to receive ucred and fd: %m"); _exit(EXIT_FAILURE); } @@ -309,7 +309,7 @@ int coredump_send_to_container(CoredumpContext *context) { r = coredump_send(context); if (r < 0) { - log_debug_errno(r, "Failed to send iovec to coredump socket: %m"); + log_error_errno(r, "Failed to send iovec to coredump socket: %m"); _exit(EXIT_FAILURE); } @@ -324,13 +324,13 @@ int coredump_send_to_container(CoredumpContext *context) { * container. The kernel will perform the translation for us. */ r = send_ucred(pair[0], &ucred); if (r < 0) - return log_debug_errno(r, "Failed to send metadata to container: %m"); + return log_error_errno(r, "Failed to send metadata to container: %m"); - r = pidref_wait_for_terminate_and_check("(sd-coredumpns)", &child, 0); + r = pidref_wait_for_terminate_and_check("(sd-coredumpns)", &child, WAIT_LOG); if (r < 0) - return log_debug_errno(r, "Failed to wait for child to terminate: %m"); + return r; if (r != EXIT_SUCCESS) - return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to process coredump in container."); + return -EPROTO; return 1; /* sent */ } diff --git a/src/libsystemd/sd-bus/bus-container.c b/src/libsystemd/sd-bus/bus-container.c index 3bbcbb81c6..475e855462 100644 --- a/src/libsystemd/sd-bus/bus-container.c +++ b/src/libsystemd/sd-bus/bus-container.c @@ -5,20 +5,99 @@ #include "bus-container.h" #include "bus-internal.h" #include "bus-socket.h" +#include "env-file.h" +#include "errno-util.h" #include "fd-util.h" #include "format-util.h" +#include "hostname-util.h" #include "log.h" #include "namespace-util.h" -#include "pidref.h" +#include "parse-util.h" +#include "path-lookup.h" +#include "path-util.h" #include "process-util.h" #include "string-util.h" +int container_get_leader(RuntimeScope scope, const char *machine, pid_t *ret) { + _cleanup_free_ char *p = NULL, *s = NULL, *class = NULL; + pid_t leader; + int r; + + assert(machine); + assert(ret); + + if (streq(machine, ".host")) { + if (scope == RUNTIME_SCOPE_USER) + return -EHOSTDOWN; + + *ret = 1; + return 0; + } + + if (!hostname_is_valid(machine, 0)) + return -EINVAL; + + r = runtime_directory_generic(scope, "systemd/machines", &p); + if (r < 0) + return r; + + if (!path_extend(&p, machine)) + return -ENOMEM; + + r = parse_env_file(NULL, p, + "LEADER", &s, + "CLASS", &class); + if (r == -ENOENT) + return -EHOSTDOWN; + if (r < 0) + return r; + if (!s) + return -ESRCH; + + if (!streq_ptr(class, "container")) + return -EMEDIUMTYPE; + + r = parse_pid(s, &leader); + if (r < 0) + return r; + if (leader <= 1) + return -EBADMSG; + + *ret = leader; + return 0; +} + +static int bus_container_connect_namespace(sd_bus *b, int pidnsfd, int mntnsfd, int usernsfd, int rootfd) { + _cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR; + int r; + + if (pipe2(errno_pipe_fd, O_CLOEXEC) < 0) + return log_debug_errno(errno, "Failed to create pipe: %m"); + + r = namespace_fork("(sd-buscntrns)", "(sd-buscntr)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, + pidnsfd, mntnsfd, /* netns_fd= */ -EBADF, usernsfd, rootfd, /* ret= */ NULL); + if (r == -EPROTO) { + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + int k = read_errno(errno_pipe_fd[0]); + if (k < 0 && k != -EIO) + return k; + } + if (r < 0) + return log_debug_errno(r, "Failed to create namespace for (sd-buscntr): %m"); + if (r == 0) { + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + r = RET_NERRNO(connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size)); + report_errno_and_exit(errno_pipe_fd[1], r); + } + + return 0; +} + int bus_container_connect_socket(sd_bus *b) { _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF; - _cleanup_(pidref_done) PidRef child = PIDREF_NULL; - _cleanup_close_pair_ int pair[2] = EBADF_PAIR; - int r, error_buf = 0; - ssize_t n; + int r; assert(b); assert(b->input_fd < 0); @@ -29,7 +108,9 @@ int bus_container_connect_socket(sd_bus *b) { log_debug("sd-bus: connecting bus%s%s to machine %s...", b->description ? " " : "", strempty(b->description), b->machine); - r = container_get_leader(b->machine, &b->nspid); + r = container_get_leader(RUNTIME_SCOPE_USER, b->machine, &b->nspid); + if (IN_SET(r, -EHOSTDOWN, -ENXIO)) + r = container_get_leader(RUNTIME_SCOPE_SYSTEM, b->machine, &b->nspid); if (r < 0) return r; } else @@ -50,56 +131,17 @@ int bus_container_connect_socket(sd_bus *b) { bus_socket_setup(b); - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0) - return log_debug_errno(errno, "Failed to create a socket pair: %m"); - - r = namespace_fork("(sd-buscntrns)", "(sd-buscntr)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, - pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child); + r = are_our_namespaces(pidnsfd, mntnsfd, /* netns_fd= */ -EBADF, usernsfd, rootfd); if (r < 0) - return log_debug_errno(r, "Failed to create namespace for (sd-buscntr): %m"); - if (r == 0) { - pair[0] = safe_close(pair[0]); - - r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size); - if (r < 0) { - /* Try to send error up */ - error_buf = errno; - (void) write(pair[1], &error_buf, sizeof(error_buf)); - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - r = pidref_wait_for_terminate_and_check("(sd-buscntrns)", &child, 0); + return log_debug_errno(r, "Failed to check if already in PID "PID_FMT" namespaces: %m", b->nspid); + if (r > 0) + r = RET_NERRNO(connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size)); + else + r = bus_container_connect_namespace(b, pidnsfd, mntnsfd, usernsfd, rootfd); + if (r == -EINPROGRESS) + return 1; if (r < 0) - return r; - bool nonzero_exit_status = r != EXIT_SUCCESS; - - n = read(pair[0], &error_buf, sizeof(error_buf)); - if (n < 0) - return log_debug_errno(errno, "Failed to read error status from (sd-buscntr): %m"); - - if (n > 0) { - if (n != sizeof(error_buf)) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Read error status of unexpected length %zd from (sd-buscntr).", n); - - if (error_buf < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "Got unexpected error status from (sd-buscntr)."); - - if (error_buf == EINPROGRESS) - return 1; - - if (error_buf > 0) - return log_debug_errno(error_buf, "(sd-buscntr) failed to connect to D-Bus socket: %m"); - } - - if (nonzero_exit_status) - return -EPROTO; + return log_debug_errno(r, "Failed to connect to D-Bus socket in namespaces of PID "PID_FMT": %m", b->nspid); return bus_socket_start_auth(b); } diff --git a/src/libsystemd/sd-bus/bus-container.h b/src/libsystemd/sd-bus/bus-container.h index 2e933bd9a7..f6ede2ecd6 100644 --- a/src/libsystemd/sd-bus/bus-container.h +++ b/src/libsystemd/sd-bus/bus-container.h @@ -3,4 +3,6 @@ #include "sd-forward.h" +int container_get_leader(RuntimeScope scope, const char *machine, pid_t *ret); + int bus_container_connect_socket(sd_bus *b); diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c index 0cae92b728..9d406a45d1 100644 --- a/src/libsystemd/sd-id128/id128-util.c +++ b/src/libsystemd/sd-id128/id128-util.c @@ -5,6 +5,7 @@ #include #include +#include "bus-container.h" #include "fd-util.h" #include "fs-util.h" #include "hash-funcs.h" @@ -13,6 +14,7 @@ #include "namespace-util.h" #include "pidref.h" #include "process-util.h" +#include "runtime-scope.h" #include "sha256.h" #include "siphash24.h" #include "string-util.h" @@ -287,7 +289,7 @@ int id128_get_boot_for_machine(const char *machine, sd_id128_t *ret) { if (isempty(machine)) return sd_id128_get_boot(ret); - r = container_get_leader(machine, &pid); + r = container_get_leader(RUNTIME_SCOPE_SYSTEM, machine, &pid); if (r < 0) return r; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 1c0ab6e712..296b12bb90 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -4368,15 +4368,8 @@ static int outer_child( /* The inner child has all namespaces that are requested, so that we all are owned by the * user if user namespaces are turned on. */ - if (arg_network_namespace_path) { - r = namespace_enter(/* pidns_fd= */ -EBADF, - /* mntns_fd= */ -EBADF, - netns_fd, - /* userns_fd= */ -EBADF, - /* root_fd= */ -EBADF); - if (r < 0) - return log_error_errno(r, "Failed to join network namespace: %m"); - } + if (arg_network_namespace_path && setns(netns_fd, CLONE_NEWNET) < 0) + return log_error_errno(errno, "Failed to join network namespace: %m"); if (arg_userns_mode == USER_NAMESPACE_MANAGED) { /* In managed usernamespace operation, sysfs + procfs are special, we'll have to