Implement a fully featured (sans selinux part) nsenter applet.

Signed-off-by: Bartosz Golaszewski <bartekg...@gmail.com>
---
 util-linux/namespace.h |  20 +++
 util-linux/nsenter.c   | 351 +++++++++++++++++++++++++++++++++++++++++++++++++
 util-linux/unshare.c   |  12 +-
 3 files changed, 374 insertions(+), 9 deletions(-)
 create mode 100644 util-linux/namespace.h
 create mode 100644 util-linux/nsenter.c

diff --git a/util-linux/namespace.h b/util-linux/namespace.h
new file mode 100644
index 0000000..331bfe6
--- /dev/null
+++ b/util-linux/namespace.h
@@ -0,0 +1,20 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Common namespace code.
+ *
+ * Copyright (C) 2016 by Bartosz Golaszewski <bartekg...@gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+#ifndef BB_NAMESPACE_H
+#define BB_NAMESPACE_H
+
+/*
+ * Longest possible path to a procfs file used in namespace utils. Must be
+ * able to contain the '/proc/' string, the '/ns/user' string which is the
+ * longest namespace name and a 32-bit integer representing the process ID.
+ */
+#define NS_PROC_PATH_MAX (sizeof("/proc//ns/user") + INT_BUF_MAX(pid_t))
+
+#endif /* BB_NAMESPACE_H */
diff --git a/util-linux/nsenter.c b/util-linux/nsenter.c
new file mode 100644
index 0000000..79a28c6
--- /dev/null
+++ b/util-linux/nsenter.c
@@ -0,0 +1,351 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini nsenter implementation for busybox.
+ *
+ * Copyright (C) 2016 by Bartosz Golaszewski <bartekg...@gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+//config:config NSENTER
+//config:      bool "nsenter"
+//config:      default y
+//config:      select PLATFORM_LINUX
+//config:      help
+//config:        Run program with namespaces of other processes.
+//config:
+//config:config FEATURE_NSENTER_LONG_OPTS
+//config:      bool "enable long options"
+//config:      default y
+//config:      depends on NSENTER && LONG_OPTS
+//config:      help
+//config:        Support long options for the nsenter applet. This makes
+//config:        the busybox implementation more compatible with upstream.
+
+//applet:IF_NSENTER(APPLET(nsenter, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_NSENTER) += nsenter.o
+
+//usage:#define nsenter_trivial_usage
+//usage:       "[options] <program> [args...]"
+//usage:#if ENABLE_FEATURE_NSENTER_LONG_OPTS
+//usage:#define nsenter_full_usage "\n\n"
+//usage:       "Options:"
+//usage:     "\n       -t, --target <pid>              target process to get 
namespaces from"
+//usage:     "\n       -m, --mount[=<file>]            enter mount namespace"
+//usage:     "\n       -u, --uts[=<file>]              enter UTS namespace 
(hostname etc)"
+//usage:     "\n       -i, --ipc[=<file>]              enter System V IPC 
namespace"
+//usage:     "\n       -n, --net[=<file>]              enter network namespace"
+//usage:     "\n       -p, --pid[=<file>]              enter pid namespace"
+//usage:     "\n       -U, --user[=<file>]             enter user namespace"
+//usage:     "\n       -S, --setuid <uid>              set uid in entered 
namespace"
+//usage:     "\n       -G, --setgid <gid>              set gid in entered 
namespace"
+//usage:     "\n       -P, --preserve-credentials      do not touch uids or 
gids"
+//usage:     "\n       -r, --root[=<dir>]              set the root directory"
+//usage:     "\n       -w, --wd[=<dir>]                set the working 
directory"
+//usage:     "\n       -F, --no-fork                   do not fork before 
exec'ing <program>"
+//usage:#else
+//usage:#define nsenter_full_usage "\n\n"
+//usage:       "Options:"
+//usage:     "\n       -t <pid>        target process to get namespaces from"
+//usage:     "\n       -m [<file>]     enter mount namespace"
+//usage:     "\n       -u [<file>]     enter UTS namespace (hostname etc)"
+//usage:     "\n       -i [<file>]     enter System V IPC namespace"
+//usage:     "\n       -n [<file>]     enter network namespace"
+//usage:     "\n       -p [<file>]     enter pid namespace"
+//usage:     "\n       -U [<file>]     enter user namespace"
+//usage:     "\n       -S <uid>        set uid in entered namespace"
+//usage:     "\n       -G <gid>        set gid in entered namespace"
+//usage:     "\n       -P              do not touch uids or gids"
+//usage:     "\n       -r [<dir>]      set the root directory"
+//usage:     "\n       -w [<dir>]      set the working directory"
+//usage:     "\n       -F              do not fork before exec'ing <program>"
+//usage:#endif
+
+#include "libbb.h"
+#include "namespace.h"
+
+#include <sched.h>
+
+enum {
+       OPT_target      = BIT( 0),
+       OPT_mount       = BIT( 1),
+       OPT_uts         = BIT( 2),
+       OPT_ipc         = BIT( 3),
+       OPT_network     = BIT( 4),
+       OPT_pid         = BIT( 5),
+       OPT_user        = BIT( 6),
+       OPT_setuid      = BIT( 7),
+       OPT_setgid      = BIT( 8),
+       OPT_prescred    = BIT( 9),
+       OPT_root        = BIT(10),
+       OPT_wd          = BIT(11),
+       OPT_nofork      = BIT(12),
+};
+
+enum {
+       NS_USR_POS = 0,
+       NS_IPC_POS,
+       NS_UTS_POS,
+       NS_NET_POS,
+       NS_PID_POS,
+       NS_MNT_POS,
+       NS_COUNT,
+};
+
+struct namespace_descr {
+       const int opt;          /* nsenter command-line option */
+       const int flag;         /* value passed to setns() */
+       const char *nsfile;     /* namespace file in process' procfs entry */
+};
+
+struct namespace_ctx {
+       char *path;             /* optional path to a custom ns file */
+       int fd;                 /* opened namespace file descriptor */
+};
+
+/*
+ * Upstream nsenter doesn't support the short option for --preserve-credentials
+ * but let's use -P here in order to let the user use them even with long
+ * options disabled in busybox config.
+ */
+static const char opt_str[] = "t:m::u::i::n::p::U::S:G:Pr::w::F";
+
+#if ENABLE_FEATURE_NSENTER_LONG_OPTS
+static const char nsenter_longopts[] ALIGN1 =
+       "target\0"                      Required_argument       "t"
+       "mount\0"                       Optional_argument       "m"
+       "uts\0"                         Optional_argument       "u"
+       "ipc\0"                         Optional_argument       "i"
+       "network\0"                     Optional_argument       "n"
+       "pid\0"                         Optional_argument       "p"
+       "user\0"                        Optional_argument       "U"
+       "setuid\0"                      Required_argument       "S"
+       "setgid\0"                      Required_argument       "G"
+       "preserve-credentials\0"        No_argument             "P"
+       "root\0"                        Optional_argument       "r"
+       "wd\0"                          Optional_argument       "w"
+       "no-fork\0"                     No_argument             "F";
+#endif
+
+/*
+ * As opposed to unshare, the order is significant in nsenter.
+ *
+ * The user namespace comes first, so that it is entered first. This
+ * gives an unprivileged user the potential to enter the other
+ * namespaces.
+ */
+static const struct namespace_descr ns_list[] = {
+       [NS_USR_POS] = {
+               .opt = OPT_user,
+               .flag = CLONE_NEWUSER,
+               .nsfile = "user",
+       },
+       [NS_IPC_POS] = {
+               .opt = OPT_ipc,
+               .flag = CLONE_NEWIPC,
+               .nsfile = "ipc",
+       },
+       [NS_UTS_POS] = {
+               .opt = OPT_uts,
+               .flag = CLONE_NEWUTS,
+               .nsfile = "uts",
+       },
+       [NS_NET_POS] = {
+               .opt = OPT_network,
+               .flag = CLONE_NEWNET,
+               .nsfile = "net",
+       },
+       [NS_PID_POS] = {
+               .opt = OPT_pid,
+               .flag = CLONE_NEWPID,
+               .nsfile = "pid",
+       },
+       [NS_MNT_POS] = {
+               .opt = OPT_mount,
+               .flag = CLONE_NEWNS,
+               .nsfile = "mnt",
+       },
+};
+
+/*
+ * Open a file and return the new descriptor. If a full path is provided in
+ * fs_path, then the file to which it points is opened. Otherwise (fd_path is
+ * NULL) the routine builds a path to a procfs file using the following
+ * template: '/proc/<target_pid>/<target_file>'.
+ */
+static int open_by_path_or_target(const char *fs_path,
+                                 pid_t target_pid, const char *target_file)
+{
+       char proc_path_buf[NS_PROC_PATH_MAX];
+       const char *path;
+
+       if (fs_path) {
+               path = fs_path;
+       } else if (target_pid) {
+               snprintf(proc_path_buf, sizeof(proc_path_buf),
+                        "/proc/%d/%s", target_pid, target_file);
+               path = proc_path_buf;
+       } else {
+               bb_error_msg_and_die(
+                       "neither filename nor target pid supplied for %s",
+                       target_file);
+       }
+
+       return xopen(path, O_RDONLY);
+}
+
+int nsenter_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nsenter_main(int argc UNUSED_PARAM, char **argv)
+{
+       int i, root_fd = -1, wd_fd = -1, status, setgroups_failed = 0;
+       const char *root_dir_str = NULL, *wd_str = NULL;
+       const char *target_pid_str, *uid_str, *gid_str;
+       struct namespace_ctx ns_ctx_list[NS_COUNT];
+       pid_t target_pid = 0;
+       unsigned int opts;
+       uid_t uid = 0;
+       gid_t gid = 0;
+
+       IF_FEATURE_NSENTER_LONG_OPTS(applet_long_options = nsenter_longopts);
+
+       for (i = 0; i < NS_COUNT; i++) {
+               ns_ctx_list[i].path = NULL;
+               ns_ctx_list[i].fd = -1;
+       }
+
+       opts = getopt32(argv, opt_str, &target_pid_str,
+                       &ns_ctx_list[NS_MNT_POS].path,
+                       &ns_ctx_list[NS_UTS_POS].path,
+                       &ns_ctx_list[NS_IPC_POS].path,
+                       &ns_ctx_list[NS_NET_POS].path,
+                       &ns_ctx_list[NS_PID_POS].path,
+                       &ns_ctx_list[NS_USR_POS].path,
+                       &uid_str, &gid_str,&root_dir_str, &wd_str);
+       argv += optind;
+
+       if (opts & OPT_target)
+               target_pid = xstrtoul(target_pid_str, 10);
+
+       for (i = 0; i < NS_COUNT; i++) {
+               const struct namespace_descr *ns = &ns_list[i];
+               struct namespace_ctx *ns_ctx = &ns_ctx_list[i];
+               char nsfile[sizeof("ns/user")];
+
+               if (opts & ns->opt) {
+                       snprintf(nsfile, sizeof(nsfile), "ns/%s", ns->nsfile);
+                       ns_ctx->fd = open_by_path_or_target(ns_ctx->path,
+                                                           target_pid,
+                                                           nsfile);
+               }
+       }
+
+       if (opts & OPT_root)
+               root_fd = open_by_path_or_target(root_dir_str,
+                                                target_pid, "root");
+
+       if (opts & OPT_wd)
+               wd_fd = open_by_path_or_target(wd_str, target_pid, "cwd");
+
+       if (opts & OPT_setgid)
+               gid = xstrtoul(gid_str, 10);
+
+       if (opts & OPT_setuid)
+               uid = xstrtoul(uid_str, 10);
+
+       /*
+        * Entering the user namespace without --preserve-credentials implies
+        * --setuid & --setgid and clearing root's groups.
+        */
+       if ((opts & OPT_user) && !(opts & OPT_prescred)) {
+               opts |= (OPT_setuid | OPT_setgid);
+
+               /*
+                * We call setgroups() before and after setns() and only
+                * bail-out if it fails twice.
+                */
+               status = setgroups(0, NULL);
+               if (status < 0)
+                       setgroups_failed = 1;
+       }
+
+       for (i = 0; i < NS_COUNT; i++) {
+               const struct namespace_descr *ns = &ns_list[i];
+               struct namespace_ctx *ns_ctx = &ns_ctx_list[i];
+
+               if (ns_ctx->fd < 0)
+                       continue;
+
+               status = setns(ns_ctx->fd, ns->flag);
+               if (status < 0) {
+                       bb_perror_msg_and_die(
+                               "reassociate to namespace '%s' failed",
+                               ns->nsfile);
+               }
+
+               close(ns_ctx->fd);
+               ns_ctx->fd = -1;
+       }
+
+       if (root_fd >= 0) {
+               if (wd_fd < 0) {
+                       /*
+                        * Save the current working directory if we're not
+                        * changing it.
+                        */
+                       wd_fd = xopen(".", O_RDONLY);
+               }
+
+               xfchdir(root_fd);
+               xchroot(".");
+               close(root_fd);
+               root_fd = -1;
+       }
+
+       if (wd_fd >= 0) {
+               xfchdir(wd_fd);
+               close(wd_fd);
+               wd_fd = -1;
+       }
+
+       /*
+        * Entering the pid namespace implies forking unless it's been
+        * explicitly requested by the user not to.
+        */
+       if (!(opts & OPT_nofork) && (opts & OPT_pid)) {
+               int exit_status;
+               pid_t pid;
+
+               pid = xfork();
+               if (pid > 0) {
+                       status = safe_waitpid(pid, &exit_status, 0);
+                       if (status < 0)
+                               bb_perror_msg_and_die("waitpid");
+
+                       if (WIFEXITED(exit_status))
+                               return WEXITSTATUS(exit_status);
+                       else if (WIFSIGNALED(exit_status))
+                               kill(getpid(), WTERMSIG(exit_status));
+
+                       bb_error_msg_and_die("child exit failed");
+               } /* Child continues. */
+       }
+
+       if (opts & OPT_setgid) {
+               status = setgroups(0, NULL);
+               if (status < 0 && setgroups_failed)
+                       bb_perror_msg_and_die("setgroups failed");
+
+               xsetgid(gid);
+       }
+
+       if (opts & OPT_setuid)
+               xsetuid(uid);
+
+       if (*argv) {
+               execvp(*argv, argv);
+               bb_perror_msg_and_die("failed to execute %s", *argv);
+       }
+
+       run_shell(getenv("SHELL"), 0, NULL, NULL);
+}
diff --git a/util-linux/unshare.c b/util-linux/unshare.c
index 742d336..3bb1990 100644
--- a/util-linux/unshare.c
+++ b/util-linux/unshare.c
@@ -61,18 +61,12 @@
 //usage:#endif
 
 #include "libbb.h"
