]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
nsenter: use pidfd to enter target namespaces
authorKarel Zak <kzak@redhat.com>
Fri, 18 Oct 2024 10:16:04 +0000 (12:16 +0200)
committerKarel Zak <kzak@redhat.com>
Mon, 21 Oct 2024 12:36:50 +0000 (14:36 +0200)
The typical use case is to enter namespaces of the task (--target
<pid>). The original nsenter opens /proc/<pid>/ns/* files and uses the
file descriptors to enter the namespaces by setns(). The recent kernel
allows using the pid file descriptor instead of the files in /proc,
making it possible to enter multiple namespaces with one setns call.
This solution reduces the number of syscalls (open+setns for each
namespace), removes the dependence on /proc, and allows entering
nested namespaces.

This commit should be backwardly compatible, meaning it can be used on
systems without pidfd_open(). Explicitly specified namespaces by
filenames are still supported, and user namespaces are still entered
first/last according to permissions privileging/deprivileging.

Addresses: https://github.com/util-linux/util-linux/pull/301
Signed-off-by: Karel Zak <kzak@redhat.com>
sys-utils/nsenter.c

index 66f278aadc3ac20c9b6c507e903d330bc2e0e598..53c3641fbbdcefc54bcfb13b5bba015a66ade4a4 100644 (file)
@@ -211,6 +211,17 @@ static void enable_namespace(int nstype, const char *path)
        assert(nsfile->nstype);
 }
 
+static void disable_namespaces(int namespaces)
+{
+       struct namespace_file *n;
+
+       for (n = namespace_files; n->nstype; n++) {
+               if (!(namespaces & n->nstype))
+                       continue;
+               disable_nsfile(n);
+       }
+}
+
 /* Returns mask of all enabled namespaces */
 static int get_namespaces(void)
 {
@@ -252,24 +263,38 @@ static void open_namespaces(int namespaces)
        }
 }
 
-static void enter_namespaces(int namespaces, bool ignore_errors)
+static int do_setns(int fd, int ns, const char *name, bool ignore_errors)
+{
+       int rc = setns(fd, ns);
+
+       if (rc < 0 && !ignore_errors) {
+               if (name)
+                       err(EXIT_FAILURE, _("reassociate to namespace '%s' failed"), name);
+               else
+                       err(EXIT_FAILURE, _("reassociate to namespaces failed"));
+       }
+       return rc;
+}
+
+static void enter_namespaces(int pid_fd, int namespaces, bool ignore_errors)
 {
        struct namespace_file *n;
 
+       if (pid_fd) {
+               int ns = 0;
+               for (n = namespace_files; n->nstype; n++) {
+                       if (n->enabled && (n->nstype & namespaces) && n->fd < 0)
+                               ns |= n->nstype;
+               }
+               if (ns && do_setns(pid_fd, ns, NULL, ignore_errors) == 0)
+                       disable_namespaces(ns);
+       }
+
        for (n = namespace_files; n->nstype; n++) {
-               if (!n->enabled)
+               if (!n->enabled || !(n->nstype & namespaces) || n->fd < 0)
                        continue;
-               if (!(n->nstype & namespaces))
-                       continue;
-               if (n->fd < 0)
-                       continue;
-
-               if (setns(n->fd, n->nstype) == 0)
-                       disable_nsfile(n);              /* sucess */
-               else if (!ignore_errors)
-                       err(EXIT_FAILURE,
-                                   _("reassociate to namespace '%s' failed"),
-                                   n->name);
+               if (do_setns(n->fd, n->nstype, n->name, ignore_errors) == 0)
+                       disable_nsfile(n);
        }
 }
 
@@ -486,6 +511,7 @@ int main(int argc, char *argv[])
        gid_t gid = 0;
        int keepcaps = 0;
        int sock_fd = -1;
+       int pid_fd = -1;
        struct ul_env_list *envls;
 #ifdef HAVE_LIBSELINUX
        bool selinux = 0;
@@ -633,7 +659,12 @@ int main(int argc, char *argv[])
        if (namespaces) {
                if (!namespace_target_pid)
                        errx(EXIT_FAILURE, _("no target PID specified"));
-               open_namespaces(namespaces);
+
+               pid_fd = pidfd_open(namespace_target_pid, 0);
+               if (pid_fd < 0) {
+                       /* fallback to classic way */
+                       open_namespaces(namespaces);
+               }
        }
 
        if (do_rd)
@@ -688,11 +719,14 @@ int main(int argc, char *argv[])
         * namespace last and if we're privileging it then we enter the user
         * namespace first (because the initial setns will fail).
         */
-       enter_namespaces(namespaces & ~CLONE_NEWUSER, 1);       /* ignore errors */
+       enter_namespaces(pid_fd, namespaces & ~CLONE_NEWUSER, 1);       /* ignore errors */
 
        namespaces = get_namespaces();
        if (namespaces)
-               enter_namespaces(namespaces, 0);                /* report errors */
+               enter_namespaces(pid_fd, namespaces, 0);                /* report errors */
+
+       if (pid_fd >= 0)
+               close(pid_fd);
 
        /* Remember the current working directory if I'm not changing it */
        if (root_fd >= 0 && wd_fd < 0 && wdns == NULL) {