Home | History | Annotate | Download | only in sandbox
      1 /*
      2  * Authors: Dan Walsh <dwalsh (at) redhat.com>
      3  * Authors: Thomas Liu <tliu (at) fedoraproject.org>
      4  */
      5 
      6 #define _GNU_SOURCE
      7 #include <signal.h>
      8 #include <sys/fsuid.h>
      9 #include <sys/stat.h>
     10 #include <sys/types.h>
     11 #include <sys/wait.h>
     12 #include <syslog.h>
     13 #include <sys/mount.h>
     14 #include <glob.h>
     15 #include <pwd.h>
     16 #include <sched.h>
     17 #include <string.h>
     18 #include <stdio.h>
     19 #include <regex.h>
     20 #include <unistd.h>
     21 #include <stdlib.h>
     22 #include <cap-ng.h>
     23 #include <getopt.h>		/* for getopt_long() form of getopt() */
     24 #include <limits.h>
     25 #include <stdlib.h>
     26 #include <errno.h>
     27 #include <fcntl.h>
     28 
     29 #include <selinux/selinux.h>
     30 #include <selinux/context.h>	/* for context-mangling functions */
     31 #include <dirent.h>
     32 
     33 #ifdef USE_NLS
     34 #include <locale.h>		/* for setlocale() */
     35 #include <libintl.h>		/* for gettext() */
     36 #define _(msgid) gettext (msgid)
     37 #else
     38 #define _(msgid) (msgid)
     39 #endif
     40 
     41 #ifndef MS_REC
     42 #define MS_REC 1<<14
     43 #endif
     44 
     45 #ifndef MS_SLAVE
     46 #define MS_SLAVE 1<<19
     47 #endif
     48 
     49 #ifndef PACKAGE
     50 #define PACKAGE "policycoreutils"	/* the name of this package lang translation */
     51 #endif
     52 
     53 #define BUF_SIZE 1024
     54 #define DEFAULT_PATH "/usr/bin:/bin"
     55 #define USAGE_STRING _("USAGE: seunshare [ -v ] [ -C ] [ -k ] [ -t tmpdir ] [ -h homedir ] [ -Z CONTEXT ] -- executable [args] ")
     56 
     57 static int verbose = 0;
     58 static int child = 0;
     59 
     60 static capng_select_t cap_set = CAPNG_SELECT_CAPS;
     61 
     62 /**
     63  * This function will drop all capabilities.
     64  */
     65 static int drop_caps(void)
     66 {
     67 	if (capng_have_capabilities(cap_set) == CAPNG_NONE)
     68 		return 0;
     69 	capng_clear(cap_set);
     70 	if (capng_lock() == -1 || capng_apply(cap_set) == -1) {
     71 		fprintf(stderr, _("Failed to drop all capabilities\n"));
     72 		return -1;
     73 	}
     74 	return 0;
     75 }
     76 
     77 /**
     78  * This function will drop all privileges.
     79  */
     80 static int drop_privs(uid_t uid)
     81 {
     82 	if (drop_caps() == -1 || setresuid(uid, uid, uid) == -1) {
     83 		fprintf(stderr, _("Failed to drop privileges\n"));
     84 		return -1;
     85 	}
     86 	return 0;
     87 }
     88 
     89 /**
     90  * If the user sends a siginto to seunshare, kill the child's session
     91  */
     92 void handler(int sig) {
     93 	if (child > 0) kill(-child,sig);
     94 }
     95 
     96 /**
     97  * Take care of any signal setup.
     98  */
     99 static int set_signal_handles(void)
    100 {
    101 	sigset_t empty;
    102 
    103 	/* Empty the signal mask in case someone is blocking a signal */
    104 	if (sigemptyset(&empty)) {
    105 		fprintf(stderr, "Unable to obtain empty signal set\n");
    106 		return -1;
    107 	}
    108 
    109 	(void)sigprocmask(SIG_SETMASK, &empty, NULL);
    110 
    111 	/* Terminate on SIGHUP */
    112 	if (signal(SIGHUP, SIG_DFL) == SIG_ERR) {
    113 		perror("Unable to set SIGHUP handler");
    114 		return -1;
    115 	}
    116 
    117 	if (signal(SIGINT, handler) == SIG_ERR) {
    118 		perror("Unable to set SIGINT handler");
    119 		return -1;
    120 	}
    121 
    122 	return 0;
    123 }
    124 
    125 #define status_to_retval(status,retval) do { \
    126 	if ((status) == -1) \
    127 		retval = -1; \
    128 	else if (WIFEXITED((status))) \
    129 		retval = WEXITSTATUS((status)); \
    130 	else if (WIFSIGNALED((status))) \
    131 		retval = 128 + WTERMSIG((status)); \
    132 	else \
    133 		retval = -1; \
    134 	} while(0)
    135 
    136 /**
    137  * Spawn external command using system() with dropped privileges.
    138  * TODO: avoid system() and use exec*() instead
    139  */
    140 static int spawn_command(const char *cmd, uid_t uid){
    141 	int childpid;
    142 	int status = -1;
    143 
    144 	if (verbose > 1)
    145 		printf("spawn_command: %s\n", cmd);
    146 
    147 	childpid = fork();
    148 	if (childpid == -1) {
    149 		perror(_("Unable to fork"));
    150 		return status;
    151 	}
    152 
    153 	if (childpid == 0) {
    154 		if (drop_privs(uid) != 0) exit(-1);
    155 
    156 		status = system(cmd);
    157 		status_to_retval(status, status);
    158 		exit(status);
    159 	}
    160 
    161 	waitpid(childpid, &status, 0);
    162 	status_to_retval(status, status);
    163 	return status;
    164 }
    165 
    166 /**
    167  * Check file/directory ownership, struct stat * must be passed to the
    168  * functions.
    169  */
    170 static int check_owner_uid(uid_t uid, const char *file, struct stat *st) {
    171 	if (S_ISLNK(st->st_mode)) {
    172 		fprintf(stderr, _("Error: %s must not be a symbolic link\n"), file);
    173 		return -1;
    174 	}
    175 	if (st->st_uid != uid) {
    176 		fprintf(stderr, _("Error: %s not owned by UID %d\n"), file, uid);
    177 		return -1;
    178 	}
    179 	return 0;
    180 }
    181 
    182 static int check_owner_gid(gid_t gid, const char *file, struct stat *st) {
    183 	if (S_ISLNK(st->st_mode)) {
    184 		fprintf(stderr, _("Error: %s must not be a symbolic link\n"), file);
    185 		return -1;
    186 	}
    187 	if (st->st_gid != gid) {
    188 		fprintf(stderr, _("Error: %s not owned by GID %d\n"), file, gid);
    189 		return -1;
    190 	}
    191 	return 0;
    192 }
    193 
    194 #define equal_stats(one,two) \
    195 	((one)->st_dev == (two)->st_dev && (one)->st_ino == (two)->st_ino && \
    196 	 (one)->st_uid == (two)->st_uid && (one)->st_gid == (two)->st_gid && \
    197 	 (one)->st_mode == (two)->st_mode)
    198 
    199 /**
    200  * Sanity check specified directory.  Store stat info for future comparison, or
    201  * compare with previously saved info to detect replaced directories.
    202  * Note: This function does not perform owner checks.
    203  */
    204 static int verify_directory(const char *dir, struct stat *st_in, struct stat *st_out) {
    205 	struct stat sb;
    206 
    207 	if (st_out == NULL) st_out = &sb;
    208 
    209 	if (lstat(dir, st_out) == -1) {
    210 		fprintf(stderr, _("Failed to stat %s: %s\n"), dir, strerror(errno));
    211 		return -1;
    212 	}
    213 	if (! S_ISDIR(st_out->st_mode)) {
    214 		fprintf(stderr, _("Error: %s is not a directory: %s\n"), dir, strerror(errno));
    215 		return -1;
    216 	}
    217 	if (st_in && !equal_stats(st_in, st_out)) {
    218 		fprintf(stderr, _("Error: %s was replaced by a different directory\n"), dir);
    219 		return -1;
    220 	}
    221 
    222 	return 0;
    223 }
    224 
    225 /**
    226  * This function checks to see if the shell is known in /etc/shells.
    227  * If so, it returns 0. On error or illegal shell, it returns -1.
    228  */
    229 static int verify_shell(const char *shell_name)
    230 {
    231 	int rc = -1;
    232 	const char *buf;
    233 
    234 	if (!(shell_name && shell_name[0]))
    235 		return rc;
    236 
    237 	while ((buf = getusershell()) != NULL) {
    238 		/* ignore comments */
    239 		if (*buf == '#')
    240 			continue;
    241 
    242 		/* check the shell skipping newline char */
    243 		if (!strcmp(shell_name, buf)) {
    244 			rc = 0;
    245 			break;
    246 		}
    247 	}
    248 	endusershell();
    249 	return rc;
    250 }
    251 
    252 /**
    253  * Mount directory and check that we mounted the right directory.
    254  */
    255 static int seunshare_mount(const char *src, const char *dst, struct stat *src_st)
    256 {
    257 	int flags = 0;
    258 	int is_tmp = 0;
    259 
    260 	if (verbose)
    261 		printf(_("Mounting %s on %s\n"), src, dst);
    262 
    263 	if (strcmp("/tmp", dst) == 0) {
    264 		flags = flags | MS_NODEV | MS_NOSUID | MS_NOEXEC;
    265 		is_tmp = 1;
    266 	}
    267 
    268 	/* mount directory */
    269 	if (mount(src, dst, NULL, MS_BIND | flags, NULL) < 0) {
    270 		fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno));
    271 		return -1;
    272 	}
    273 
    274 	/* verify whether we mounted what we expected to mount */
    275 	if (verify_directory(dst, src_st, NULL) < 0) return -1;
    276 
    277 	/* bind mount /tmp on /var/tmp too */
    278 	if (is_tmp) {
    279 		if (verbose)
    280 			printf(_("Mounting /tmp on /var/tmp\n"));
    281 
    282 		if (mount("/tmp", "/var/tmp",  NULL, MS_BIND | flags, NULL) < 0) {
    283 			fprintf(stderr, _("Failed to mount /tmp on /var/tmp: %s\n"), strerror(errno));
    284 			return -1;
    285 		}
    286 	}
    287 
    288 	return 0;
    289 
    290 }
    291 
    292 /*
    293    If path is empy or ends with  "/." or "/.. return -1 else return 0;
    294  */
    295 static int bad_path(const char *path) {
    296 	const char *ptr;
    297 	ptr = path;
    298 	while (*ptr) ptr++;
    299 	if (ptr == path) return -1; // ptr null
    300 	ptr--;
    301 	if (ptr != path && *ptr  == '.') {
    302 		ptr--;
    303 		if (*ptr  == '/') return -1; // path ends in /.
    304 		if (*ptr  == '.') {
    305 			if (ptr != path) {
    306 				ptr--;
    307 				if (*ptr  == '/') return -1; // path ends in /..
    308 			}
    309 		}
    310 	}
    311 	return 0;
    312 }
    313 
    314 static int rsynccmd(const char * src, const char *dst, char **cmdbuf)
    315 {
    316 	char *buf = NULL;
    317 	char *newbuf = NULL;
    318 	glob_t fglob;
    319 	fglob.gl_offs = 0;
    320 	int flags = GLOB_PERIOD;
    321 	unsigned int i = 0;
    322 	int rc = -1;
    323 
    324 	/* match glob for all files in src dir */
    325 	if (asprintf(&buf, "%s/*", src) == -1) {
    326 		fprintf(stderr, "Out of memory\n");
    327 		return -1;
    328 	}
    329 
    330 	if (glob(buf, flags, NULL, &fglob) != 0) {
    331 		free(buf); buf = NULL;
    332 		return -1;
    333 	}
    334 
    335 	free(buf); buf = NULL;
    336 
    337 	for ( i=0; i < fglob.gl_pathc; i++) {
    338 		const char *path = fglob.gl_pathv[i];
    339 
    340 		if (bad_path(path)) continue;
    341 
    342 		if (!buf) {
    343 			if (asprintf(&newbuf, "\'%s\'", path) == -1) {
    344 				fprintf(stderr, "Out of memory\n");
    345 				goto err;
    346 			}
    347 		} else {
    348 			if (asprintf(&newbuf, "%s  \'%s\'", buf, path) == -1) {
    349 				fprintf(stderr, "Out of memory\n");
    350 				goto err;
    351 			}
    352 		}
    353 
    354 		free(buf); buf = newbuf;
    355 		newbuf = NULL;
    356 	}
    357 
    358 	if (buf) {
    359 		if (asprintf(&newbuf, "/usr/bin/rsync -trlHDq %s '%s'", buf, dst) == -1) {
    360 			fprintf(stderr, "Out of memory\n");
    361 			goto err;
    362 		}
    363 		*cmdbuf=newbuf;
    364 	}
    365 	else {
    366 		*cmdbuf=NULL;
    367 	}
    368 	rc = 0;
    369 
    370 err:
    371 	free(buf); buf = NULL;
    372 	globfree(&fglob);
    373 	return rc;
    374 }
    375 
    376 /**
    377  * Clean up runtime temporary directory.  Returns 0 if no problem was detected,
    378  * >0 if some error was detected, but errors here are treated as non-fatal and
    379  * left to tmpwatch to finish incomplete cleanup.
    380  */
    381 static int cleanup_tmpdir(const char *tmpdir, const char *src,
    382 	struct passwd *pwd, int copy_content)
    383 {
    384 	char *cmdbuf = NULL;
    385 	int rc = 0;
    386 
    387 	/* rsync files back */
    388 	if (copy_content) {
    389 		if (asprintf(&cmdbuf, "/usr/bin/rsync --exclude=.X11-unix -utrlHDq --delete '%s/' '%s/'", tmpdir, src) == -1) {
    390 			fprintf(stderr, _("Out of memory\n"));
    391 			cmdbuf = NULL;
    392 			rc++;
    393 		}
    394 		if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) {
    395 			fprintf(stderr, _("Failed to copy files from the runtime temporary directory\n"));
    396 			rc++;
    397 		}
    398 		free(cmdbuf); cmdbuf = NULL;
    399 	}
    400 
    401 	/* remove files from the runtime temporary directory */
    402 	if (asprintf(&cmdbuf, "/bin/rm -r '%s/' 2>/dev/null", tmpdir) == -1) {
    403 		fprintf(stderr, _("Out of memory\n"));
    404 		cmdbuf = NULL;
    405 		rc++;
    406 	}
    407 	/* this may fail if there's root-owned file left in the runtime tmpdir */
    408 	if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) rc++;
    409 	free(cmdbuf); cmdbuf = NULL;
    410 
    411 	/* remove runtime temporary directory */
    412 	if ((uid_t)setfsuid(0) != 0) {
    413 		/* setfsuid does not return errror, but this check makes code checkers happy */
    414 		rc++;
    415 	}
    416 
    417 	if (rmdir(tmpdir) == -1)
    418 		fprintf(stderr, _("Failed to remove directory %s: %s\n"), tmpdir, strerror(errno));
    419 	if ((uid_t)setfsuid(pwd->pw_uid) != 0) {
    420 		fprintf(stderr, _("unable to switch back to user after clearing tmp dir\n"));
    421 		rc++;
    422 	}
    423 
    424 	return rc;
    425 }
    426 
    427 /**
    428  * seunshare will create a tmpdir in /tmp, with root ownership.  The parent
    429  * process waits for it child to exit to attempt to remove the directory.  If
    430  * it fails to remove the directory, we will need to rely on tmpreaper/tmpwatch
    431  * to clean it up.
    432  */
    433 static char *create_tmpdir(const char *src, struct stat *src_st,
    434 	struct stat *out_st, struct passwd *pwd, security_context_t execcon)
    435 {
    436 	char *tmpdir = NULL;
    437 	char *cmdbuf = NULL;
    438 	int fd_t = -1, fd_s = -1;
    439 	struct stat tmp_st;
    440 	security_context_t con = NULL;
    441 
    442 	/* get selinux context */
    443 	if (execcon) {
    444 		if ((uid_t)setfsuid(pwd->pw_uid) != 0)
    445 			goto err;
    446 
    447 		if ((fd_s = open(src, O_RDONLY)) < 0) {
    448 			fprintf(stderr, _("Failed to open directory %s: %s\n"), src, strerror(errno));
    449 			goto err;
    450 		}
    451 		if (fstat(fd_s, &tmp_st) == -1) {
    452 			fprintf(stderr, _("Failed to stat directory %s: %s\n"), src, strerror(errno));
    453 			goto err;
    454 		}
    455 		if (!equal_stats(src_st, &tmp_st)) {
    456 			fprintf(stderr, _("Error: %s was replaced by a different directory\n"), src);
    457 			goto err;
    458 		}
    459 		if (fgetfilecon(fd_s, &con) == -1) {
    460 			fprintf(stderr, _("Failed to get context of the directory %s: %s\n"), src, strerror(errno));
    461 			goto err;
    462 		}
    463 
    464 		/* ok to not reach this if there is an error */
    465 		if ((uid_t)setfsuid(0) != pwd->pw_uid)
    466 			goto err;
    467 	}
    468 
    469 	if (asprintf(&tmpdir, "/tmp/.sandbox-%s-XXXXXX", pwd->pw_name) == -1) {
    470 		fprintf(stderr, _("Out of memory\n"));
    471 		tmpdir = NULL;
    472 		goto err;
    473 	}
    474 	if (mkdtemp(tmpdir) == NULL) {
    475 		fprintf(stderr, _("Failed to create temporary directory: %s\n"), strerror(errno));
    476 		goto err;
    477 	}
    478 
    479 	/* temporary directory must be owned by root:user */
    480 	if (verify_directory(tmpdir, NULL, out_st) < 0) {
    481 		goto err;
    482 	}
    483 
    484 	if (check_owner_uid(0, tmpdir, out_st) < 0)
    485 		goto err;
    486 
    487 	if (check_owner_gid(getgid(), tmpdir, out_st) < 0)
    488 		goto err;
    489 
    490 	/* change permissions of the temporary directory */
    491 	if ((fd_t = open(tmpdir, O_RDONLY)) < 0) {
    492 		fprintf(stderr, _("Failed to open directory %s: %s\n"), tmpdir, strerror(errno));
    493 		goto err;
    494 	}
    495 	if (fstat(fd_t, &tmp_st) == -1) {
    496 		fprintf(stderr, _("Failed to stat directory %s: %s\n"), tmpdir, strerror(errno));
    497 		goto err;
    498 	}
    499 	if (!equal_stats(out_st, &tmp_st)) {
    500 		fprintf(stderr, _("Error: %s was replaced by a different directory\n"), tmpdir);
    501 		goto err;
    502 	}
    503 	if (fchmod(fd_t, 01770) == -1) {
    504 		fprintf(stderr, _("Unable to change mode on %s: %s\n"), tmpdir, strerror(errno));
    505 		goto err;
    506 	}
    507 	/* re-stat again to pick change mode */
    508 	if (fstat(fd_t, out_st) == -1) {
    509 		fprintf(stderr, _("Failed to stat directory %s: %s\n"), tmpdir, strerror(errno));
    510 		goto err;
    511 	}
    512 
    513 	/* copy selinux context */
    514 	if (execcon) {
    515 		if (fsetfilecon(fd_t, con) == -1) {
    516 			fprintf(stderr, _("Failed to set context of the directory %s: %s\n"), tmpdir, strerror(errno));
    517 			goto err;
    518 		}
    519 	}
    520 
    521 	if ((uid_t)setfsuid(pwd->pw_uid) != 0)
    522 		goto err;
    523 
    524 	if (rsynccmd(src, tmpdir, &cmdbuf) < 0) {
    525 		goto err;
    526 	}
    527 
    528 	/* ok to not reach this if there is an error */
    529 	if ((uid_t)setfsuid(0) != pwd->pw_uid)
    530 		goto err;
    531 
    532 	if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) {
    533 		fprintf(stderr, _("Failed to populate runtime temporary directory\n"));
    534 		cleanup_tmpdir(tmpdir, src, pwd, 0);
    535 		goto err;
    536 	}
    537 
    538 	goto good;
    539 err:
    540 	free(tmpdir); tmpdir = NULL;
    541 good:
    542 	free(cmdbuf); cmdbuf = NULL;
    543 	freecon(con); con = NULL;
    544 	if (fd_t >= 0) close(fd_t);
    545 	if (fd_s >= 0) close(fd_s);
    546 	return tmpdir;
    547 }
    548 
    549 #define PROC_BASE "/proc"
    550 
    551 static int
    552 killall (security_context_t execcon)
    553 {
    554 	DIR *dir;
    555 	security_context_t scon;
    556 	struct dirent *de;
    557 	pid_t *pid_table, pid, self;
    558 	int i;
    559 	int pids, max_pids;
    560 	int running = 0;
    561 	self = getpid();
    562 	if (!(dir = opendir(PROC_BASE))) {
    563 		return -1;
    564 	}
    565 	max_pids = 256;
    566 	pid_table = malloc(max_pids * sizeof (pid_t));
    567 	if (!pid_table) {
    568 		(void)closedir(dir);
    569 		return -1;
    570 	}
    571 	pids = 0;
    572 	context_t con;
    573 	con = context_new(execcon);
    574 	const char *mcs = context_range_get(con);
    575 	printf("mcs=%s\n", mcs);
    576 	while ((de = readdir (dir)) != NULL) {
    577 		if (!(pid = (pid_t)atoi(de->d_name)) || pid == self)
    578 			continue;
    579 
    580 		if (pids == max_pids) {
    581 			pid_t *new_pid_table = realloc(pid_table, 2*pids*sizeof(pid_t));
    582 			if (!new_pid_table) {
    583 				free(pid_table);
    584 				(void)closedir(dir);
    585 				return -1;
    586 			}
    587 			pid_table = new_pid_table;
    588 			max_pids *= 2;
    589 		}
    590 		pid_table[pids++] = pid;
    591 	}
    592 
    593 	(void)closedir(dir);
    594 
    595 	for (i = 0; i < pids; i++) {
    596 		pid_t id = pid_table[i];
    597 
    598 		if (getpidcon(id, &scon) == 0) {
    599 
    600 			context_t pidcon = context_new(scon);
    601 			/* Attempt to kill remaining processes */
    602 			if (strcmp(context_range_get(pidcon), mcs) == 0)
    603 				kill(id, SIGKILL);
    604 
    605 			context_free(pidcon);
    606 			freecon(scon);
    607 		}
    608 		running++;
    609 	}
    610 
    611 	context_free(con);
    612 	free(pid_table);
    613 	return running;
    614 }
    615 
    616 int main(int argc, char **argv) {
    617 	int status = -1;
    618 	security_context_t execcon = NULL;
    619 
    620 	int clflag;		/* holds codes for command line flags */
    621 	int kill_all = 0;
    622 
    623 	char *homedir_s = NULL;	/* homedir spec'd by user in argv[] */
    624 	char *tmpdir_s = NULL;	/* tmpdir spec'd by user in argv[] */
    625 	char *tmpdir_r = NULL;	/* tmpdir created by seunshare */
    626 
    627 	struct stat st_curhomedir;
    628 	struct stat st_homedir;
    629 	struct stat st_tmpdir_s;
    630 	struct stat st_tmpdir_r;
    631 
    632 	const struct option long_options[] = {
    633 		{"homedir", 1, 0, 'h'},
    634 		{"tmpdir", 1, 0, 't'},
    635 		{"kill", 1, 0, 'k'},
    636 		{"verbose", 1, 0, 'v'},
    637 		{"context", 1, 0, 'Z'},
    638 		{"capabilities", 1, 0, 'C'},
    639 		{NULL, 0, 0, 0}
    640 	};
    641 
    642 	uid_t uid = getuid();
    643 /*
    644 	if (!uid) {
    645 		fprintf(stderr, _("Must not be root"));
    646 		return -1;
    647 	}
    648 */
    649 
    650 #ifdef USE_NLS
    651 	setlocale(LC_ALL, "");
    652 	bindtextdomain(PACKAGE, LOCALEDIR);
    653 	textdomain(PACKAGE);
    654 #endif
    655 
    656 	struct passwd *pwd=getpwuid(uid);
    657 	if (!pwd) {
    658 		perror(_("getpwduid failed"));
    659 		return -1;
    660 	}
    661 
    662 	if (verify_shell(pwd->pw_shell) < 0) {
    663 		fprintf(stderr, _("Error: User shell is not valid\n"));
    664 		return -1;
    665 	}
    666 
    667 	while (1) {
    668 		clflag = getopt_long(argc, argv, "Ccvh:t:Z:", long_options, NULL);
    669 		if (clflag == -1)
    670 			break;
    671 
    672 		switch (clflag) {
    673 		case 't':
    674 			tmpdir_s = optarg;
    675 			break;
    676 		case 'k':
    677 			kill_all = 1;
    678 			break;
    679 		case 'h':
    680 			homedir_s = optarg;
    681 			break;
    682 		case 'v':
    683 			verbose++;
    684 			break;
    685 		case 'C':
    686 			cap_set = CAPNG_SELECT_CAPS;
    687 			break;
    688 		case 'Z':
    689 			execcon = optarg;
    690 			break;
    691 		default:
    692 			fprintf(stderr, "%s\n", USAGE_STRING);
    693 			return -1;
    694 		}
    695 	}
    696 
    697 	if (! homedir_s && ! tmpdir_s) {
    698 		fprintf(stderr, _("Error: tmpdir and/or homedir required\n %s\n"), USAGE_STRING);
    699 		return -1;
    700 	}
    701 
    702 	if (argc - optind < 1) {
    703 		fprintf(stderr, _("Error: executable required\n %s\n"), USAGE_STRING);
    704 		return -1;
    705 	}
    706 
    707 	if (execcon && is_selinux_enabled() != 1) {
    708 		fprintf(stderr, _("Error: execution context specified, but SELinux is not enabled\n"));
    709 		return -1;
    710 	}
    711 
    712 	if (set_signal_handles())
    713 		return -1;
    714 
    715 	/* set fsuid to ruid */
    716 	/* Changing fsuid is usually required when user-specified directory is
    717 	 * on an NFS mount.  It's also desired to avoid leaking info about
    718 	 * existence of the files not accessible to the user. */
    719 	if (((uid_t)setfsuid(uid) != 0)   && (errno != 0)) {
    720 		fprintf(stderr, _("Error: unable to setfsuid %m\n"));
    721 
    722 		return -1;
    723 	}
    724 
    725 	/* verify homedir and tmpdir */
    726 	if (homedir_s && (
    727 		verify_directory(homedir_s, NULL, &st_homedir) < 0 ||
    728 		check_owner_uid(uid, homedir_s, &st_homedir))) return -1;
    729 	if (tmpdir_s && (
    730 		verify_directory(tmpdir_s, NULL, &st_tmpdir_s) < 0 ||
    731 		check_owner_uid(uid, tmpdir_s, &st_tmpdir_s))) return -1;
    732 	if ((uid_t)setfsuid(0) != uid) return -1;
    733 
    734 	/* create runtime tmpdir */
    735 	if (tmpdir_s && (tmpdir_r = create_tmpdir(tmpdir_s, &st_tmpdir_s,
    736 						  &st_tmpdir_r, pwd, execcon)) == NULL) {
    737 		fprintf(stderr, _("Failed to create runtime temporary directory\n"));
    738 		return -1;
    739 	}
    740 
    741 	/* spawn child process */
    742 	child = fork();
    743 	if (child == -1) {
    744 		perror(_("Unable to fork"));
    745 		goto err;
    746 	}
    747 
    748 	if (child == 0) {
    749 		char *display = NULL;
    750 		char *LANG = NULL;
    751 		char *RUNTIME_DIR = NULL;
    752 		int rc = -1;
    753 		char *resolved_path = NULL;
    754 
    755 		if (unshare(CLONE_NEWNS) < 0) {
    756 			perror(_("Failed to unshare"));
    757 			goto childerr;
    758 		}
    759 
    760 		/* Remount / as SLAVE so that nothing mounted in the namespace
    761 		   shows up in the parent */
    762 		if (mount("none", "/", NULL, MS_SLAVE | MS_REC , NULL) < 0) {
    763 			perror(_("Failed to make / a SLAVE mountpoint\n"));
    764 			goto childerr;
    765 		}
    766 
    767 		/* assume fsuid==ruid after this point */
    768 		if ((uid_t)setfsuid(uid) != 0) goto childerr;
    769 
    770 		resolved_path = realpath(pwd->pw_dir,NULL);
    771 		if (! resolved_path) goto childerr;
    772 
    773 		if (verify_directory(resolved_path, NULL, &st_curhomedir) < 0)
    774 			goto childerr;
    775 		if (check_owner_uid(uid, resolved_path, &st_curhomedir) < 0)
    776 			goto childerr;
    777 
    778 		/* mount homedir and tmpdir, in this order */
    779 		if (homedir_s && seunshare_mount(homedir_s, resolved_path,
    780 			&st_homedir) != 0) goto childerr;
    781 		if (tmpdir_s &&	seunshare_mount(tmpdir_r, "/tmp",
    782 			&st_tmpdir_r) != 0) goto childerr;
    783 
    784 		if (drop_privs(uid) != 0) goto childerr;
    785 
    786 		/* construct a new environment */
    787 		if ((display = getenv("DISPLAY")) != NULL) {
    788 			if ((display = strdup(display)) == NULL) {
    789 				perror(_("Out of memory"));
    790 				goto childerr;
    791 			}
    792 		}
    793 
    794 		/* construct a new environment */
    795 		if ((LANG = getenv("LANG")) != NULL) {
    796 			if ((LANG = strdup(LANG)) == NULL) {
    797 				perror(_("Out of memory"));
    798 				goto childerr;
    799 			}
    800 		}
    801 
    802 		if ((RUNTIME_DIR = getenv("XDG_RUNTIME_DIR")) != NULL) {
    803 			if ((RUNTIME_DIR = strdup(RUNTIME_DIR)) == NULL) {
    804 				perror(_("Out of memory"));
    805 				goto childerr;
    806 			}
    807 		}
    808 
    809 		if ((rc = clearenv()) != 0) {
    810 			perror(_("Failed to clear environment"));
    811 			goto childerr;
    812 		}
    813 		if (display)
    814 			rc |= setenv("DISPLAY", display, 1);
    815 		if (LANG)
    816 			rc |= setenv("LANG", LANG, 1);
    817 		if (RUNTIME_DIR)
    818 			rc |= setenv("XDG_RUNTIME_DIR", RUNTIME_DIR, 1);
    819 		rc |= setenv("HOME", pwd->pw_dir, 1);
    820 		rc |= setenv("SHELL", pwd->pw_shell, 1);
    821 		rc |= setenv("USER", pwd->pw_name, 1);
    822 		rc |= setenv("LOGNAME", pwd->pw_name, 1);
    823 		rc |= setenv("PATH", DEFAULT_PATH, 1);
    824 		if (rc != 0) {
    825 			fprintf(stderr, _("Failed to construct environment\n"));
    826 			goto childerr;
    827 		}
    828 
    829 		if (chdir(pwd->pw_dir)) {
    830 			perror(_("Failed to change dir to homedir"));
    831 			goto childerr;
    832 		}
    833 		setsid();
    834 
    835 		/* selinux context */
    836 		if (execcon) {
    837 			/* try dyntransition, since no_new_privs can interfere
    838 			 * with setexeccon */
    839 			if (setcon(execcon) != 0) {
    840 				/* failed; fall back to setexeccon */
    841 				if (setexeccon(execcon) != 0) {
    842 					fprintf(stderr, _("Could not set exec context to %s. %s\n"), execcon, strerror(errno));
    843 					goto childerr;
    844 				}
    845 			}
    846 		}
    847 
    848 		execv(argv[optind], argv + optind);
    849 		fprintf(stderr, _("Failed to execute command %s: %s\n"), argv[optind], strerror(errno));
    850 childerr:
    851 		free(resolved_path);
    852 		free(display);
    853 		free(LANG);
    854 		free(RUNTIME_DIR);
    855 		exit(-1);
    856 	}
    857 
    858 	drop_caps();
    859 
    860 	/* parent waits for child exit to do the cleanup */
    861 	waitpid(child, &status, 0);
    862 	status_to_retval(status, status);
    863 
    864 	/* Make sure all child processes exit */
    865 	kill(-child,SIGTERM);
    866 
    867 	if (execcon && kill_all)
    868 		killall(execcon);
    869 
    870 	if (tmpdir_r) cleanup_tmpdir(tmpdir_r, tmpdir_s, pwd, 1);
    871 
    872 err:
    873 	free(tmpdir_r);
    874 	return status;
    875 }
    876