aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar smitsohu <smitsohu@gmail.com>2020-03-06 02:01:54 +0100
committerLibravatar smitsohu <smitsohu@gmail.com>2020-03-06 02:01:54 +0100
commitad98c8f8edf3821c47559f8bf368ebd173bdafd1 (patch)
tree9f77111debfc54f77546553a58f01d2bc8c96d5e /src
parentMerge pull request #3241 from kris7t/sbox-harden-exec (diff)
downloadfirejail-ad98c8f8edf3821c47559f8bf368ebd173bdafd1.tar.gz
firejail-ad98c8f8edf3821c47559f8bf368ebd173bdafd1.tar.zst
firejail-ad98c8f8edf3821c47559f8bf368ebd173bdafd1.zip
remount hardening: move to file descriptor based mounts
Diffstat (limited to 'src')
-rw-r--r--src/firejail/firejail.h4
-rw-r--r--src/firejail/fs.c196
2 files changed, 120 insertions, 80 deletions
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h
index 66328a55e..cc5f01ead 100644
--- a/src/firejail/firejail.h
+++ b/src/firejail/firejail.h
@@ -394,6 +394,7 @@ typedef enum {
394 MOUNT_TMPFS, 394 MOUNT_TMPFS,
395 MOUNT_NOEXEC, 395 MOUNT_NOEXEC,
396 MOUNT_RDWR, 396 MOUNT_RDWR,
397 MOUNT_RDWR_NOCHECK, // no check of ownership
397 OPERATION_MAX 398 OPERATION_MAX
398} OPERATION; 399} OPERATION;
399 400
@@ -402,8 +403,7 @@ void fs_blacklist(void);
402// mount a writable tmpfs 403// mount a writable tmpfs
403void fs_tmpfs(const char *dir, unsigned check_owner); 404void fs_tmpfs(const char *dir, unsigned check_owner);
404// remount noexec/nodev/nosuid or read-only or read-write 405// remount noexec/nodev/nosuid or read-only or read-write
405void fs_remount(const char *dir, OPERATION op, unsigned check_mnt); 406void fs_remount(const char *dir, OPERATION op, int rec);
406void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt);
407// mount /proc and /sys directories 407// mount /proc and /sys directories
408void fs_proc_sys_dev_boot(void); 408void fs_proc_sys_dev_boot(void);
409// blacklist firejail configuration and runtime directories 409// blacklist firejail configuration and runtime directories
diff --git a/src/firejail/fs.c b/src/firejail/fs.c
index c7dd91b06..ed1cae45a 100644
--- a/src/firejail/fs.c
+++ b/src/firejail/fs.c
@@ -28,10 +28,9 @@
28#include <dirent.h> 28#include <dirent.h>
29#include <errno.h> 29#include <errno.h>
30 30
31
32#include <fcntl.h> 31#include <fcntl.h>
33#ifndef O_PATH 32#ifndef O_PATH
34# define O_PATH 010000000 33#define O_PATH 010000000
35#endif 34#endif
36 35
37#define MAX_BUF 4096 36#define MAX_BUF 4096
@@ -43,6 +42,8 @@
43//*********************************************** 42//***********************************************
44// process profile file 43// process profile file
45//*********************************************** 44//***********************************************
45static void fs_remount_rec(const char *dir, OPERATION op);
46
46static char *opstr[] = { 47static char *opstr[] = {
47 [BLACKLIST_FILE] = "blacklist", 48 [BLACKLIST_FILE] = "blacklist",
48 [BLACKLIST_NOLOG] = "blacklist-nolog", 49 [BLACKLIST_NOLOG] = "blacklist-nolog",
@@ -50,6 +51,7 @@ static char *opstr[] = {
50 [MOUNT_TMPFS] = "tmpfs", 51 [MOUNT_TMPFS] = "tmpfs",
51 [MOUNT_NOEXEC] = "noexec", 52 [MOUNT_NOEXEC] = "noexec",
52 [MOUNT_RDWR] = "read-write", 53 [MOUNT_RDWR] = "read-write",
54 [MOUNT_RDWR_NOCHECK] = "read-write",
53}; 55};
54 56
55typedef enum { 57typedef enum {
@@ -148,7 +150,7 @@ static void disable_file(OPERATION op, const char *filename) {
148 } 150 }
149 } 151 }
150 else if (op == MOUNT_READONLY || op == MOUNT_RDWR || op == MOUNT_NOEXEC) { 152 else if (op == MOUNT_READONLY || op == MOUNT_RDWR || op == MOUNT_NOEXEC) {
151 fs_remount_rec(fname, op, 1); 153 fs_remount_rec(fname, op);
152 // todo: last_disable = SUCCESSFUL; 154 // todo: last_disable = SUCCESSFUL;
153 } 155 }
154 else if (op == MOUNT_TMPFS) { 156 else if (op == MOUNT_TMPFS) {
@@ -425,21 +427,11 @@ void fs_blacklist(void) {
425 free(noblacklist); 427 free(noblacklist);
426} 428}
427 429
428static int get_mount_flags(const char *path, unsigned long *flags) {
429 struct statvfs buf;
430
431 if (statvfs(path, &buf) < 0)
432 return -errno;
433 *flags = buf.f_flag;
434 return 0;
435}
436
437//*********************************************** 430//***********************************************
438// mount namespace 431// mount namespace
439// - functions need fully resolved paths
440//*********************************************** 432//***********************************************
441 433
442// mount a writable tmpfs on directory 434// mount a writable tmpfs on directory; requires a resolved path
443void fs_tmpfs(const char *dir, unsigned check_owner) { 435void fs_tmpfs(const char *dir, unsigned check_owner) {
444 assert(dir); 436 assert(dir);
445 if (arg_debug) 437 if (arg_debug)
@@ -480,71 +472,106 @@ void fs_tmpfs(const char *dir, unsigned check_owner) {
480 close(fd); 472 close(fd);
481} 473}
482 474
483void fs_remount(const char *dir, OPERATION op, unsigned check_mnt) { 475// remount path, but preserve existing mount flags; requires a resolved path
484 assert(dir); 476static void fs_remount_simple(const char *path, OPERATION op) {
485 // check directory exists 477 assert(path);
478
479 // open path without following symbolic links
480 int fd = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
481 if (fd == -1) {
482 fwarning("cannot open %s, skipping the remount...\n", path);
483 return;
484 }
485 // identify file owner
486 struct stat s; 486 struct stat s;
487 int rv = stat(dir, &s); 487 if (fstat(fd, &s) == -1)
488 if (rv == 0) { 488 errExit("fstat");
489 unsigned long flags = 0; 489 // get mount flags
490 if (get_mount_flags(dir, &flags) != 0) { 490 struct statvfs buf;
491 fwarning("cannot remount %s\n", dir); 491 if (fstatvfs(fd, &buf) == -1)
492 errExit("fstatvfs");
493 unsigned long flags = buf.f_flag;
494
495 // read-write option
496 if (op == MOUNT_RDWR || op == MOUNT_RDWR_NOCHECK) {
497 // nothing to do if there is no read-only flag
498 if ((flags & MS_RDONLY) == 0) {
499 close(fd);
492 return; 500 return;
493 } 501 }
494 if (op == MOUNT_RDWR) { 502 // allow only user owned directories, except the user is root
495 // allow only user owned directories, except the user is root 503 if (op == MOUNT_RDWR && getuid() != 0 && s.st_uid != getuid()) {
496 if (getuid() != 0 && s.st_uid != getuid()) { 504 fwarning("you are not allowed to change %s to read-write\n", path);
497 fwarning("you are not allowed to change %s to read-write\n", dir); 505 close(fd);
498 return; 506 return;
499 }
500 if ((flags & MS_RDONLY) == 0)
501 return;
502 flags &= ~MS_RDONLY;
503 }
504 else if (op == MOUNT_NOEXEC) {
505 if ((flags & (MS_NOEXEC|MS_NODEV|MS_NOSUID)) == (MS_NOEXEC|MS_NODEV|MS_NOSUID))
506 return;
507 flags |= MS_NOEXEC|MS_NODEV|MS_NOSUID;
508 } 507 }
509 else if (op == MOUNT_READONLY) { 508 flags &= ~MS_RDONLY;
510 if ((flags & MS_RDONLY) == MS_RDONLY) 509 }
511 return; 510 // noexec option
512 flags |= MS_RDONLY; 511 else if (op == MOUNT_NOEXEC) {
512 // nothing to do if path is mounted noexec already
513 if ((flags & (MS_NOEXEC|MS_NODEV|MS_NOSUID)) == (MS_NOEXEC|MS_NODEV|MS_NOSUID)) {
514 close(fd);
515 return;
513 } 516 }
514 else 517 flags |= MS_NOEXEC|MS_NODEV|MS_NOSUID;
515 assert(0); 518 }
516 519 // read-only option
517 if (arg_debug) 520 else if (op == MOUNT_READONLY) {
518 printf("Mounting %s %s\n", opstr[op], dir); 521 // nothing to do if path is mounted read-only already
519 // mount --bind /bin /bin 522 if ((flags & MS_RDONLY) == MS_RDONLY) {
520 // mount --bind -o remount,rw /bin 523 close(fd);
521 if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || 524 return;
522 mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0)
523 errExit("remounting");
524 // run a sanity check on /proc/self/mountinfo
525 if (check_mnt) {
526 // confirm target of the last mount operation was dir; if there are other
527 // mount points contained inside dir, one of those will show up as target
528 // of the last mount operation instead
529 MountData *mptr = get_last_mount();
530 size_t len = strlen(dir);
531 if ((strncmp(mptr->dir, dir, len) != 0 ||
532 (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/'))
533 && strcmp(dir, "/") != 0) // support read-only=/
534 errLogExit("invalid %s mount", opstr[op]);
535 } 525 }
536 fs_logger2(opstr[op], dir); 526 flags |= MS_RDONLY;
537 } 527 }
528 else
529 assert(0);
530
531 if (arg_debug)
532 printf("Mounting %s %s\n", opstr[op], path);
533 // mount --bind /bin /bin
534 char *proc;
535 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
536 errExit("asprintf");
537 if (mount(proc, proc, NULL, MS_BIND|MS_REC, NULL) < 0)
538 errExit("mount");
539 free(proc);
540 close(fd);
541
542 // mount --bind -o remount,ro /bin
543 // we need to open path again
544 fd = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
545 if (fd == -1)
546 errExit("open");
547 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
548 errExit("asprintf");
549 if (mount(NULL, proc, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0)
550 errExit("mount");
551
552 // run a sanity check on /proc/self/mountinfo and confirm that target of the last
553 // mount operation was path; if there are other mount points contained inside path,
554 // one of those will show up as target of the last mount operation instead
555 MountData *mptr = get_last_mount();
556 size_t len = strlen(path);
557 if ((strncmp(mptr->dir, path, len) != 0 ||
558 (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/'))
559 && strcmp(path, "/") != 0) // support read-only=/
560 errLogExit("invalid %s mount", opstr[op]);
561 fs_logger2(opstr[op], path);
562 free(proc);
563 close(fd);
538} 564}
539 565
540void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt) { 566// remount recursively; requires a resolved path
567static void fs_remount_rec(const char *dir, OPERATION op) {
541 assert(dir); 568 assert(dir);
542 struct stat s; 569 struct stat s;
543 if (stat(dir, &s) != 0) 570 if (stat(dir, &s) != 0)
544 return; 571 return;
545 if (!S_ISDIR(s.st_mode)) { 572 if (!S_ISDIR(s.st_mode)) {
546 // no need to search in /proc/self/mountinfo for submounts if not a directory 573 // no need to search in /proc/self/mountinfo for submounts if not a directory
547 fs_remount(dir, op, check_mnt); 574 fs_remount_simple(dir, op);
548 return; 575 return;
549 } 576 }
550 // get mount point of the directory 577 // get mount point of the directory
@@ -558,7 +585,7 @@ void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt) {
558 fwarning("read-only, read-write and noexec options are not applied recursively\n"); 585 fwarning("read-only, read-write and noexec options are not applied recursively\n");
559 mount_warning = 1; 586 mount_warning = 1;
560 } 587 }
561 fs_remount(dir, op, check_mnt); 588 fs_remount_simple(dir, op);
562 return; 589 return;
563 } 590 }
564 // build array with all mount points that need to get remounted 591 // build array with all mount points that need to get remounted
@@ -567,12 +594,25 @@ void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt) {
567 // remount 594 // remount
568 char **tmp = arr; 595 char **tmp = arr;
569 while (*tmp) { 596 while (*tmp) {
570 fs_remount(*tmp, op, check_mnt); 597 fs_remount_simple(*tmp, op);
571 free(*tmp++); 598 free(*tmp++);
572 } 599 }
573 free(arr); 600 free(arr);
574} 601}
575 602
603// resolve a path and remount it
604void fs_remount(const char *path, OPERATION op, int rec) {
605 assert(path);
606 char *rpath = realpath(path, NULL);
607 if (rpath) {
608 if (rec)
609 fs_remount_rec(rpath, op);
610 else
611 fs_remount_simple(rpath, op);
612 free(rpath);
613 }
614}
615
576// Disable /mnt, /media, /run/mount and /run/media access 616// Disable /mnt, /media, /run/mount and /run/media access
577void fs_mnt(const int enforce) { 617void fs_mnt(const int enforce) {
578 if (enforce) { 618 if (enforce) {
@@ -749,22 +789,22 @@ void fs_basic_fs(void) {
749 if (arg_debug) 789 if (arg_debug)
750 printf("Basic read-only filesystem:\n"); 790 printf("Basic read-only filesystem:\n");
751 if (!arg_writable_etc) { 791 if (!arg_writable_etc) {
752 fs_remount("/etc", MOUNT_READONLY, 0); 792 fs_remount("/etc", MOUNT_READONLY, 1);
753 if (uid) 793 if (uid)
754 fs_remount("/etc", MOUNT_NOEXEC, 0); 794 fs_remount("/etc", MOUNT_NOEXEC, 1);
755 } 795 }
756 if (!arg_writable_var) { 796 if (!arg_writable_var) {
757 fs_remount("/var", MOUNT_READONLY, 0); 797 fs_remount("/var", MOUNT_READONLY, 1);
758 if (uid) 798 if (uid)
759 fs_remount("/var", MOUNT_NOEXEC, 0); 799 fs_remount("/var", MOUNT_NOEXEC, 1);
760 } 800 }
761 fs_remount("/bin", MOUNT_READONLY, 0); 801 fs_remount("/usr", MOUNT_READONLY, 1);
762 fs_remount("/sbin", MOUNT_READONLY, 0); 802 fs_remount("/bin", MOUNT_READONLY, 1);
763 fs_remount("/lib", MOUNT_READONLY, 0); 803 fs_remount("/sbin", MOUNT_READONLY, 1);
764 fs_remount("/lib64", MOUNT_READONLY, 0); 804 fs_remount("/lib", MOUNT_READONLY, 1);
765 fs_remount("/lib32", MOUNT_READONLY, 0); 805 fs_remount("/lib64", MOUNT_READONLY, 1);
766 fs_remount("/libx32", MOUNT_READONLY, 0); 806 fs_remount("/lib32", MOUNT_READONLY, 1);
767 fs_remount("/usr", MOUNT_READONLY, 0); 807 fs_remount("/libx32", MOUNT_READONLY, 1);
768 808
769 // update /var directory in order to support multiple sandboxes running on the same root directory 809 // update /var directory in order to support multiple sandboxes running on the same root directory
770 fs_var_lock(); 810 fs_var_lock();
@@ -773,7 +813,7 @@ void fs_basic_fs(void) {
773 if (!arg_writable_var_log) 813 if (!arg_writable_var_log)
774 fs_var_log(); 814 fs_var_log();
775 else 815 else
776 fs_remount("/var/log", MOUNT_RDWR, 0); 816 fs_remount("/var/log", MOUNT_RDWR_NOCHECK, 0);
777 817
778 fs_var_lib(); 818 fs_var_lib();
779 fs_var_cache(); 819 fs_var_cache();