diff options
Diffstat (limited to 'src/firejail/fs.c')
-rw-r--r-- | src/firejail/fs.c | 163 |
1 files changed, 78 insertions, 85 deletions
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index 09de11de9..eaf23e603 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -54,16 +54,9 @@ static char *opstr[] = { | |||
54 | [MOUNT_RDWR_NOCHECK] = "read-write", | 54 | [MOUNT_RDWR_NOCHECK] = "read-write", |
55 | }; | 55 | }; |
56 | 56 | ||
57 | typedef enum { | ||
58 | UNSUCCESSFUL, | ||
59 | SUCCESSFUL | ||
60 | } LAST_DISABLE_OPERATION; | ||
61 | LAST_DISABLE_OPERATION last_disable = UNSUCCESSFUL; | ||
62 | |||
63 | static void disable_file(OPERATION op, const char *filename) { | 57 | static void disable_file(OPERATION op, const char *filename) { |
64 | assert(filename); | 58 | assert(filename); |
65 | assert(op <OPERATION_MAX); | 59 | assert(op <OPERATION_MAX); |
66 | last_disable = UNSUCCESSFUL; | ||
67 | 60 | ||
68 | // Resolve all symlinks | 61 | // Resolve all symlinks |
69 | char* fname = realpath(filename, NULL); | 62 | char* fname = realpath(filename, NULL); |
@@ -71,20 +64,22 @@ static void disable_file(OPERATION op, const char *filename) { | |||
71 | return; | 64 | return; |
72 | } | 65 | } |
73 | if (fname == NULL && errno == EACCES) { | 66 | if (fname == NULL && errno == EACCES) { |
74 | if (arg_debug) | ||
75 | printf("Debug: no access to file %s, forcing mount\n", filename); | ||
76 | // realpath and stat functions will fail on FUSE filesystems | 67 | // realpath and stat functions will fail on FUSE filesystems |
77 | // they don't seem to like a uid of 0 | 68 | // they don't seem to like a uid of 0 |
78 | // force mounting | 69 | // force mounting |
79 | int rv = mount(RUN_RO_DIR, filename, "none", MS_BIND, "mode=400,gid=0"); | 70 | int fd = open(filename, O_PATH|O_CLOEXEC); |
80 | if (rv == 0) | 71 | if (fd < 0) { |
81 | last_disable = SUCCESSFUL; | 72 | if (arg_debug) |
82 | else { | 73 | printf("Warning (blacklisting): cannot open %s: %s\n", filename, strerror(errno)); |
83 | rv = mount(RUN_RO_FILE, filename, "none", MS_BIND, "mode=400,gid=0"); | 74 | return; |
84 | if (rv == 0) | ||
85 | last_disable = SUCCESSFUL; | ||
86 | } | 75 | } |
87 | if (last_disable == SUCCESSFUL) { | 76 | |
77 | int err = bind_mount_path_to_fd(RUN_RO_DIR, fd); | ||
78 | if (err < 0) | ||
79 | err = bind_mount_path_to_fd(RUN_RO_FILE, fd); | ||
80 | close(fd); | ||
81 | |||
82 | if (err == 0) { | ||
88 | if (arg_debug) | 83 | if (arg_debug) |
89 | printf("Disable %s\n", filename); | 84 | printf("Disable %s\n", filename); |
90 | if (op == BLACKLIST_FILE) | 85 | if (op == BLACKLIST_FILE) |
@@ -92,21 +87,18 @@ static void disable_file(OPERATION op, const char *filename) { | |||
92 | else | 87 | else |
93 | fs_logger2("blacklist-nolog", filename); | 88 | fs_logger2("blacklist-nolog", filename); |
94 | } | 89 | } |
95 | else { | 90 | else if (arg_debug) |
96 | if (arg_debug) | 91 | printf("Warning (blacklisting): cannot mount on %s\n", filename); |
97 | printf("Warning (blacklisting): %s is an invalid file, skipping...\n", filename); | ||
98 | } | ||
99 | 92 | ||
100 | return; | 93 | return; |
101 | } | 94 | } |
102 | 95 | ||
103 | // if the file is not present, do nothing | 96 | // if the file is not present, do nothing |
97 | assert(fname); | ||
104 | struct stat s; | 98 | struct stat s; |
105 | if (fname == NULL) | 99 | if (stat(fname, &s) < 0) { |
106 | return; | ||
107 | if (stat(fname, &s) == -1) { | ||
108 | if (arg_debug) | 100 | if (arg_debug) |
109 | fwarning("%s does not exist, skipping...\n", fname); | 101 | printf("Warning (blacklisting): cannot access %s: %s\n", fname, strerror(errno)); |
110 | free(fname); | 102 | free(fname); |
111 | return; | 103 | return; |
112 | } | 104 | } |
@@ -115,8 +107,10 @@ static void disable_file(OPERATION op, const char *filename) { | |||
115 | // we migth have a file found in ${PATH} pointing to /usr/bin/firejail | 107 | // we migth have a file found in ${PATH} pointing to /usr/bin/firejail |
116 | // blacklisting it here will end up breaking situations like user clicks on a link in Thunderbird | 108 | // blacklisting it here will end up breaking situations like user clicks on a link in Thunderbird |
117 | // and expects Firefox to open in the same sandbox | 109 | // and expects Firefox to open in the same sandbox |
118 | if (strcmp(BINDIR "/firejail", fname) == 0) | 110 | if (strcmp(BINDIR "/firejail", fname) == 0) { |
111 | free(fname); | ||
119 | return; | 112 | return; |
113 | } | ||
120 | 114 | ||
121 | // modify the file | 115 | // modify the file |
122 | if (op == BLACKLIST_FILE || op == BLACKLIST_NOLOG) { | 116 | if (op == BLACKLIST_FILE || op == BLACKLIST_NOLOG) { |
@@ -141,15 +135,23 @@ static void disable_file(OPERATION op, const char *filename) { | |||
141 | printf(" - no logging\n"); | 135 | printf(" - no logging\n"); |
142 | } | 136 | } |
143 | 137 | ||
138 | int fd = open(fname, O_PATH|O_CLOEXEC); | ||
139 | if (fd < 0) { | ||
140 | if (arg_debug) | ||
141 | printf("Warning (blacklisting): cannot open %s: %s\n", fname, strerror(errno)); | ||
142 | free(fname); | ||
143 | return; | ||
144 | } | ||
144 | if (S_ISDIR(s.st_mode)) { | 145 | if (S_ISDIR(s.st_mode)) { |
145 | if (mount(RUN_RO_DIR, fname, "none", MS_BIND, "mode=400,gid=0") < 0) | 146 | if (bind_mount_path_to_fd(RUN_RO_DIR, fd) < 0) |
146 | errExit("disable file"); | 147 | errExit("disable file"); |
147 | } | 148 | } |
148 | else { | 149 | else { |
149 | if (mount(RUN_RO_FILE, fname, "none", MS_BIND, "mode=400,gid=0") < 0) | 150 | if (bind_mount_path_to_fd(RUN_RO_FILE, fd) < 0) |
150 | errExit("disable file"); | 151 | errExit("disable file"); |
151 | } | 152 | } |
152 | last_disable = SUCCESSFUL; | 153 | close(fd); |
154 | |||
153 | if (op == BLACKLIST_FILE) | 155 | if (op == BLACKLIST_FILE) |
154 | fs_logger2("blacklist", fname); | 156 | fs_logger2("blacklist", fname); |
155 | else | 157 | else |
@@ -158,7 +160,6 @@ static void disable_file(OPERATION op, const char *filename) { | |||
158 | } | 160 | } |
159 | else if (op == MOUNT_READONLY || op == MOUNT_RDWR || op == MOUNT_NOEXEC) { | 161 | else if (op == MOUNT_READONLY || op == MOUNT_RDWR || op == MOUNT_NOEXEC) { |
160 | fs_remount_rec(fname, op); | 162 | fs_remount_rec(fname, op); |
161 | // todo: last_disable = SUCCESSFUL; | ||
162 | } | 163 | } |
163 | else if (op == MOUNT_TMPFS) { | 164 | else if (op == MOUNT_TMPFS) { |
164 | if (S_ISDIR(s.st_mode)) { | 165 | if (S_ISDIR(s.st_mode)) { |
@@ -171,7 +172,6 @@ static void disable_file(OPERATION op, const char *filename) { | |||
171 | } | 172 | } |
172 | fs_tmpfs(fname, getuid()); | 173 | fs_tmpfs(fname, getuid()); |
173 | selinux_relabel_path(fname, fname); | 174 | selinux_relabel_path(fname, fname); |
174 | last_disable = SUCCESSFUL; | ||
175 | } | 175 | } |
176 | else | 176 | else |
177 | fwarning("%s is not a directory; cannot mount a tmpfs on top of it.\n", fname); | 177 | fwarning("%s is not a directory; cannot mount a tmpfs on top of it.\n", fname); |
@@ -493,35 +493,38 @@ static void fs_remount_simple(const char *path, OPERATION op) { | |||
493 | assert(path); | 493 | assert(path); |
494 | 494 | ||
495 | // open path without following symbolic links | 495 | // open path without following symbolic links |
496 | int fd1 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 496 | int fd = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
497 | if (fd1 == -1) | 497 | if (fd < 0) |
498 | goto out; | 498 | goto out; |
499 | struct stat s1; | 499 | |
500 | if (fstat(fd1, &s1) == -1) { | 500 | struct stat s; |
501 | if (fstat(fd, &s) < 0) { | ||
501 | // fstat can fail with EACCES if path is a FUSE mount, | 502 | // fstat can fail with EACCES if path is a FUSE mount, |
502 | // mounted without 'allow_root' or 'allow_other' | 503 | // mounted without 'allow_root' or 'allow_other' |
503 | if (errno != EACCES) | 504 | if (errno != EACCES) |
504 | errExit("fstat"); | 505 | errExit("fstat"); |
505 | close(fd1); | 506 | close(fd); |
506 | goto out; | 507 | goto out; |
507 | } | 508 | } |
508 | // get mount flags | 509 | // get mount flags |
509 | struct statvfs buf; | 510 | struct statvfs buf; |
510 | if (fstatvfs(fd1, &buf) == -1) | 511 | if (fstatvfs(fd, &buf) < 0) { |
511 | errExit("fstatvfs"); | 512 | close(fd); |
513 | goto out; | ||
514 | } | ||
512 | unsigned long flags = buf.f_flag; | 515 | unsigned long flags = buf.f_flag; |
513 | 516 | ||
514 | // read-write option | 517 | // read-write option |
515 | if (op == MOUNT_RDWR || op == MOUNT_RDWR_NOCHECK) { | 518 | if (op == MOUNT_RDWR || op == MOUNT_RDWR_NOCHECK) { |
516 | // nothing to do if there is no read-only flag | 519 | // nothing to do if there is no read-only flag |
517 | if ((flags & MS_RDONLY) == 0) { | 520 | if ((flags & MS_RDONLY) == 0) { |
518 | close(fd1); | 521 | close(fd); |
519 | return; | 522 | return; |
520 | } | 523 | } |
521 | // allow only user owned directories, except the user is root | 524 | // allow only user owned directories, except the user is root |
522 | if (op != MOUNT_RDWR_NOCHECK && getuid() != 0 && s1.st_uid != getuid()) { | 525 | if (op != MOUNT_RDWR_NOCHECK && getuid() != 0 && s.st_uid != getuid()) { |
523 | fwarning("you are not allowed to change %s to read-write\n", path); | 526 | fwarning("you are not allowed to change %s to read-write\n", path); |
524 | close(fd1); | 527 | close(fd); |
525 | return; | 528 | return; |
526 | } | 529 | } |
527 | flags &= ~MS_RDONLY; | 530 | flags &= ~MS_RDONLY; |
@@ -530,7 +533,7 @@ static void fs_remount_simple(const char *path, OPERATION op) { | |||
530 | else if (op == MOUNT_NOEXEC) { | 533 | else if (op == MOUNT_NOEXEC) { |
531 | // nothing to do if path is mounted noexec already | 534 | // nothing to do if path is mounted noexec already |
532 | if ((flags & (MS_NOEXEC|MS_NODEV|MS_NOSUID)) == (MS_NOEXEC|MS_NODEV|MS_NOSUID)) { | 535 | if ((flags & (MS_NOEXEC|MS_NODEV|MS_NOSUID)) == (MS_NOEXEC|MS_NODEV|MS_NOSUID)) { |
533 | close(fd1); | 536 | close(fd); |
534 | return; | 537 | return; |
535 | } | 538 | } |
536 | flags |= MS_NOEXEC|MS_NODEV|MS_NOSUID; | 539 | flags |= MS_NOEXEC|MS_NODEV|MS_NOSUID; |
@@ -539,7 +542,7 @@ static void fs_remount_simple(const char *path, OPERATION op) { | |||
539 | else if (op == MOUNT_READONLY) { | 542 | else if (op == MOUNT_READONLY) { |
540 | // nothing to do if path is mounted read-only already | 543 | // nothing to do if path is mounted read-only already |
541 | if ((flags & MS_RDONLY) == MS_RDONLY) { | 544 | if ((flags & MS_RDONLY) == MS_RDONLY) { |
542 | close(fd1); | 545 | close(fd); |
543 | return; | 546 | return; |
544 | } | 547 | } |
545 | flags |= MS_RDONLY; | 548 | flags |= MS_RDONLY; |
@@ -549,29 +552,33 @@ static void fs_remount_simple(const char *path, OPERATION op) { | |||
549 | 552 | ||
550 | if (arg_debug) | 553 | if (arg_debug) |
551 | printf("Mounting %s %s\n", opstr[op], path); | 554 | printf("Mounting %s %s\n", opstr[op], path); |
555 | |||
556 | // make path a mount point: | ||
552 | // mount --bind path path | 557 | // mount --bind path path |
553 | char *proc; | 558 | int err = bind_mount_by_fd(fd, fd); |
554 | if (asprintf(&proc, "/proc/self/fd/%d", fd1) == -1) | 559 | if (err) { |
555 | errExit("asprintf"); | 560 | close(fd); |
556 | if (mount(proc, proc, NULL, MS_BIND|MS_REC, NULL) < 0) | 561 | goto out; |
557 | errExit("mount"); | 562 | } |
558 | free(proc); | ||
559 | 563 | ||
560 | // mount --bind -o remount,ro path | 564 | // remount the mount point |
561 | // need to open path again without following symbolic links | 565 | // need to open path again |
562 | int fd2 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 566 | int fd2 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
563 | if (fd2 == -1) | 567 | close(fd); // earliest timepoint to close fd |
564 | errExit("open"); | 568 | if (fd2 < 0) |
569 | goto out; | ||
570 | |||
571 | // device and inode number should be the same | ||
565 | struct stat s2; | 572 | struct stat s2; |
566 | if (fstat(fd2, &s2) == -1) | 573 | if (fstat(fd2, &s2) < 0) |
567 | errExit("fstat"); | 574 | errExit("fstat"); |
568 | // device and inode number should be the same | 575 | if (s.st_dev != s2.st_dev || s.st_ino != s2.st_ino) |
569 | if (s1.st_dev != s2.st_dev || s1.st_ino != s2.st_ino) | ||
570 | errLogExit("invalid %s mount", opstr[op]); | 576 | errLogExit("invalid %s mount", opstr[op]); |
571 | if (asprintf(&proc, "/proc/self/fd/%d", fd2) == -1) | 577 | |
572 | errExit("asprintf"); | 578 | err = remount_by_fd(fd2, flags); |
573 | if (mount(NULL, proc, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0) | 579 | close(fd2); |
574 | errExit("mount"); | 580 | if (err) |
581 | goto out; | ||
575 | 582 | ||
576 | // run a sanity check on /proc/self/mountinfo and confirm that target of the last | 583 | // run a sanity check on /proc/self/mountinfo and confirm that target of the last |
577 | // mount operation was path; if there are other mount points contained inside path, | 584 | // mount operation was path; if there are other mount points contained inside path, |
@@ -582,10 +589,8 @@ static void fs_remount_simple(const char *path, OPERATION op) { | |||
582 | (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/')) | 589 | (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/')) |
583 | && strcmp(path, "/") != 0) // support read-only=/ | 590 | && strcmp(path, "/") != 0) // support read-only=/ |
584 | errLogExit("invalid %s mount", opstr[op]); | 591 | errLogExit("invalid %s mount", opstr[op]); |
592 | |||
585 | fs_logger2(opstr[op], path); | 593 | fs_logger2(opstr[op], path); |
586 | free(proc); | ||
587 | close(fd1); | ||
588 | close(fd2); | ||
589 | return; | 594 | return; |
590 | 595 | ||
591 | out: | 596 | out: |
@@ -743,8 +748,6 @@ void fs_proc_sys_dev_boot(void) { | |||
743 | 748 | ||
744 | // disable various ipc sockets in /run/user | 749 | // disable various ipc sockets in /run/user |
745 | if (!arg_writable_run_user) { | 750 | if (!arg_writable_run_user) { |
746 | struct stat s; | ||
747 | |||
748 | char *fname; | 751 | char *fname; |
749 | if (asprintf(&fname, "/run/user/%d", getuid()) == -1) | 752 | if (asprintf(&fname, "/run/user/%d", getuid()) == -1) |
750 | errExit("asprintf"); | 753 | errExit("asprintf"); |
@@ -755,8 +758,7 @@ void fs_proc_sys_dev_boot(void) { | |||
755 | errExit("asprintf"); | 758 | errExit("asprintf"); |
756 | if (create_empty_dir_as_user(fnamegpg, 0700)) | 759 | if (create_empty_dir_as_user(fnamegpg, 0700)) |
757 | fs_logger2("create", fnamegpg); | 760 | fs_logger2("create", fnamegpg); |
758 | if (stat(fnamegpg, &s) == 0) | 761 | disable_file(BLACKLIST_FILE, fnamegpg); |
759 | disable_file(BLACKLIST_FILE, fnamegpg); | ||
760 | free(fnamegpg); | 762 | free(fnamegpg); |
761 | 763 | ||
762 | // disable /run/user/{uid}/systemd | 764 | // disable /run/user/{uid}/systemd |
@@ -765,8 +767,7 @@ void fs_proc_sys_dev_boot(void) { | |||
765 | errExit("asprintf"); | 767 | errExit("asprintf"); |
766 | if (create_empty_dir_as_user(fnamesysd, 0755)) | 768 | if (create_empty_dir_as_user(fnamesysd, 0755)) |
767 | fs_logger2("create", fnamesysd); | 769 | fs_logger2("create", fnamesysd); |
768 | if (stat(fnamesysd, &s) == 0) | 770 | disable_file(BLACKLIST_FILE, fnamesysd); |
769 | disable_file(BLACKLIST_FILE, fnamesysd); | ||
770 | free(fnamesysd); | 771 | free(fnamesysd); |
771 | } | 772 | } |
772 | free(fname); | 773 | free(fname); |
@@ -781,26 +782,18 @@ void fs_proc_sys_dev_boot(void) { | |||
781 | 782 | ||
782 | // disable firejail configuration in ~/.config/firejail | 783 | // disable firejail configuration in ~/.config/firejail |
783 | void disable_config(void) { | 784 | void disable_config(void) { |
784 | struct stat s; | ||
785 | |||
786 | char *fname; | 785 | char *fname; |
787 | if (asprintf(&fname, "%s/.config/firejail", cfg.homedir) == -1) | 786 | if (asprintf(&fname, "%s/.config/firejail", cfg.homedir) == -1) |
788 | errExit("asprintf"); | 787 | errExit("asprintf"); |
789 | if (stat(fname, &s) == 0) | 788 | disable_file(BLACKLIST_FILE, fname); |
790 | disable_file(BLACKLIST_FILE, fname); | ||
791 | free(fname); | 789 | free(fname); |
792 | 790 | ||
793 | // disable run time information | 791 | // disable run time information |
794 | if (stat(RUN_FIREJAIL_NETWORK_DIR, &s) == 0) | 792 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NETWORK_DIR); |
795 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NETWORK_DIR); | 793 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_BANDWIDTH_DIR); |
796 | if (stat(RUN_FIREJAIL_BANDWIDTH_DIR, &s) == 0) | 794 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NAME_DIR); |
797 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_BANDWIDTH_DIR); | 795 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_PROFILE_DIR); |
798 | if (stat(RUN_FIREJAIL_NAME_DIR, &s) == 0) | 796 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_X11_DIR); |
799 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NAME_DIR); | ||
800 | if (stat(RUN_FIREJAIL_PROFILE_DIR, &s) == 0) | ||
801 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_PROFILE_DIR); | ||
802 | if (stat(RUN_FIREJAIL_X11_DIR, &s) == 0) | ||
803 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_X11_DIR); | ||
804 | } | 797 | } |
805 | 798 | ||
806 | 799 | ||
@@ -1241,8 +1234,8 @@ void fs_private_tmp(void) { | |||
1241 | 1234 | ||
1242 | // whitelist x11 directory | 1235 | // whitelist x11 directory |
1243 | profile_add("whitelist /tmp/.X11-unix"); | 1236 | profile_add("whitelist /tmp/.X11-unix"); |
1244 | // read-only x11 directory | 1237 | // read-only x11 directory |
1245 | profile_add("read-only /tmp/.X11-unix"); | 1238 | profile_add("read-only /tmp/.X11-unix"); |
1246 | 1239 | ||
1247 | // whitelist any pulse* file in /tmp directory | 1240 | // whitelist any pulse* file in /tmp directory |
1248 | // some distros use PulseAudio sockets under /tmp instead of the socket in /urn/user | 1241 | // some distros use PulseAudio sockets under /tmp instead of the socket in /urn/user |