diff options
author | startx2017 <vradu.startx@yandex.com> | 2020-03-13 17:18:58 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-13 17:18:58 -0500 |
commit | 4b1d2b9502254600e1d8e99ab4413e7530404c2a (patch) | |
tree | c2f85d953a16a967a500c9fbce3c32e55da31c80 /src | |
parent | Fix "Extraction not performed" on Debian 10 (diff) | |
parent | fail if opening the resolved path fails (diff) | |
download | firejail-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.h | 4 | ||||
-rw-r--r-- | src/firejail/fs.c | 194 |
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 |
404 | void fs_tmpfs(const char *dir, unsigned check_owner); | 405 | void 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 |
406 | void fs_remount(const char *dir, OPERATION op, unsigned check_mnt); | 407 | void fs_remount(const char *dir, OPERATION op, int rec); |
407 | void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt); | ||
408 | // mount /proc and /sys directories | 408 | // mount /proc and /sys directories |
409 | void fs_proc_sys_dev_boot(void); | 409 | void 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 | //*********************************************** |
45 | static void fs_remount_rec(const char *dir, OPERATION op); | ||
46 | |||
46 | static char *opstr[] = { | 47 | static 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 | ||
55 | typedef enum { | 57 | typedef 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 | ||
428 | static 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 |
443 | void fs_tmpfs(const char *dir, unsigned check_owner) { | 435 | void 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 | ||
483 | void 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); | 476 | static 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 | ||
540 | void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt) { | 564 | // remount recursively; requires a resolved path |
565 | static 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 | ||
602 | void 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 |
577 | void fs_mnt(const int enforce) { | 615 | void 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(); |