-t, -S and -G each take mandatory integer arguments. getopt32long()'s option string syntax for this type of argument is 'c:+', however nsenter's opt_str uses 'c+', which specifies two options 'c' and '+' which do not take arguments. This means that giving a target PID causes nsenter to exit and print the usage string: # nsenter -t1 sh nsenter: unrecognized option: 1 BusyBox v1.27.2 (2017-12-12 10:41:50 GMT) multi-call binary. ... The long form options are also broken: # nsenter --setuid=1000 --setgid=1000 sh BusyBox v1.29.0.git (2018-05-04 13:56:49 UTC) multi-call binary. ... `nsenter --target=<pid> sh` parses correctly and appears to work, but <pid> is ignored and set to 0. This doesn't raise an error unless one of the namespace arguments is also given: # ./busybox_unstripped nsenter --target=42 sh # exit # ./busybox_unstripped nsenter -n --target=42 sh BusyBox v1.29.0.git (2018-05-04 13:56:49 UTC) multi-call binary. ... This has caused problems in a couple of places: https://github.com/linuxkit/linuxkit/issues/567 https://github.com/gliderlabs/docker-alpine/issues/359 https://github.com/kontena/pharos-cluster/pull/81 Signed-off-by: Euan Harris <euan.harris@docker.com> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
		
			
				
	
	
		
			268 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* vi: set sw=4 ts=4: */
 | 
						|
/*
 | 
						|
 * Mini nsenter implementation for busybox.
 | 
						|
 *
 | 
						|
 * Copyright (C) 2016 by Bartosz Golaszewski <bartekgola@gmail.com>
 | 
						|
 *
 | 
						|
 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
 | 
						|
 */
 | 
						|
//config:config NSENTER
 | 
						|
//config:	bool "nsenter (8.6 kb)"
 | 
						|
//config:	default y
 | 
						|
//config:	select PLATFORM_LINUX
 | 
						|
//config:	help
 | 
						|
//config:	Run program with namespaces of other processes.
 | 
						|
 | 
						|
//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] [PROG [ARGS]]"
 | 
						|
//usage:#define nsenter_full_usage "\n"
 | 
						|
//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:	IF_LONG_OPTS(
 | 
						|
//usage:     "\n	--preserve-credentials	Don't touch uids or gids"
 | 
						|
//usage:	)
 | 
						|
//usage:     "\n	-r[DIR]		Set root directory"
 | 
						|
//usage:     "\n	-w[DIR]		Set working directory"
 | 
						|
//usage:     "\n	-F		Don't fork before exec'ing PROG"
 | 
						|
 | 
						|
#include <sched.h>
 | 
						|
#ifndef CLONE_NEWUTS
 | 
						|
# define CLONE_NEWUTS  0x04000000
 | 
						|
#endif
 | 
						|
#ifndef CLONE_NEWIPC
 | 
						|
# define CLONE_NEWIPC  0x08000000
 | 
						|
#endif
 | 
						|
#ifndef CLONE_NEWUSER
 | 
						|
# define CLONE_NEWUSER 0x10000000
 | 
						|
#endif
 | 
						|
#ifndef CLONE_NEWPID
 | 
						|
# define CLONE_NEWPID  0x20000000
 | 
						|
#endif
 | 
						|
#ifndef CLONE_NEWNET
 | 
						|
# define CLONE_NEWNET  0x40000000
 | 
						|
#endif
 | 
						|
 | 
						|
#include "libbb.h"
 | 
						|
 | 
						|
struct namespace_descr {
 | 
						|
	int flag;		/* value passed to setns() */
 | 
						|
	char ns_nsfile8[8];	/* "ns/" + namespace file in process' procfs entry */
 | 
						|
};
 | 
						|
 | 
						|
struct namespace_ctx {
 | 
						|
	char *path;		/* optional path to a custom ns file */
 | 
						|
	int fd;			/* opened namespace file descriptor */
 | 
						|
};
 | 
						|
 | 
						|
