]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
namespace-util: introduce namespace_enter_delegated()
authorMike Yuan <me@yhndnzj.com>
Wed, 17 Dec 2025 11:32:14 +0000 (12:32 +0100)
committerMike Yuan <me@yhndnzj.com>
Tue, 10 Feb 2026 20:54:11 +0000 (21:54 +0100)
Typically when entering a namespace the userns is handled last,
because we assume our process is more privileged than the userns.
However, that assumption no longer holds for user managers, which
have no privilege over initial userns and all other namespaces
are actually owned by the userns unshared first (in executor).
Hence, let's add another flavor namespace_enter_delegated() to
accommodate that use case.

src/basic/namespace-util.c
src/basic/namespace-util.h

index c3b84ea7840d151c155f61526181528eaead6b6a..69bd4945dfb9136628bb2aa858d70e85a8b57a49 100644 (file)
@@ -262,6 +262,71 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
         return 0;
 }
 
+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_delegated(int userns_fd, int pidns_fd, int mntns_fd, int netns_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 (pidns_fd >= 0) {
+                r = namespace_enter_one_idempotent(pidns_fd, NAMESPACE_PID);
+                if (r < 0)
+                        return r;
+        }
+
+        if (mntns_fd >= 0) {
+                r = namespace_enter_one_idempotent(mntns_fd, NAMESPACE_MOUNT);
+                if (r < 0)
+                        return r;
+        }
+
+        if (netns_fd >= 0) {
+                r = namespace_enter_one_idempotent(netns_fd, NAMESPACE_NET);
+                if (r < 0)
+                        return r;
+        }
+
+        if (root_fd >= 0) {
+                if (fchdir(root_fd) < 0)
+                        return -errno;
+
+                if (chroot(".") < 0)
+                        return -errno;
+        }
+
+        return maybe_setgroups(/* size = */ 0, NULL);
+}
+
 int fd_is_namespace(int fd, NamespaceType type) {
         int r;
 
index 32e64fa9e6801490bc7dbcbc0136018b2811b375..994125818f90683594af131d9633c2f835a1bc97 100644 (file)
@@ -47,6 +47,7 @@ int namespace_open(
                 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);