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