[PATCH v6 5/7] nsenter: new applet
Bartosz Golaszewski
bartekgola at gmail.com
Thu Mar 17 14:52:27 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 | 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 <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") + 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 <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 = 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 bb7c165..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") + 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
More information about the busybox
mailing list