[PATCH v4 3/4] nsenter: new applet

Bartosz Golaszewski bartekgola at gmail.com
Mon Mar 14 17:07:22 UTC 2016


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

Signed-off-by: Bartosz Golaszewski <bartekgola at gmail.com>
---
 util-linux/namespace.h |  20 +++
 util-linux/nsenter.c   | 367 +++++++++++++++++++++++++++++++++++++++++++++++++
 util-linux/unshare.c   |  12 +-
 3 files changed, 390 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..da79fd9
--- /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 <bartekgola at 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") + sizeof(pid_t) * 3)
+
+#endif /* __BB_NAMESPACE_H */
diff --git a/util-linux/nsenter.c b/util-linux/nsenter.c
new file mode 100644
index 0000000..cdf5b79
--- /dev/null
+++ b/util-linux/nsenter.c
@@ -0,0 +1,367 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini nsenter implementation for busybox.
+ *
+ * Copyright (C) 2016 by Bartosz Golaszewski <bartekgola at 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 = open(".", O_RDONLY);
+			if (wd_fd < 0) {
+				bb_perror_msg_and_die(
+					"cannot open current working directory");
+			}
+		}
+
+		status = fchdir(root_fd);
+		if (status < 0) {
+			bb_perror_msg_and_die(
+				"change directory by root file descriptor failed");
+		}
+
+		if (chroot(".") < 0)
+			bb_perror_msg_and_die("chroot failed");
+
+		close(root_fd);
+		root_fd = -1;
+	}
+
+	if (wd_fd >= 0) {
+		status = fchdir(wd_fd);
+		if (status < 0) {
+			bb_perror_msg_and_die(
+				"change directory by working directory file descriptor failed");
+		}
+
+		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 d1394a9..c1811be 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") + sizeof(pid_t) * 3)
-
 #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



More information about the busybox mailing list