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 | 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..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..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 <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 = 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 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 _______________________________________________ busybox mailing list busybox@busybox.net http://lists.busybox.net/mailman/listinfo/busybox