#include <sys/mount.h>
#include <unistd.h>
+#include "capability-util.h"
#include "dlfcn-util.h"
#include "errno-util.h"
#include "fd-util.h"
return pidref_namespace_open(&pidref, ret_pidns_fd, ret_mntns_fd, ret_netns_fd, ret_userns_fd, ret_root_fd);
}
-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();
-
- 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. */
-
- r = is_our_namespace(userns_fd, NAMESPACE_USER);
- if (r < 0)
- return r;
- if (r > 0)
- userns_fd = -EBADF;
- }
-
- if (pidns_fd >= 0)
- if (setns(pidns_fd, CLONE_NEWPID) < 0)
- return -errno;
-
- if (mntns_fd >= 0)
- if (setns(mntns_fd, CLONE_NEWNS) < 0)
- return -errno;
-
- if (netns_fd >= 0)
- if (setns(netns_fd, CLONE_NEWNET) < 0)
- return -errno;
-
- if (userns_fd >= 0)
- if (setns(userns_fd, CLONE_NEWUSER) < 0)
- return -errno;
-
- if (root_fd >= 0) {
- if (fchdir(root_fd) < 0)
- return -errno;
-
- if (chroot(".") < 0)
- return -errno;
- }
-
- if (userns_fd >= 0)
- return reset_uid_gid();
-
- return 0;
-}
-
static int namespace_enter_one_idempotent(int nsfd, NamespaceType type) {
int r;
return 1;
}
-int namespace_enter_delegated(int userns_fd, int pidns_fd, int mntns_fd, int netns_fd, int root_fd) {
+int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) {
int r;
- /* Similar to namespace_enter(), but operates on a set of namespaces that are potentially owned
- * by the userns ("delegated"), in which case we'll need to gain CAP_SYS_ADMIN by joining
- * the userns first, and the rest later. */
-
- assert(userns_fd >= 0);
-
/* Block dlopen() now, to avoid us inadvertently loading shared library from another namespace */
block_dlopen();
- if (setns(userns_fd, CLONE_NEWUSER) < 0)
- return -errno;
+ 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. */
+
+ r = is_our_namespace(userns_fd, NAMESPACE_USER);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ userns_fd = -EBADF;
+ }
+
+ r = have_effective_cap(CAP_SYS_ADMIN);
+ if (r < 0)
+ return r;
+
+ bool have_cap_sys_admin = r > 0;
+
+ if (!have_cap_sys_admin) {
+ /* If we don't have CAP_SYS_ADMIN in our own user namespace, our best bet is to enter the
+ * user namespace first (if we got one) to get CAP_SYS_ADMIN within the child user namespace,
+ * and then hope the other namespaces are owned by the child user namespace. If they aren't,
+ * we'll just get an EPERM later on when trying to setns() to them. */
+
+ if (userns_fd < 0)
+ return log_debug_errno(
+ SYNTHETIC_ERRNO(EPERM),
+ "Need CAP_SYS_ADMIN or a child user namespace to enter namespaces.");
+
+ if (setns(userns_fd, CLONE_NEWUSER) < 0)
+ return -errno;
+ }
if (pidns_fd >= 0) {
r = namespace_enter_one_idempotent(pidns_fd, NAMESPACE_PID);
return r;
}
+ if (userns_fd >= 0 && have_cap_sys_admin)
+ if (setns(userns_fd, CLONE_NEWUSER) < 0)
+ return -errno;
+
if (root_fd >= 0) {
if (fchdir(root_fd) < 0)
return -errno;
return -errno;
}
- return maybe_setgroups(/* size = */ 0, NULL);
+ if (userns_fd >= 0) {
+ /* Try to become root in the user namespace but don't error out if we can't, since it's not
+ * uncommon to have user namespaces without a root user in them. */
+ r = reset_uid_gid();
+ if (r < 0)
+ log_debug_errno(r, "Unable to drop auxiliary groups or reset UID/GID, ignoring: %m");
+ }
+
+ return 0;
}
int fd_is_namespace(int fd, NamespaceType type) {
int *ret_root_fd);
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
-int namespace_enter_delegated(int userns_fd, int pidns_fd, int mntns_fd, int netns_fd, int root_fd);
int fd_is_namespace(int fd, NamespaceType type);
int is_our_namespace(int fd, NamespaceType type);
int netns_fd,
int userns_fd,
int root_fd,
- bool delegated,
PidRef *ret) {
_cleanup_(pidref_done_sigkill_wait) PidRef pidref_outer = PIDREF_NULL;
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
- if (delegated)
- r = namespace_enter_delegated(userns_fd, pidns_fd, mntns_fd, netns_fd, root_fd);
- else
- r = namespace_enter(pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd);
+ r = namespace_enter(pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd);
if (r < 0) {
log_full_errno(prio, r, "Failed to join namespace: %m");
report_errno_and_exit(errno_pipe_fd[1], r);
int netns_fd,
int userns_fd,
int root_fd,
- bool delegated,
PidRef *ret);
static inline int namespace_fork(
PidRef *ret) {
return namespace_fork_full(outer_name, inner_name, NULL, 0, flags,
- pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd, false,
+ pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd,
ret);
}
(int[]) { tunnel_fds[1] }, 1,
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG,
pidns_fd, mntns_fd, /* netns_fd = */ -EBADF, userns_fd, root_fd,
- /* delegated = */ MANAGER_IS_USER(u->manager),
&child);
if (r < 0)
return log_full_errno(ERRNO_IS_NEG_PRIVILEGE(r) ? LOG_WARNING : LOG_ERR, r,
'test-mkdir.c',
'test-modhex.c',
'test-mountpoint-util.c',
+ 'test-namespace-util.c',
'test-net-naming-scheme.c',
'test-notify-recv.c',
'test-nsresource.c',
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "capability-util.h"
+#include "errno-util.h"
+#include "pidref.h"
+#include "process-util.h"
+#include "fd-util.h"
+#include "namespace-util.h"
+#include "tests.h"
+
+TEST(namespace_enter) {
+ _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
+ int r;
+
+ r = pidref_safe_fork(
+ "test-ns-enter-1",
+ FORK_NEW_USERNS|FORK_NEW_MOUNTNS|FORK_LOG|FORK_FREEZE|FORK_DEATHSIG_SIGKILL,
+ &pidref);
+ if (ERRNO_IS_NEG_PRIVILEGE(r))
+ return (void) log_tests_skipped_errno(r, "Unable to unshare user namespace");
+
+ ASSERT_OK(r);
+
+ _cleanup_close_ int mntns_fd = -EBADF, userns_fd = -EBADF, root_fd = -EBADF;
+ ASSERT_OK(pidref_namespace_open(&pidref, NULL, &mntns_fd, NULL, &userns_fd, &root_fd));
+
+ r = ASSERT_OK(pidref_safe_fork(
+ "test-ns-enter-2",
+ FORK_LOG|FORK_WAIT|FORK_DEATHSIG_SIGKILL,
+ NULL));
+ if (r == 0) {
+ ASSERT_OK(namespace_enter(-EBADF, mntns_fd, -EBADF, userns_fd, root_fd));
+ _exit(EXIT_SUCCESS);
+ }
+
+ /* Make sure we can enter the namespaces as well if we don't have CAP_SYS_ADMIN. */
+ r = ASSERT_OK(pidref_safe_fork(
+ "test-ns-enter-3",
+ FORK_LOG|FORK_WAIT|FORK_DEATHSIG_SIGKILL,
+ NULL));
+ if (r == 0) {
+ ASSERT_OK(drop_capability(CAP_SYS_ADMIN));
+ ASSERT_OK(namespace_enter(-EBADF, mntns_fd, -EBADF, userns_fd, root_fd));
+ _exit(EXIT_SUCCESS);
+ }
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);