aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar startx2017 <vradu.startx@yandex.com>2020-03-13 17:18:58 -0500
committerLibravatar GitHub <noreply@github.com>2020-03-13 17:18:58 -0500
commit4b1d2b9502254600e1d8e99ab4413e7530404c2a (patch)
treec2f85d953a16a967a500c9fbce3c32e55da31c80 /src
parentFix "Extraction not performed" on Debian 10 (diff)
parentfail if opening the resolved path fails (diff)
downloadfirejail-4b1d2b9502254600e1d8e99ab4413e7530404c2a.tar.gz
firejail-4b1d2b9502254600e1d8e99ab4413e7530404c2a.tar.zst
firejail-4b1d2b9502254600e1d8e99ab4413e7530404c2a.zip
Merge pull request #3268 from smitsohu/remount
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.c194
2 files changed, 118 insertions, 80 deletions
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h
index 0e4fcea6a..7391a8994 100644
--- a/src/firejail/firejail.h
+++ b/src/firejail/firejail.h
@@ -395,6 +395,7 @@ typedef enum {
395 MOUNT_TMPFS, 395 MOUNT_TMPFS,
396 MOUNT_NOEXEC, 396 MOUNT_NOEXEC,
397 MOUNT_RDWR, 397 MOUNT_RDWR,
398 MOUNT_RDWR_NOCHECK, // no check of ownership
398 OPERATION_MAX 399 OPERATION_MAX
399} OPERATION; 400} OPERATION;
400 401
@@ -403,8 +404,7 @@ void fs_blacklist(void);
403// mount a writable tmpfs 404// mount a writable tmpfs
404void fs_tmpfs(const char *dir, unsigned check_owner); 405void fs_tmpfs(const char *dir, unsigned check_owner);
405// remount noexec/nodev/nosuid or read-only or read-write 406// remount noexec/nodev/nosuid or read-only or read-write
406void fs_remount(const char *dir, OPERATION op, unsigned check_mnt); 407void fs_remount(const char *dir, OPERATION op, int rec);
407void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt);
408// mount /proc and /sys directories 408// mount /proc and /sys directories
409void fs_proc_sys_dev_boot(void); 409void fs_proc_sys_dev_boot(void);
410// blacklist firejail configuration and runtime directories 410// blacklist firejail configuration and runtime directories
diff --git a/src/firejail/fs.c b/src/firejail/fs.c
index c7dd91b06..d7f6c899d 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,104 @@ 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 errExit("open");
483 // identify file owner
486 struct stat s; 484 struct stat s;
487 int rv = stat(dir, &s); 485 if (fstat(fd, &s) == -1)
488 if (rv == 0) { 486 errExit("fstat");
489 unsigned long flags = 0; 487 // get mount flags
490 if (get_mount_flags(dir, &flags) != 0) { 488 struct statvfs buf;
491 fwarning("cannot remount %s\n", dir); 489 if (fstatvfs(fd, &buf) == -1)
490 errExit("fstatvfs");
491 unsigned long flags = buf.f_flag;
492
493 // read-write option
494 if (op == MOUNT_RDWR || op == MOUNT_RDWR_NOCHECK) {
495 // nothing to do if there is no read-only flag
496 if ((flags & MS_RDONLY) == 0) {
497 close(fd);
492 return; 498 return;
493 } 499 }
494 if (op == MOUNT_RDWR) { 500 // allow only user owned directories, except the user is root
495 // allow only user owned directories, except the user is root 501 if (op == MOUNT_RDWR && getuid() != 0 && s.st_uid != getuid()) {
496 if (getuid() != 0 && s.st_uid != getuid()) { 502 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); 503 close(fd);
498 return; 504 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 } 505 }
509 else if (op == MOUNT_READONLY) { 506 flags &= ~MS_RDONLY;
510 if ((flags & MS_RDONLY) == MS_RDONLY) 507 }
511 return; 508 // noexec option
512 flags |= MS_RDONLY; 509 else if (op == MOUNT_NOEXEC) {
510 // nothing to do if path is mounted noexec already
511 if ((flags & (MS_NOEXEC|MS_NODEV|MS_NOSUID)) == (MS_NOEXEC|MS_NODEV|MS_NOSUID)) {
512 close(fd);
513 return;
513 } 514 }
514 else 515 flags |= MS_NOEXEC|MS_NODEV|MS_NOSUID;
515 assert(0); 516 }
516 517 // read-only option
517 if (arg_debug) 518 else if (op == MOUNT_READONLY) {
518 printf("Mounting %s %s\n", opstr[op], dir); 519 // nothing to do if path is mounted read-only already
519 // mount --bind /bin /bin 520 if ((flags & MS_RDONLY) == MS_RDONLY) {
520 // mount --bind -o remount,rw /bin 521 close(fd);
521 if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || 522 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 } 523 }
536 fs_logger2(opstr[op], dir); 524 flags |= MS_RDONLY;
537 } 525 }
526 else
527 assert(0);
528
529 if (arg_debug)
530 printf("Mounting %s %s\n", opstr[op], path);
531 // mount --bind /bin /bin
532 char *proc;
533 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
534 errExit("asprintf");
535 if (mount(proc, proc, NULL, MS_BIND|MS_REC, NULL) < 0)
536 errExit("mount");
537 free(proc);
538 close(fd);
539
540 // mount --bind -o remount,ro /bin
541 // we need to open path again
542 fd = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
543 if (fd == -1)
544 errExit("open");
545 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
546 errExit("asprintf");
547 if (mount(NULL, proc, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0)
548 errExit("mount");
549
550 // run a sanity check on /proc/self/mountinfo and confirm that target of the last
551 // mount operation was path; if there are other mount points contained inside path,
552 // one of those will show up as target of the last mount operation instead
553 MountData *mptr = get_last_mount();
554 size_t len = strlen(path);
555 if ((strncmp(mptr->dir, path, len) != 0 ||
556 (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/'))
557 && strcmp(path, "/") != 0) // support read-only=/
558 errLogExit("invalid %s mount", opstr[op]);
559 fs_logger2(opstr[op], path);
560 free(proc);
561 close(fd);
538} 562}
539 563
540void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt) { 564// remount recursively; requires a resolved path
565static void fs_remount_rec(const char *dir, OPERATION op) {
541 assert(dir); 566 assert(dir);
542 struct stat s; 567 struct stat s;
543 if (stat(dir, &s) != 0) 568 if (stat(dir, &s) != 0)
544 return; 569 return;
545 if (!S_ISDIR(s.st_mode)) { 570 if (!S_ISDIR(s.st_mode)) {
546 // no need to search in /proc/self/mountinfo for submounts if not a directory 571 // no need to search in /proc/self/mountinfo for submounts if not a directory
547 fs_remount(dir, op, check_mnt); 572 fs_remount_simple(dir, op);
548 return; 573 return;
549 } 574 }
550 // get mount point of the directory 575 // get mount point of the directory
@@ -558,7 +583,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"); 583 fwarning("read-only, read-write and noexec options are not applied recursively\n");
559 mount_warning = 1; 584 mount_warning = 1;
560 } 585 }
561 fs_remount(dir, op, check_mnt); 586 fs_remount_simple(dir, op);
562 return; 587 return;
563 } 588 }
564 // build array with all mount points that need to get remounted 589 // build array with all mount points that need to get remounted
@@ -567,12 +592,25 @@ void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt) {
567 // remount 592 // remount
568 char **tmp = arr; 593 char **tmp = arr;
569 while (*tmp) { 594 while (*tmp) {
570 fs_remount(*tmp, op, check_mnt); 595 fs_remount_simple(*tmp, op);
571 free(*tmp++); 596 free(*tmp++);
572 } 597 }
573 free(arr); 598 free(arr);
574} 599}
575 600
601// resolve a path and remount it
602void fs_remount(const char *path, OPERATION op, int rec) {
603 assert(path);
604 char *rpath = realpath(path, NULL);
605 if (rpath) {
606 if (rec)
607 fs_remount_rec(rpath, op);
608 else
609 fs_remount_simple(rpath, op);
610 free(rpath);
611 }
612}
613
576// Disable /mnt, /media, /run/mount and /run/media access 614// Disable /mnt, /media, /run/mount and /run/media access
577void fs_mnt(const int enforce) { 615void fs_mnt(const int enforce) {
578 if (enforce) { 616 if (enforce) {
@@ -749,22 +787,22 @@ void fs_basic_fs(void) {
749 if (arg_debug) 787 if (arg_debug)
750 printf("Basic read-only filesystem:\n"); 788 printf("Basic read-only filesystem:\n");
751 if (!arg_writable_etc) { 789 if (!arg_writable_etc) {
752 fs_remount("/etc", MOUNT_READONLY, 0); 790 fs_remount("/etc", MOUNT_READONLY, 1);
753 if (uid) 791 if (uid)
754 fs_remount("/etc", MOUNT_NOEXEC, 0); 792 fs_remount("/etc", MOUNT_NOEXEC, 1);
755 } 793 }
756 if (!arg_writable_var) { 794 if (!arg_writable_var) {
757 fs_remount("/var", MOUNT_READONLY, 0); 795 fs_remount("/var", MOUNT_READONLY, 1);
758 if (uid) 796 if (uid)
759 fs_remount("/var", MOUNT_NOEXEC, 0); 797 fs_remount("/var", MOUNT_NOEXEC, 1);
760 } 798 }
761 fs_remount("/bin", MOUNT_READONLY, 0); 799 fs_remount("/usr", MOUNT_READONLY, 1);
762 fs_remount("/sbin", MOUNT_READONLY, 0); 800 fs_remount("/bin", MOUNT_READONLY, 1);
763 fs_remount("/lib", MOUNT_READONLY, 0); 801 fs_remount("/sbin", MOUNT_READONLY, 1);
764 fs_remount("/lib64", MOUNT_READONLY, 0); 802 fs_remount("/lib", MOUNT_READONLY, 1);
765 fs_remount("/lib32", MOUNT_READONLY, 0); 803 fs_remount("/lib64", MOUNT_READONLY, 1);
766 fs_remount("/libx32", MOUNT_READONLY, 0); 804 fs_remount("/lib32", MOUNT_READONLY, 1);
767 fs_remount("/usr", MOUNT_READONLY, 0); 805 fs_remount("/libx32", MOUNT_READONLY, 1);
768 806
769 // update /var directory in order to support multiple sandboxes running on the same root directory 807 // update /var directory in order to support multiple sandboxes running on the same root directory
770 fs_var_lock(); 808 fs_var_lock();
@@ -773,7 +811,7 @@ void fs_basic_fs(void) {
773 if (!arg_writable_var_log) 811 if (!arg_writable_var_log)
774 fs_var_log(); 812 fs_var_log();
775 else 813 else
776 fs_remount("/var/log", MOUNT_RDWR, 0); 814 fs_remount("/var/log", MOUNT_RDWR_NOCHECK, 0);
777 815
778 fs_var_lib(); 816 fs_var_lib();
779 fs_var_cache(); 817 fs_var_cache();