blob: 9707a456f68c64538cec614ace6b3b15a28f27ef [file] [log] [blame]
Eric Parise1340132011-08-15 20:10:14 -04001/*
2 * Authors: Dan Walsh <dwalsh@redhat.com>
3 * Authors: Thomas Liu <tliu@fedoraproject.org>
4 */
5
Eric Paris39066bd2011-08-02 13:58:07 -04006#define _GNU_SOURCE
Daniel J Walshd6848ea2010-06-10 16:35:55 -04007#include <signal.h>
Eric Paris31edb312011-08-15 19:58:08 -04008#include <sys/fsuid.h>
9#include <sys/stat.h>
Daniel J Walshd6848ea2010-06-10 16:35:55 -040010#include <sys/types.h>
11#include <sys/wait.h>
12#include <syslog.h>
13#include <sys/mount.h>
Eric Paris31edb312011-08-15 19:58:08 -040014#include <glob.h>
Daniel J Walshd6848ea2010-06-10 16:35:55 -040015#include <pwd.h>
Daniel J Walshd6848ea2010-06-10 16:35:55 -040016#include <sched.h>
17#include <string.h>
18#include <stdio.h>
Eric Paris4347a5c2011-08-03 15:09:22 -040019#include <regex.h>
Daniel J Walshd6848ea2010-06-10 16:35:55 -040020#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>
Eric Paris31edb312011-08-15 19:58:08 -040027#include <fcntl.h>
Daniel J Walshd6848ea2010-06-10 16:35:55 -040028
29#include <selinux/selinux.h>
30#include <selinux/context.h> /* for context-mangling functions */
Dan Walshe8575bf2011-07-06 20:22:26 -040031#include <dirent.h>
Daniel J Walshd6848ea2010-06-10 16:35:55 -040032
Daniel J Walshd6848ea2010-06-10 16:35:55 -040033#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
Steve Lawrence582fd002010-06-10 16:37:59 -040041#ifndef MS_REC
42#define MS_REC 1<<14
43#endif
44
Dan Walsh70c582f2012-01-03 13:45:08 -050045#ifndef MS_SLAVE
46#define MS_SLAVE 1<<19
Steve Lawrence582fd002010-06-10 16:37:59 -040047#endif
48
Eric Parise1340132011-08-15 20:10:14 -040049#ifndef PACKAGE
50#define PACKAGE "policycoreutils" /* the name of this package lang translation */
51#endif
52
Eric Paris4347a5c2011-08-03 15:09:22 -040053#define BUF_SIZE 1024
Eric Paris406ae122011-08-03 16:23:12 -040054#define DEFAULT_PATH "/usr/bin:/bin"
Dan Walshde0795a2014-05-12 13:19:20 -040055#define USAGE_STRING _("USAGE: seunshare [ -v ] [ -C ] [ -k ] [ -t tmpdir ] [ -h homedir ] [ -Z CONTEXT ] -- executable [args] ")
Eric Paris406ae122011-08-03 16:23:12 -040056
57static int verbose = 0;
Dan Walsh216f4562011-07-06 20:52:05 -040058static int child = 0;
Eric Paris406ae122011-08-03 16:23:12 -040059
Dan Walsh1f0b5bd2012-02-22 15:55:39 -050060static capng_select_t cap_set = CAPNG_SELECT_CAPS;
Dan Walsh149afc62011-06-13 13:24:38 -040061
Daniel J Walshd6848ea2010-06-10 16:35:55 -040062/**
Eric Parisd6c09602011-08-05 13:33:35 -040063 * This function will drop all capabilities.
Daniel J Walshd6848ea2010-06-10 16:35:55 -040064 */
Nicolas Ioossc4a4a1a2014-09-14 23:41:49 +020065static int drop_caps(void)
Daniel J Walshd6848ea2010-06-10 16:35:55 -040066{
Dan Walsh149afc62011-06-13 13:24:38 -040067 if (capng_have_capabilities(cap_set) == CAPNG_NONE)
Eric Parisd6c09602011-08-05 13:33:35 -040068 return 0;
Dan Walsh149afc62011-06-13 13:24:38 -040069 capng_clear(cap_set);
70 if (capng_lock() == -1 || capng_apply(cap_set) == -1) {
Eric Parisd6c09602011-08-05 13:33:35 -040071 fprintf(stderr, _("Failed to drop all capabilities\n"));
Daniel J Walshd6848ea2010-06-10 16:35:55 -040072 return -1;
73 }
Eric Parisd6c09602011-08-05 13:33:35 -040074 return 0;
75}
76
77/**
78 * This function will drop all privileges.
79 */
80static 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;
Daniel J Walshd6848ea2010-06-10 16:35:55 -040087}
88
Daniel J Walshd6848ea2010-06-10 16:35:55 -040089/**
Dan Walsh216f4562011-07-06 20:52:05 -040090 * If the user sends a siginto to seunshare, kill the child's session
91 */
92void handler(int sig) {
93 if (child > 0) kill(-child,sig);
94}
95
96/**
Eric Paris89e3dd62011-08-03 14:27:32 -040097 * Take care of any signal setup.
Daniel J Walshd6848ea2010-06-10 16:35:55 -040098 */
99static 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
Eric Paris31edb312011-08-15 19:58:08 -0400111 /* Terminate on SIGHUP */
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400112 if (signal(SIGHUP, SIG_DFL) == SIG_ERR) {
113 perror("Unable to set SIGHUP handler");
114 return -1;
115 }
116
Dan Walsh216f4562011-07-06 20:52:05 -0400117 if (signal(SIGINT, handler) == SIG_ERR) {
118 perror("Unable to set SIGINT handler");
119 return -1;
120 }
121
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400122 return 0;
123}
124
Eric Parisf6558d92011-08-05 14:06:34 -0400125#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 */
140static int spawn_command(const char *cmd, uid_t uid){
Nicolas Ioossf978b1b2014-09-14 23:41:35 +0200141 int childpid;
Eric Parisf6558d92011-08-05 14:06:34 -0400142 int status = -1;
143
144 if (verbose > 1)
145 printf("spawn_command: %s\n", cmd);
146
Nicolas Ioossf978b1b2014-09-14 23:41:35 +0200147 childpid = fork();
148 if (childpid == -1) {
Eric Parisf6558d92011-08-05 14:06:34 -0400149 perror(_("Unable to fork"));
150 return status;
151 }
152
Nicolas Ioossf978b1b2014-09-14 23:41:35 +0200153 if (childpid == 0) {
Eric Parisf6558d92011-08-05 14:06:34 -0400154 if (drop_privs(uid) != 0) exit(-1);
155
156 status = system(cmd);
157 status_to_retval(status, status);
158 exit(status);
159 }
160
Nicolas Ioossf978b1b2014-09-14 23:41:35 +0200161 waitpid(childpid, &status, 0);
Eric Parisf6558d92011-08-05 14:06:34 -0400162 status_to_retval(status, status);
163 return status;
164}
165
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400166/**
Eric Parisbf22cff2011-08-05 14:36:29 -0400167 * Check file/directory ownership, struct stat * must be passed to the
168 * functions.
169 */
170static 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
182static 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 */
204static 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/**
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400226 * 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 */
229static 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)) {
Eric Paris31edb312011-08-15 19:58:08 -0400244 rc = 0;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400245 break;
246 }
247 }
248 endusershell();
249 return rc;
250}
251
Eric Paris31edb312011-08-15 19:58:08 -0400252/**
253 * Mount directory and check that we mounted the right directory.
254 */
255static int seunshare_mount(const char *src, const char *dst, struct stat *src_st)
256{
Dan Walsh70c582f2012-01-03 13:45:08 -0500257 int flags = 0;
Eric Paris31edb312011-08-15 19:58:08 -0400258 int is_tmp = 0;
259
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400260 if (verbose)
Eric Paris31edb312011-08-15 19:58:08 -0400261 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 */
Eric Paris31edb312011-08-15 19:58:08 -0400269 if (mount(src, dst, NULL, MS_BIND | flags, NULL) < 0) {
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400270 fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno));
271 return -1;
272 }
273
Eric Paris31edb312011-08-15 19:58:08 -0400274 /* 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
Eric Paris31edb312011-08-15 19:58:08 -0400282 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
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400290}
291
Eric Paris31edb312011-08-15 19:58:08 -0400292/*
Nicolas Ioossb550c0e2019-08-05 22:11:20 +0200293 If path is empty or ends with "/." or "/.. return -1 else return 0;
Eric Paris31edb312011-08-15 19:58:08 -0400294 */
295static 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
314static 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
370err:
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 */
381static 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 */
Dan Walshe4488ec2013-10-09 17:28:37 -0400412 if ((uid_t)setfsuid(0) != 0) {
Nicolas Ioossb550c0e2019-08-05 22:11:20 +0200413 /* setfsuid does not return error, but this check makes code checkers happy */
Eric Paris221e6d42012-09-26 11:00:56 -0400414 rc++;
415 }
416
Eric Paris31edb312011-08-15 19:58:08 -0400417 if (rmdir(tmpdir) == -1)
418 fprintf(stderr, _("Failed to remove directory %s: %s\n"), tmpdir, strerror(errno));
Eric Paris221e6d42012-09-26 11:00:56 -0400419 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 }
Eric Paris31edb312011-08-15 19:58:08 -0400423
Eric Paris221e6d42012-09-26 11:00:56 -0400424 return rc;
Eric Paris31edb312011-08-15 19:58:08 -0400425}
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 */
433static 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) {
Eric Paris221e6d42012-09-26 11:00:56 -0400444 if ((uid_t)setfsuid(pwd->pw_uid) != 0)
445 goto err;
446
Eric Paris31edb312011-08-15 19:58:08 -0400447 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 */
Eric Paris221e6d42012-09-26 11:00:56 -0400465 if ((uid_t)setfsuid(0) != pwd->pw_uid)
466 goto err;
Eric Paris31edb312011-08-15 19:58:08 -0400467 }
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
Eric Paris221e6d42012-09-26 11:00:56 -0400521 if ((uid_t)setfsuid(pwd->pw_uid) != 0)
522 goto err;
Eric Paris31edb312011-08-15 19:58:08 -0400523
524 if (rsynccmd(src, tmpdir, &cmdbuf) < 0) {
525 goto err;
526 }
527
528 /* ok to not reach this if there is an error */
Eric Paris221e6d42012-09-26 11:00:56 -0400529 if ((uid_t)setfsuid(0) != pwd->pw_uid)
530 goto err;
Eric Paris31edb312011-08-15 19:58:08 -0400531
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;
539err:
540 free(tmpdir); tmpdir = NULL;
541good:
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
Dan Walshe8575bf2011-07-06 20:22:26 -0400549#define PROC_BASE "/proc"
550
551static int
552killall (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) {
Eric Paris0a5dc302013-02-01 15:23:12 -0500581 pid_t *new_pid_table = realloc(pid_table, 2*pids*sizeof(pid_t));
582 if (!new_pid_table) {
583 free(pid_table);
Dan Walshe8575bf2011-07-06 20:22:26 -0400584 (void)closedir(dir);
585 return -1;
586 }
Eric Paris0a5dc302013-02-01 15:23:12 -0500587 pid_table = new_pid_table;
Dan Walshe8575bf2011-07-06 20:22:26 -0400588 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
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400616int main(int argc, char **argv) {
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400617 int status = -1;
Eric Paris31edb312011-08-15 19:58:08 -0400618 security_context_t execcon = NULL;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400619
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400620 int clflag; /* holds codes for command line flags */
Dan Walshe8575bf2011-07-06 20:22:26 -0400621 int kill_all = 0;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400622
Eric Paris31edb312011-08-15 19:58:08 -0400623 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
Dan Walshe4488ec2013-10-09 17:28:37 -0400627 struct stat st_curhomedir;
Eric Paris31edb312011-08-15 19:58:08 -0400628 struct stat st_homedir;
629 struct stat st_tmpdir_s;
630 struct stat st_tmpdir_r;
631
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400632 const struct option long_options[] = {
633 {"homedir", 1, 0, 'h'},
634 {"tmpdir", 1, 0, 't'},
Dan Walshe8575bf2011-07-06 20:22:26 -0400635 {"kill", 1, 0, 'k'},
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400636 {"verbose", 1, 0, 'v'},
Eric Parisda7ae792011-08-15 16:00:04 -0400637 {"context", 1, 0, 'Z'},
Dan Walsh149afc62011-06-13 13:24:38 -0400638 {"capabilities", 1, 0, 'C'},
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400639 {NULL, 0, 0, 0}
640 };
641
642 uid_t uid = getuid();
Dan Walsha0e2e162011-07-26 10:42:26 -0400643/*
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400644 if (!uid) {
645 fprintf(stderr, _("Must not be root"));
646 return -1;
647 }
Dan Walsha0e2e162011-07-26 10:42:26 -0400648*/
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400649
Eric Parise1340132011-08-15 20:10:14 -0400650#ifdef USE_NLS
651 setlocale(LC_ALL, "");
652 bindtextdomain(PACKAGE, LOCALEDIR);
653 textdomain(PACKAGE);
654#endif
655
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400656 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) {
Eric Paris31edb312011-08-15 19:58:08 -0400663 fprintf(stderr, _("Error: User shell is not valid\n"));
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400664 return -1;
665 }
666
667 while (1) {
Dan Walsh149afc62011-06-13 13:24:38 -0400668 clflag = getopt_long(argc, argv, "Ccvh:t:Z:", long_options, NULL);
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400669 if (clflag == -1)
670 break;
671
672 switch (clflag) {
673 case 't':
Eric Paris31edb312011-08-15 19:58:08 -0400674 tmpdir_s = optarg;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400675 break;
Dan Walshe8575bf2011-07-06 20:22:26 -0400676 case 'k':
677 kill_all = 1;
678 break;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400679 case 'h':
Eric Paris31edb312011-08-15 19:58:08 -0400680 homedir_s = optarg;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400681 break;
682 case 'v':
Eric Paris31edb312011-08-15 19:58:08 -0400683 verbose++;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400684 break;
Dan Walsh149afc62011-06-13 13:24:38 -0400685 case 'C':
686 cap_set = CAPNG_SELECT_CAPS;
687 break;
Eric Parisda7ae792011-08-15 16:00:04 -0400688 case 'Z':
Eric Paris31edb312011-08-15 19:58:08 -0400689 execcon = optarg;
Eric Parisda7ae792011-08-15 16:00:04 -0400690 break;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400691 default:
692 fprintf(stderr, "%s\n", USAGE_STRING);
693 return -1;
694 }
695 }
696
697 if (! homedir_s && ! tmpdir_s) {
Eric Paris31edb312011-08-15 19:58:08 -0400698 fprintf(stderr, _("Error: tmpdir and/or homedir required\n %s\n"), USAGE_STRING);
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400699 return -1;
700 }
701
Eric Parisda7ae792011-08-15 16:00:04 -0400702 if (argc - optind < 1) {
Eric Paris31edb312011-08-15 19:58:08 -0400703 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"));
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400709 return -1;
710 }
711
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400712 if (set_signal_handles())
713 return -1;
714
Eric Paris31edb312011-08-15 19:58:08 -0400715 /* 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. */
Dan Walshe4488ec2013-10-09 17:28:37 -0400719 if (((uid_t)setfsuid(uid) != 0) && (errno != 0)) {
720 fprintf(stderr, _("Error: unable to setfsuid %m\n"));
721
Eric Paris221e6d42012-09-26 11:00:56 -0400722 return -1;
Dan Walshe4488ec2013-10-09 17:28:37 -0400723 }
Eric Paris31edb312011-08-15 19:58:08 -0400724
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;
Eric Paris221e6d42012-09-26 11:00:56 -0400732 if ((uid_t)setfsuid(0) != uid) return -1;
Eric Paris31edb312011-08-15 19:58:08 -0400733
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"));
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400738 return -1;
739 }
740
Eric Paris31edb312011-08-15 19:58:08 -0400741 /* spawn child process */
Dan Walsh216f4562011-07-06 20:52:05 -0400742 child = fork();
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400743 if (child == -1) {
744 perror(_("Unable to fork"));
Eric Paris31edb312011-08-15 19:58:08 -0400745 goto err;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400746 }
747
Eric Paris31edb312011-08-15 19:58:08 -0400748 if (child == 0) {
749 char *display = NULL;
Dan Walsh5c2a0d12011-09-07 14:20:30 -0400750 char *LANG = NULL;
Dan Walsh6ee02992014-05-12 13:19:19 -0400751 char *RUNTIME_DIR = NULL;
Eric Paris31edb312011-08-15 19:58:08 -0400752 int rc = -1;
Dan Walshe4488ec2013-10-09 17:28:37 -0400753 char *resolved_path = NULL;
Eric Paris31edb312011-08-15 19:58:08 -0400754
755 if (unshare(CLONE_NEWNS) < 0) {
756 perror(_("Failed to unshare"));
757 goto childerr;
758 }
759
Dan Walsh70c582f2012-01-03 13:45:08 -0500760 /* 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
Eric Paris31edb312011-08-15 19:58:08 -0400767 /* assume fsuid==ruid after this point */
Eric Paris221e6d42012-09-26 11:00:56 -0400768 if ((uid_t)setfsuid(uid) != 0) goto childerr;
Eric Paris31edb312011-08-15 19:58:08 -0400769
Dan Walshe4488ec2013-10-09 17:28:37 -0400770 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
Eric Paris31edb312011-08-15 19:58:08 -0400778 /* mount homedir and tmpdir, in this order */
Dan Walshe4488ec2013-10-09 17:28:37 -0400779 if (homedir_s && seunshare_mount(homedir_s, resolved_path,
Eric Paris31edb312011-08-15 19:58:08 -0400780 &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) {
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400789 perror(_("Out of memory"));
Eric Paris31edb312011-08-15 19:58:08 -0400790 goto childerr;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400791 }
792 }
Eric Paris221e6d42012-09-26 11:00:56 -0400793
Dan Walsh5c2a0d12011-09-07 14:20:30 -0400794 /* 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 }
Eric Paris221e6d42012-09-26 11:00:56 -0400801
Dan Walsh6ee02992014-05-12 13:19:19 -0400802 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
Eric Paris31edb312011-08-15 19:58:08 -0400809 if ((rc = clearenv()) != 0) {
810 perror(_("Failed to clear environment"));
811 goto childerr;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400812 }
Eric Paris31edb312011-08-15 19:58:08 -0400813 if (display)
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400814 rc |= setenv("DISPLAY", display, 1);
Eric Paris221e6d42012-09-26 11:00:56 -0400815 if (LANG)
Dan Walsh5c2a0d12011-09-07 14:20:30 -0400816 rc |= setenv("LANG", LANG, 1);
Dan Walsh6ee02992014-05-12 13:19:19 -0400817 if (RUNTIME_DIR)
818 rc |= setenv("XDG_RUNTIME_DIR", RUNTIME_DIR, 1);
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400819 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);
Eric Paris31edb312011-08-15 19:58:08 -0400824 if (rc != 0) {
825 fprintf(stderr, _("Failed to construct environment\n"));
826 goto childerr;
827 }
828
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400829 if (chdir(pwd->pw_dir)) {
830 perror(_("Failed to change dir to homedir"));
Eric Paris31edb312011-08-15 19:58:08 -0400831 goto childerr;
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400832 }
833 setsid();
Andy Lutomirski74d27a92014-05-12 13:19:21 -0400834
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
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400848 execv(argv[optind], argv + optind);
Eric Paris31edb312011-08-15 19:58:08 -0400849 fprintf(stderr, _("Failed to execute command %s: %s\n"), argv[optind], strerror(errno));
850childerr:
Dan Walshe4488ec2013-10-09 17:28:37 -0400851 free(resolved_path);
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400852 free(display);
Dan Walsh5c2a0d12011-09-07 14:20:30 -0400853 free(LANG);
Dan Walsh6ee02992014-05-12 13:19:19 -0400854 free(RUNTIME_DIR);
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400855 exit(-1);
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400856 }
857
Eric Paris31edb312011-08-15 19:58:08 -0400858 drop_caps();
Steve Lawrence582fd002010-06-10 16:37:59 -0400859
Eric Paris31edb312011-08-15 19:58:08 -0400860 /* parent waits for child exit to do the cleanup */
861 waitpid(child, &status, 0);
862 status_to_retval(status, status);
863
Dan Walsh216f4562011-07-06 20:52:05 -0400864 /* Make sure all child processes exit */
865 kill(-child,SIGTERM);
866
Dan Walshe8575bf2011-07-06 20:22:26 -0400867 if (execcon && kill_all)
868 killall(execcon);
869
Eric Paris31edb312011-08-15 19:58:08 -0400870 if (tmpdir_r) cleanup_tmpdir(tmpdir_r, tmpdir_s, pwd, 1);
871
872err:
873 free(tmpdir_r);
Daniel J Walshd6848ea2010-06-10 16:35:55 -0400874 return status;
875}