+#include "namespace.h"
 
 #include <sched.h>
 #include <sys/types.h>
 #include <sys/mount.h>
 
-/*
- * Longest possible path to a procfs file used in unshare. Must be able to
- * contain the '/proc/' string, the '/ns/user' string which is the longest
- * namespace name and a 32-bit integer representing the process ID.
- */
-#define PROC_PATH_MAX  (sizeof("/proc//ns/user") + INT_BUF_MAX(pid_t))
-
 #define PATH_PROC_SETGROUPS    "/proc/self/setgroups"
 #define PATH_PROC_UIDMAP       "/proc/self/uid_map"
 #define PATH_PROC_GIDMAP       "/proc/self/gid_map"
@@ -209,7 +203,7 @@ static unsigned long parse_propagation(const char *prop_str)
 
 static ino_t get_mnt_ns_inode_by_pid(pid_t pid)
 {
-       char path[PROC_PATH_MAX];
+       char path[NS_PROC_PATH_MAX];
        struct stat statbuf;
 
        snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
@@ -222,7 +216,7 @@ static void mount_namespaces(pid_t pid, struct 
namespace_ctx *ns_ctx_list)
 {
        const struct namespace_descr *ns;
        struct namespace_ctx *ns_ctx;
-       char nsf[PROC_PATH_MAX];
+       char nsf[NS_PROC_PATH_MAX];
        int i, status;
 
        for (i = 0; i < NS_COUNT; i++) {
-- 
2.1.4

_______________________________________________
busybox mailing list
busybox@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to