aboutsummaryrefslogtreecommitdiffstats
path: root/src/firejail/fs.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/firejail/fs.c')
-rw-r--r--src/firejail/fs.c163
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
57typedef enum {
58 UNSUCCESSFUL,
59 SUCCESSFUL
60} LAST_DISABLE_OPERATION;
61LAST_DISABLE_OPERATION last_disable = UNSUCCESSFUL;
62
63static void disable_file(OPERATION op, const char *filename) { 57static 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
591out: 596out:
@@ -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
783void disable_config(void) { 784void 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