[PATCH v7 5/7] nsenter: new applet

Denys Vlasenko vda.linux at googlemail.com
Fri Apr 1 20:50:30 UTC 2016


Applied, did not test it yet. Thanks!

On Fri, Mar 18, 2016 at 12:37 PM, Bartosz Golaszewski
<bartekgola at gmail.com> wrote:
> 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 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
>


More information about the busybox mailing list