(root)/
util-linux-2.39/
sys-utils/
nsenter.c
       1  /*
       2   * nsenter(1) - command-line interface for setns(2)
       3   *
       4   * Copyright (C) 2012-2013 Eric Biederman <ebiederm@xmission.com>
       5   *
       6   * This program is free software; you can redistribute it and/or modify it
       7   * under the terms of the GNU General Public License as published by the
       8   * Free Software Foundation; version 2.
       9   *
      10   * This program is distributed in the hope that it will be useful, but
      11   * WITHOUT ANY WARRANTY; without even the implied warranty of
      12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13   * General Public License for more details.
      14   *
      15   * You should have received a copy of the GNU General Public License along
      16   * with this program; if not, write to the Free Software Foundation, Inc.,
      17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      18   */
      19  
      20  #include <dirent.h>
      21  #include <errno.h>
      22  #include <getopt.h>
      23  #include <sched.h>
      24  #include <stdio.h>
      25  #include <stdlib.h>
      26  #include <stdbool.h>
      27  #include <unistd.h>
      28  #include <assert.h>
      29  #include <sys/types.h>
      30  #include <sys/wait.h>
      31  #include <grp.h>
      32  #include <sys/stat.h>
      33  
      34  #ifdef HAVE_LIBSELINUX
      35  # include <selinux/selinux.h>
      36  #endif
      37  
      38  #include "strutils.h"
      39  #include "nls.h"
      40  #include "c.h"
      41  #include "closestream.h"
      42  #include "namespace.h"
      43  #include "exec_shell.h"
      44  #include "optutils.h"
      45  #include "xalloc.h"
      46  #include "all-io.h"
      47  #include "env.h"
      48  
      49  static struct namespace_file {
      50  	int nstype;
      51  	const char *name;
      52  	int fd;
      53  } namespace_files[] = {
      54  	/* Careful the order is significant in this array.
      55  	 *
      56  	 * The user namespace comes either first or last: first if
      57  	 * you're using it to increase your privilege and last if
      58  	 * you're using it to decrease.  We enter the namespaces in
      59  	 * two passes starting initially from offset 1 and then offset
      60  	 * 0 if that fails.
      61  	 */
      62  	{ .nstype = CLONE_NEWUSER,  .name = "ns/user", .fd = -1 },
      63  	{ .nstype = CLONE_NEWCGROUP,.name = "ns/cgroup", .fd = -1 },
      64  	{ .nstype = CLONE_NEWIPC,   .name = "ns/ipc",  .fd = -1 },
      65  	{ .nstype = CLONE_NEWUTS,   .name = "ns/uts",  .fd = -1 },
      66  	{ .nstype = CLONE_NEWNET,   .name = "ns/net",  .fd = -1 },
      67  	{ .nstype = CLONE_NEWPID,   .name = "ns/pid",  .fd = -1 },
      68  	{ .nstype = CLONE_NEWNS,    .name = "ns/mnt",  .fd = -1 },
      69  	{ .nstype = CLONE_NEWTIME,  .name = "ns/time", .fd = -1 },
      70  	{ .nstype = 0, .name = NULL, .fd = -1 }
      71  };
      72  
      73  static void __attribute__((__noreturn__)) usage(void)
      74  {
      75  	FILE *out = stdout;
      76  
      77  	fputs(USAGE_HEADER, out);
      78  	fprintf(out, _(" %s [options] [<program> [<argument>...]]\n"),
      79  		program_invocation_short_name);
      80  
      81  	fputs(USAGE_SEPARATOR, out);
      82  	fputs(_("Run a program with namespaces of other processes.\n"), out);
      83  
      84  	fputs(USAGE_OPTIONS, out);
      85  	fputs(_(" -a, --all              enter all namespaces\n"), out);
      86  	fputs(_(" -t, --target <pid>     target process to get namespaces from\n"), out);
      87  	fputs(_(" -m, --mount[=<file>]   enter mount namespace\n"), out);
      88  	fputs(_(" -u, --uts[=<file>]     enter UTS namespace (hostname etc)\n"), out);
      89  	fputs(_(" -i, --ipc[=<file>]     enter System V IPC namespace\n"), out);
      90  	fputs(_(" -n, --net[=<file>]     enter network namespace\n"), out);
      91  	fputs(_(" -p, --pid[=<file>]     enter pid namespace\n"), out);
      92  	fputs(_(" -C, --cgroup[=<file>]  enter cgroup namespace\n"), out);
      93  	fputs(_(" -U, --user[=<file>]    enter user namespace\n"), out);
      94  	fputs(_(" -T, --time[=<file>]    enter time namespace\n"), out);
      95  	fputs(_(" -S, --setuid[=<uid>]   set uid in entered namespace\n"), out);
      96  	fputs(_(" -G, --setgid[=<gid>]   set gid in entered namespace\n"), out);
      97  	fputs(_("     --preserve-credentials do not touch uids or gids\n"), out);
      98  	fputs(_(" -r, --root[=<dir>]     set the root directory\n"), out);
      99  	fputs(_(" -w, --wd[=<dir>]       set the working directory\n"), out);
     100  	fputs(_(" -W, --wdns <dir>       set the working directory in namespace\n"), out);
     101  	fputs(_(" -e, --env              inherit environment variables from target process\n"), out);
     102  	fputs(_(" -F, --no-fork          do not fork before exec'ing <program>\n"), out);
     103  #ifdef HAVE_LIBSELINUX
     104  	fputs(_(" -Z, --follow-context   set SELinux context according to --target PID\n"), out);
     105  #endif
     106  
     107  	fputs(USAGE_SEPARATOR, out);
     108  	printf(USAGE_HELP_OPTIONS(24));
     109  	printf(USAGE_MAN_TAIL("nsenter(1)"));
     110  
     111  	exit(EXIT_SUCCESS);
     112  }
     113  
     114  static pid_t namespace_target_pid = 0;
     115  static int root_fd = -1;
     116  static int wd_fd = -1;
     117  static int env_fd = -1;
     118  static int uid_gid_fd = -1;
     119  
     120  static void open_target_fd(int *fd, const char *type, const char *path)
     121  {
     122  	char pathbuf[PATH_MAX];
     123  
     124  	if (!path && namespace_target_pid) {
     125  		snprintf(pathbuf, sizeof(pathbuf), "/proc/%u/%s",
     126  			 namespace_target_pid, type);
     127  		path = pathbuf;
     128  	}
     129  	if (!path)
     130  		errx(EXIT_FAILURE,
     131  		     _("neither filename nor target pid supplied for %s"),
     132  		     type);
     133  
     134  	if (*fd >= 0)
     135  		close(*fd);
     136  
     137  	*fd = open(path, O_RDONLY);
     138  	if (*fd < 0)
     139  		err(EXIT_FAILURE, _("cannot open %s"), path);
     140  }
     141  
     142  static void open_namespace_fd(int nstype, const char *path)
     143  {
     144  	struct namespace_file *nsfile;
     145  
     146  	for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
     147  		if (nstype != nsfile->nstype)
     148  			continue;
     149  
     150  		open_target_fd(&nsfile->fd, nsfile->name, path);
     151  		return;
     152  	}
     153  	/* This should never happen */
     154  	assert(nsfile->nstype);
     155  }
     156  
     157  static int get_ns_ino(const char *path, ino_t *ino)
     158  {
     159  	struct stat st;
     160  
     161  	if (stat(path, &st) != 0)
     162  		return -errno;
     163  	*ino = st.st_ino;
     164  	return 0;
     165  }
     166  
     167  static int is_usable_namespace(pid_t target, const struct namespace_file *nsfile)
     168  {
     169  	char path[PATH_MAX];
     170  	ino_t my_ino = 0;
     171  	int rc;
     172  
     173  	/* Check NS accessibility */
     174  	snprintf(path, sizeof(path), "/proc/%u/%s", getpid(), nsfile->name);
     175  	rc = get_ns_ino(path, &my_ino);
     176  	if (rc == -ENOENT)
     177  		return false; /* Unsupported NS */
     178  
     179  	/* It is not permitted to use setns(2) to reenter the caller's
     180  	 * current user namespace; see setns(2) man page for more details.
     181  	 */
     182  	if (nsfile->nstype & CLONE_NEWUSER) {
     183  		ino_t target_ino = 0;
     184  
     185  		snprintf(path, sizeof(path), "/proc/%u/%s", target, nsfile->name);
     186  		if (get_ns_ino(path, &target_ino) != 0)
     187  			err(EXIT_FAILURE, _("stat of %s failed"), path);
     188  
     189  		if (my_ino == target_ino)
     190  			return false;
     191  	}
     192  
     193  	return true; /* All pass */
     194  }
     195  
     196  static void continue_as_child(void)
     197  {
     198  	pid_t child;
     199  	int status;
     200  	pid_t ret;
     201  
     202  	/* Clear any inherited settings */
     203  	signal(SIGCHLD, SIG_DFL);
     204  
     205  	child = fork();
     206  	if (child < 0)
     207  		err(EXIT_FAILURE, _("fork failed"));
     208  
     209  	/* Only the child returns */
     210  	if (child == 0)
     211  		return;
     212  
     213  	for (;;) {
     214  		ret = waitpid(child, &status, WUNTRACED);
     215  		if ((ret == child) && (WIFSTOPPED(status))) {
     216  			/* The child suspended so suspend us as well */
     217  			kill(getpid(), SIGSTOP);
     218  			kill(child, SIGCONT);
     219  		} else {
     220  			break;
     221  		}
     222  	}
     223  	/* Return the child's exit code if possible */
     224  	if (WIFEXITED(status)) {
     225  		exit(WEXITSTATUS(status));
     226  	} else if (WIFSIGNALED(status)) {
     227  		kill(getpid(), WTERMSIG(status));
     228  	}
     229  	exit(EXIT_FAILURE);
     230  }
     231  
     232  int main(int argc, char *argv[])
     233  {
     234  	enum {
     235  		OPT_PRESERVE_CRED = CHAR_MAX + 1
     236  	};
     237  	static const struct option longopts[] = {
     238  		{ "all", no_argument, NULL, 'a' },
     239  		{ "help", no_argument, NULL, 'h' },
     240  		{ "version", no_argument, NULL, 'V'},
     241  		{ "target", required_argument, NULL, 't' },
     242  		{ "mount", optional_argument, NULL, 'm' },
     243  		{ "uts", optional_argument, NULL, 'u' },
     244  		{ "ipc", optional_argument, NULL, 'i' },
     245  		{ "net", optional_argument, NULL, 'n' },
     246  		{ "pid", optional_argument, NULL, 'p' },
     247  		{ "user", optional_argument, NULL, 'U' },
     248  		{ "cgroup", optional_argument, NULL, 'C' },
     249  		{ "time", optional_argument, NULL, 'T' },
     250  		{ "setuid", required_argument, NULL, 'S' },
     251  		{ "setgid", required_argument, NULL, 'G' },
     252  		{ "root", optional_argument, NULL, 'r' },
     253  		{ "wd", optional_argument, NULL, 'w' },
     254  		{ "wdns", optional_argument, NULL, 'W' },
     255  		{ "env", no_argument, NULL, 'e' },
     256  		{ "no-fork", no_argument, NULL, 'F' },
     257  		{ "preserve-credentials", no_argument, NULL, OPT_PRESERVE_CRED },
     258  #ifdef HAVE_LIBSELINUX
     259  		{ "follow-context", no_argument, NULL, 'Z' },
     260  #endif
     261  		{ NULL, 0, NULL, 0 }
     262  	};
     263  	static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
     264  		{ 'W', 'w' },
     265  		{ 0 }
     266  	};
     267  	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
     268  
     269  	struct namespace_file *nsfile;
     270  	int c, pass, namespaces = 0, setgroups_nerrs = 0, preserve_cred = 0;
     271  	bool do_rd = false, do_wd = false, do_uid = false, force_uid = false,
     272  	     do_gid = false, force_gid = false, do_env = false, do_all = false;
     273  	int do_fork = -1; /* unknown yet */
     274  	char *wdns = NULL;
     275  	uid_t uid = 0;
     276  	gid_t gid = 0;
     277  	struct ul_env_list *envls;
     278  #ifdef HAVE_LIBSELINUX
     279  	bool selinux = 0;
     280  #endif
     281  
     282  	setlocale(LC_ALL, "");
     283  	bindtextdomain(PACKAGE, LOCALEDIR);
     284  	textdomain(PACKAGE);
     285  	close_stdout_atexit();
     286  
     287  	while ((c =
     288  		getopt_long(argc, argv, "+ahVt:m::u::i::n::p::C::U::T::S:G:r::w::W::eFZ",
     289  			    longopts, NULL)) != -1) {
     290  
     291  		err_exclusive_options(c, longopts, excl, excl_st);
     292  
     293  		switch (c) {
     294  		case 'a':
     295  			do_all = true;
     296  			break;
     297  		case 't':
     298  			namespace_target_pid =
     299  			    strtoul_or_err(optarg, _("failed to parse pid"));
     300  			break;
     301  		case 'm':
     302  			if (optarg)
     303  				open_namespace_fd(CLONE_NEWNS, optarg);
     304  			else
     305  				namespaces |= CLONE_NEWNS;
     306  			break;
     307  		case 'u':
     308  			if (optarg)
     309  				open_namespace_fd(CLONE_NEWUTS, optarg);
     310  			else
     311  				namespaces |= CLONE_NEWUTS;
     312  			break;
     313  		case 'i':
     314  			if (optarg)
     315  				open_namespace_fd(CLONE_NEWIPC, optarg);
     316  			else
     317  				namespaces |= CLONE_NEWIPC;
     318  			break;
     319  		case 'n':
     320  			if (optarg)
     321  				open_namespace_fd(CLONE_NEWNET, optarg);
     322  			else
     323  				namespaces |= CLONE_NEWNET;
     324  			break;
     325  		case 'p':
     326  			if (optarg)
     327  				open_namespace_fd(CLONE_NEWPID, optarg);
     328  			else
     329  				namespaces |= CLONE_NEWPID;
     330  			break;
     331  		case 'C':
     332  			if (optarg)
     333  				open_namespace_fd(CLONE_NEWCGROUP, optarg);
     334  			else
     335  				namespaces |= CLONE_NEWCGROUP;
     336  			break;
     337  		case 'U':
     338  			if (optarg)
     339  				open_namespace_fd(CLONE_NEWUSER, optarg);
     340  			else
     341  				namespaces |= CLONE_NEWUSER;
     342  			break;
     343  		case 'T':
     344  			if (optarg)
     345  				open_namespace_fd(CLONE_NEWTIME, optarg);
     346  			else
     347  				namespaces |= CLONE_NEWTIME;
     348  			break;
     349  		case 'S':
     350  			if (strcmp(optarg, "follow") == 0)
     351  				do_uid = true;
     352  			else
     353  				uid = strtoul_or_err(optarg, _("failed to parse uid"));
     354  			force_uid = true;
     355  			break;
     356  		case 'G':
     357  			if (strcmp(optarg, "follow") == 0)
     358  				do_gid = true;
     359  			else
     360  				gid = strtoul_or_err(optarg, _("failed to parse gid"));
     361  			force_gid = true;
     362  			break;
     363  		case 'F':
     364  			do_fork = 0;
     365  			break;
     366  		case 'r':
     367  			if (optarg)
     368  				open_target_fd(&root_fd, "root", optarg);
     369  			else
     370  				do_rd = true;
     371  			break;
     372  		case 'w':
     373  			if (optarg)
     374  				open_target_fd(&wd_fd, "cwd", optarg);
     375  			else
     376  				do_wd = true;
     377  			break;
     378  		case 'W':
     379  			wdns = optarg;
     380  			break;
     381  		case 'e':
     382  			do_env = true;
     383  			break;
     384  		case OPT_PRESERVE_CRED:
     385  			preserve_cred = 1;
     386  			break;
     387  #ifdef HAVE_LIBSELINUX
     388  		case 'Z':
     389  			selinux = 1;
     390  			break;
     391  #endif
     392  		case 'h':
     393  			usage();
     394  		case 'V':
     395  			print_version(EXIT_SUCCESS);
     396  		default:
     397  			errtryhelp(EXIT_FAILURE);
     398  		}
     399  	}
     400  
     401  #ifdef HAVE_LIBSELINUX
     402  	if (selinux && is_selinux_enabled() > 0) {
     403  		char *scon = NULL;
     404  
     405  		if (!namespace_target_pid)
     406  			errx(EXIT_FAILURE, _("no target PID specified for --follow-context"));
     407  		if (getpidcon(namespace_target_pid, &scon) < 0)
     408  			errx(EXIT_FAILURE, _("failed to get %d SELinux context"),
     409  					(int) namespace_target_pid);
     410  		if (setexeccon(scon) < 0)
     411  			errx(EXIT_FAILURE, _("failed to set exec context to '%s'"), scon);
     412  		freecon(scon);
     413  	}
     414  #endif
     415  
     416  	if (do_all) {
     417  		if (!namespace_target_pid)
     418  			errx(EXIT_FAILURE, _("no target PID specified for --all"));
     419  		for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
     420  			if (nsfile->fd >= 0)
     421  				continue;	/* namespace already specified */
     422  
     423  			if (!is_usable_namespace(namespace_target_pid, nsfile))
     424  				continue;
     425  
     426  			namespaces |= nsfile->nstype;
     427  		}
     428  	}
     429  
     430  	/*
     431  	 * Open remaining namespace and directory descriptors.
     432  	 */
     433  	for (nsfile = namespace_files; nsfile->nstype; nsfile++)
     434  		if (nsfile->nstype & namespaces)
     435  			open_namespace_fd(nsfile->nstype, NULL);
     436  	if (do_rd)
     437  		open_target_fd(&root_fd, "root", NULL);
     438  	if (do_wd)
     439  		open_target_fd(&wd_fd, "cwd", NULL);
     440  	if (do_env)
     441  		open_target_fd(&env_fd, "environ", NULL);
     442  	if (do_uid || do_gid)
     443  		open_target_fd(&uid_gid_fd, "", NULL);
     444  
     445  	/*
     446  	 * Update namespaces variable to contain all requested namespaces
     447  	 */
     448  	for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
     449  		if (nsfile->fd < 0)
     450  			continue;
     451  		namespaces |= nsfile->nstype;
     452  	}
     453  
     454  	/* for user namespaces we always set UID and GID (default is 0)
     455  	 * and clear root's groups if --preserve-credentials is no specified */
     456  	if ((namespaces & CLONE_NEWUSER) && !preserve_cred) {
     457  		force_uid = true, force_gid = true;
     458  
     459  		/* We call setgroups() before and after we enter user namespace,
     460  		 * let's complain only if both fail */
     461  		if (setgroups(0, NULL) != 0)
     462  			setgroups_nerrs++;
     463  	}
     464  
     465  	/*
     466  	 * Now that we know which namespaces we want to enter, enter
     467  	 * them.  Do this in two passes, not entering the user
     468  	 * namespace on the first pass.  So if we're deprivileging the
     469  	 * container we'll enter the user namespace last and if we're
     470  	 * privileging it then we enter the user namespace first
     471  	 * (because the initial setns will fail).
     472  	 */
     473  	for (pass = 0; pass < 2; pass ++) {
     474  		for (nsfile = namespace_files + 1 - pass; nsfile->nstype; nsfile++) {
     475  			if (nsfile->fd < 0)
     476  				continue;
     477  			if (nsfile->nstype == CLONE_NEWPID && do_fork == -1)
     478  				do_fork = 1;
     479  			if (setns(nsfile->fd, nsfile->nstype)) {
     480  				if (pass != 0)
     481  					err(EXIT_FAILURE,
     482  					    _("reassociate to namespace '%s' failed"),
     483  					    nsfile->name);
     484  				else
     485  					continue;
     486  			}
     487  
     488  			close(nsfile->fd);
     489  			nsfile->fd = -1;
     490  		}
     491  	}
     492  
     493  	/* Remember the current working directory if I'm not changing it */
     494  	if (root_fd >= 0 && wd_fd < 0 && wdns == NULL) {
     495  		wd_fd = open(".", O_RDONLY);
     496  		if (wd_fd < 0)
     497  			err(EXIT_FAILURE,
     498  			    _("cannot open current working directory"));
     499  	}
     500  
     501  	/* Change the root directory */
     502  	if (root_fd >= 0) {
     503  		if (fchdir(root_fd) < 0)
     504  			err(EXIT_FAILURE,
     505  			    _("change directory by root file descriptor failed"));
     506  
     507  		if (chroot(".") < 0)
     508  			err(EXIT_FAILURE, _("chroot failed"));
     509  		if (chdir("/"))
     510  			err(EXIT_FAILURE, _("cannot change directory to %s"), "/");
     511  
     512  		close(root_fd);
     513  		root_fd = -1;
     514  	}
     515  
     516  	/* working directory specified as in-namespace path */
     517  	if (wdns) {
     518  		wd_fd = open(wdns, O_RDONLY);
     519  		if (wd_fd < 0)
     520  			err(EXIT_FAILURE,
     521  			    _("cannot open current working directory"));
     522  	}
     523  
     524  	/* Change the working directory */
     525  	if (wd_fd >= 0) {
     526  		if (fchdir(wd_fd) < 0)
     527  			err(EXIT_FAILURE,
     528  			    _("change directory by working directory file descriptor failed"));
     529  
     530  		close(wd_fd);
     531  		wd_fd = -1;
     532  	}
     533  
     534  	/* Pass environment variables of the target process to the spawned process */
     535  	if (env_fd >= 0) {
     536  		if ((envls = env_from_fd(env_fd)) == NULL)
     537  			err(EXIT_FAILURE, _("failed to get environment variables"));
     538  		clearenv();
     539  		if (env_list_setenv(envls) < 0)
     540  			err(EXIT_FAILURE, _("failed to set environment variables"));
     541  		env_list_free(envls);
     542  		close(env_fd);
     543  	}
     544  
     545  	if (uid_gid_fd >= 0) {
     546  		struct stat st;
     547  
     548  		if (fstat(uid_gid_fd, &st) > 0)
     549  			err(EXIT_FAILURE, _("can not get process stat"));
     550  
     551  		close(uid_gid_fd);
     552  		uid_gid_fd = -1;
     553  
     554  		if (do_uid)
     555  			uid = st.st_uid;
     556  		if (do_gid)
     557  			gid = st.st_gid;
     558  	}
     559  
     560  	if (do_fork == 1)
     561  		continue_as_child();
     562  
     563  	if (force_uid || force_gid) {
     564  		if (force_gid && setgroups(0, NULL) != 0 && setgroups_nerrs)	/* drop supplementary groups */
     565  			err(EXIT_FAILURE, _("setgroups failed"));
     566  		if (force_gid && setgid(gid) < 0)		/* change GID */
     567  			err(EXIT_FAILURE, _("setgid failed"));
     568  		if (force_uid && setuid(uid) < 0)		/* change UID */
     569  			err(EXIT_FAILURE, _("setuid failed"));
     570  	}
     571  
     572  	if (optind < argc) {
     573  		execvp(argv[optind], argv + optind);
     574  		errexec(argv[optind]);
     575  	}
     576  	exec_shell();
     577  }