• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Authors: Dan Walsh <dwalsh@redhat.com>
3  * Authors: Thomas Liu <tliu@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  */
drop_caps(void)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  */
drop_privs(uid_t uid)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  */
handler(int sig)92 void handler(int sig) {
93 	if (child > 0) kill(-child,sig);
94 }
95 
96 /**
97  * Take care of any signal setup.
98  */
set_signal_handles(void)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  */
spawn_command(const char * cmd,uid_t uid)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  */
check_owner_uid(uid_t uid,const char * file,struct stat * st)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 
check_owner_gid(gid_t gid,const char * file,struct stat * st)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  */
verify_directory(const char * dir,struct stat * st_in,struct stat * st_out)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  */
verify_shell(const char * shell_name)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  */
seunshare_mount(const char * src,const char * dst,struct stat * src_st)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  */
bad_path(const char * path)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 
rsynccmd(const char * src,const char * dst,char ** cmdbuf)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  */
cleanup_tmpdir(const char * tmpdir,const char * src,struct passwd * pwd,int copy_content)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  */
create_tmpdir(const char * src,struct stat * src_st,struct stat * out_st,struct passwd * pwd,security_context_t execcon)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
killall(security_context_t execcon)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 
main(int argc,char ** argv)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