enum {
 | 
						|
	OPT_user	= 1 << 0,
 | 
						|
	OPT_ipc		= 1 << 1,
 | 
						|
	OPT_uts		= 1 << 2,
 | 
						|
	OPT_network	= 1 << 3,
 | 
						|
	OPT_pid		= 1 << 4,
 | 
						|
	OPT_mount	= 1 << 5,
 | 
						|
	OPT_target	= 1 << 6,
 | 
						|
	OPT_setuid	= 1 << 7,
 | 
						|
	OPT_setgid	= 1 << 8,
 | 
						|
	OPT_root	= 1 << 9,
 | 
						|
	OPT_wd		= 1 << 10,
 | 
						|
	OPT_nofork	= 1 << 11,
 | 
						|
	OPT_prescred	= (1 << 12) * ENABLE_LONG_OPTS,
 | 
						|
};
 | 
						|
enum {
 | 
						|
	NS_USR_POS = 0,
 | 
						|
	NS_IPC_POS,
 | 
						|
	NS_UTS_POS,
 | 
						|
	NS_NET_POS,
 | 
						|
	NS_PID_POS,
 | 
						|
	NS_MNT_POS,
 | 
						|
	NS_COUNT,
 | 
						|
};
 | 
						|
/*
 | 
						|
 * 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 other namespaces.
 | 
						|
 */
 | 
						|
static const struct namespace_descr ns_list[] = {
 | 
						|
	{ CLONE_NEWUSER, "ns/user", },
 | 
						|
	{ CLONE_NEWIPC,  "ns/ipc",  },
 | 
						|
	{ CLONE_NEWUTS,  "ns/uts",  },
 | 
						|
	{ CLONE_NEWNET,  "ns/net",  },
 | 
						|
	{ CLONE_NEWPID,  "ns/pid",  },
 | 
						|
	{ CLONE_NEWNS,   "ns/mnt",  },
 | 
						|
};
 | 
						|
/*
 | 
						|
 * Upstream nsenter doesn't support the short option for --preserve-credentials
 | 
						|
 */
 | 
						|
static const char opt_str[] ALIGN1 = "U::i::u::n::p::m::""t:+S:+G:+r::w::F";
 | 
						|
 | 
						|
#if ENABLE_LONG_OPTS
 | 
						|
static const char nsenter_longopts[] ALIGN1 =
 | 
						|
	"user\0"			Optional_argument	"U"
 | 
						|
	"ipc\0"				Optional_argument	"i"
 | 
						|
	"uts\0"				Optional_argument	"u"
 | 
						|
	"net\0"				Optional_argument	"n"
 | 
						|
	"pid\0"				Optional_argument	"p"
 | 
						|
	"mount\0"			Optional_argument	"m"
 | 
						|
	"target\0"			Required_argument	"t"
 | 
						|
	"setuid\0"			Required_argument	"S"
 | 
						|
	"setgid\0"			Required_argument	"G"
 | 
						|
	"root\0"			Optional_argument	"r"
 | 
						|
	"wd\0"				Optional_argument	"w"
 | 
						|
	"no-fork\0"			No_argument		"F"
 | 
						|
	"preserve-credentials\0"	No_argument		"\xff"
 | 
						|
	;
 | 
						|
#endif
 | 
						|
 | 
						|
/*
 | 
						|
 * 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 *path,
 | 
						|
				  pid_t target_pid, const char *target_file)
 | 
						|
{
 | 
						|
	char proc_path_buf[sizeof("/proc/%u/1234567890") + sizeof(int)*3];
 | 
						|
 | 
						|
	if (!path) {
 | 
						|
		if (target_pid == 0) {
 | 
						|
			/* Example:
 | 
						|
			 * "nsenter -p PROG" - neither -pFILE nor -tPID given.
 | 
						|
			 */
 | 
						|
			bb_show_usage();
 | 
						|
		}
 | 
						|
		snprintf(proc_path_buf, sizeof(proc_path_buf),
 | 
						|
			 "/proc/%u/%s", (unsigned)target_pid, target_file);
 | 
						|
		path = proc_path_buf;
 | 
						|
	}
 | 
						|
 | 
						|
	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;
 | 
						|
	unsigned int opts;
 | 
						|
	const char *root_dir_str = NULL;
 | 
						|
	const char *wd_str = NULL;
 | 
						|
	struct namespace_ctx ns_ctx_list[NS_COUNT];
 | 
						|
	int setgroups_failed;
 | 
						|
	int root_fd, wd_fd;
 | 
						|
	int target_pid = 0;
 | 
						|
	int uid = 0;
 | 
						|
	int gid = 0;
 | 
						|
 | 
						|
	memset(ns_ctx_list, 0, sizeof(ns_ctx_list));
 | 
						|
 | 
						|
	opts = getopt32long(argv, opt_str, nsenter_longopts,
 | 
						|
			&ns_ctx_list[NS_USR_POS].path,
 | 
						|
			&ns_ctx_list[NS_IPC_POS].path,
 | 
						|
			&ns_ctx_list[NS_UTS_POS].path,
 | 
						|
			&ns_ctx_list[NS_NET_POS].path,
 | 
						|
			&ns_ctx_list[NS_PID_POS].path,
 | 
						|
			&ns_ctx_list[NS_MNT_POS].path,
 | 
						|
			&target_pid, &uid, &gid,
 | 
						|
			&root_dir_str, &wd_str
 | 
						|
	);
 | 
						|
	argv += optind;
 | 
						|
 | 
						|
	root_fd = wd_fd = -1;
 | 
						|
	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");
 | 
						|
 | 
						|
	for (i = 0; i < NS_COUNT; i++) {
 | 
						|
		const struct namespace_descr *ns = &ns_list[i];
 | 
						|
		struct namespace_ctx *ns_ctx = &ns_ctx_list[i];
 | 
						|
 | 
						|
		ns_ctx->fd = -1;
 | 
						|
		if (opts & (1 << i))
 | 
						|
			ns_ctx->fd = open_by_path_or_target(ns_ctx->path,
 | 
						|
					target_pid, ns->ns_nsfile8);
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Entering the user namespace without --preserve-credentials implies
 | 
						|
	 * --setuid & --setgid and clearing root's groups.
 | 
						|
	 */
 | 
						|
	setgroups_failed = 0;
 | 
						|
	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.
 | 
						|
		 */
 | 
						|
		setgroups_failed = (setgroups(0, NULL) < 0);
 | 
						|
	}
 | 
						|
 | 
						|
	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;
 | 
						|
		if (setns(ns_ctx->fd, ns->flag)) {
 | 
						|
			bb_perror_msg_and_die(
 | 
						|
				"setns(): can't reassociate to namespace '%s'",
 | 
						|
				ns->ns_nsfile8 + 3 /* skip over "ns/" */
 | 
						|
			);
 | 
						|
		}
 | 
						|
		close(ns_ctx->fd); /* should close fds, to not confuse exec'ed PROG */
 | 
						|
		/*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)) {
 | 
						|
		xvfork_parent_waits_and_exits();
 | 
						|
		/* Child continues */
 | 
						|
	}
 | 
						|
 | 
						|
	if (opts & OPT_setgid) {
 | 
						|
		if (setgroups(0, NULL) < 0 && setgroups_failed)
 | 
						|
			bb_perror_msg_and_die("setgroups");
 | 
						|
		xsetgid(gid);
 | 
						|
	}
 | 
						|
	if (opts & OPT_setuid)
 | 
						|
		xsetuid(uid);
 | 
						|
 | 
						|
	exec_prog_or_SHELL(argv);
 | 
						|
}
 |