diff options
-rw-r--r-- | src/firejail/fs_whitelist.c | 165 | ||||
-rw-r--r-- | src/firejail/util.c | 16 |
2 files changed, 115 insertions, 66 deletions
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c index d11f727ec..9fbe45726 100644 --- a/src/firejail/fs_whitelist.c +++ b/src/firejail/fs_whitelist.c | |||
@@ -191,7 +191,7 @@ static int mkpath(const char* path, mode_t mode) { | |||
191 | 191 | ||
192 | static void whitelist_path(ProfileEntry *entry) { | 192 | static void whitelist_path(ProfileEntry *entry) { |
193 | assert(entry); | 193 | assert(entry); |
194 | char *path = entry->data + 10; | 194 | const char *path = entry->data + 10; |
195 | assert(path); | 195 | assert(path); |
196 | const char *fname; | 196 | const char *fname; |
197 | char *wfile = NULL; | 197 | char *wfile = NULL; |
@@ -280,68 +280,103 @@ static void whitelist_path(ProfileEntry *entry) { | |||
280 | } | 280 | } |
281 | assert(wfile); | 281 | assert(wfile); |
282 | 282 | ||
283 | // check if the file exists | 283 | // check if the file exists, confirm again there is no symlink |
284 | EUID_USER(); | 284 | EUID_USER(); |
285 | struct stat s; | 285 | int fd = safe_fd(wfile, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
286 | if (stat(wfile, &s) == 0) { | 286 | if (fd == -1) { |
287 | if (arg_debug || arg_debug_whitelists) | ||
288 | printf("Whitelisting %s\n", path); | ||
289 | } | ||
290 | else { | ||
291 | free(wfile); | 287 | free(wfile); |
292 | EUID_ROOT(); | 288 | EUID_ROOT(); |
293 | return; | 289 | return; |
294 | } | 290 | } |
295 | EUID_ROOT(); | 291 | struct stat wfilestat; |
292 | if (fstat(fd, &wfilestat) == -1) | ||
293 | errExit("fstat"); | ||
294 | if (S_ISLNK(wfilestat.st_mode)) { | ||
295 | fprintf(stderr, "Error: unexpected symbolic link %s\n", path); | ||
296 | exit(1); | ||
297 | } | ||
298 | close(fd); | ||
296 | 299 | ||
297 | // create the path if necessary | 300 | if (arg_debug || arg_debug_whitelists) |
298 | mkpath(path, s.st_mode); | 301 | printf("Whitelisting %s\n", path); |
299 | fs_logger2("whitelist", path); | 302 | fs_logger2("whitelist", path); |
300 | 303 | ||
301 | // process directory | 304 | // create the path if necessary |
302 | if (S_ISDIR(s.st_mode)) { | 305 | EUID_ROOT(); |
303 | // create directory | 306 | struct stat s; |
304 | int rv = mkdir(path, 0755); | 307 | if (stat(path, &s) == -1) { |
305 | (void) rv; | 308 | mkpath(path, 0755); |
306 | } | 309 | if (S_ISDIR(wfilestat.st_mode)) { |
307 | 310 | int rv = mkdir(path, 0755); | |
308 | // process regular file | 311 | if (rv) { |
309 | else { | 312 | fprintf(stderr, "Error: cannot create directory %s\n", path); |
310 | if (access(path, R_OK)) { | ||
311 | // create an empty file | ||
312 | FILE *fp = fopen(path, "w"); | ||
313 | if (!fp) { | ||
314 | fprintf(stderr, "Error: cannot create empty file in home directory\n"); | ||
315 | exit(1); | 313 | exit(1); |
316 | } | 314 | } |
317 | // set file properties | ||
318 | SET_PERMS_STREAM(fp, s.st_uid, s.st_gid, s.st_mode); | ||
319 | fclose(fp); | ||
320 | } | 315 | } |
321 | else { | 316 | else { |
317 | int fd2 = open(path, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR); | ||
318 | if (fd2 == -1) { | ||
319 | fprintf(stderr, "Error: cannot create empty file %s\n", path); | ||
320 | exit(1); | ||
321 | } | ||
322 | close(fd2); | ||
323 | } | ||
324 | } | ||
325 | else { | ||
326 | if (!S_ISDIR(s.st_mode)) { | ||
322 | free(wfile); | 327 | free(wfile); |
323 | return; // the file is already present | 328 | return; // the file is already present |
324 | } | 329 | } |
325 | } | 330 | } |
326 | 331 | ||
327 | // mount | 332 | // get a file descriptor for path; if path contains anything other than directories |
328 | if (mount(wfile, path, NULL, MS_BIND|MS_REC, NULL) < 0) | 333 | // or a regular file, assume it is whitelisted already |
334 | int fd3 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | ||
335 | if (fd3 == -1) { | ||
336 | free(wfile); | ||
337 | return; | ||
338 | } | ||
339 | if (fstat(fd3, &s) == -1) | ||
340 | errExit("fstat"); | ||
341 | if (!(S_ISDIR(s.st_mode) || S_ISREG(s.st_mode))) { | ||
342 | free(wfile); | ||
343 | close(fd3); | ||
344 | return; | ||
345 | } | ||
346 | |||
347 | // mount via the link in /proc/self/fd | ||
348 | char *proc; | ||
349 | if (asprintf(&proc, "/proc/self/fd/%d", fd3) == -1) | ||
350 | errExit("asprintf"); | ||
351 | if (mount(wfile, proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
329 | errExit("mount bind"); | 352 | errExit("mount bind"); |
353 | free(proc); | ||
354 | close(fd3); | ||
330 | 355 | ||
331 | // check the last mount operation | 356 | // check the last mount operation |
332 | MountData *mptr = get_last_mount(); // will do exit(1) if the mount cannot be found | 357 | MountData *mptr = get_last_mount(); // will do exit(1) if the mount cannot be found |
333 | 358 | ||
359 | if (strncmp(mptr->dir, path, strlen(path)) != 0) | ||
360 | errLogExit("invalid whitelist mount"); | ||
334 | // No mounts are allowed on top level directories. A destination such as "/etc" is very bad! | 361 | // No mounts are allowed on top level directories. A destination such as "/etc" is very bad! |
335 | // - there should be more than one '/' char in dest string | 362 | // - there should be more than one '/' char in dest string |
336 | if (mptr->dir == strrchr(mptr->dir, '/')) | 363 | if (mptr->dir == strrchr(mptr->dir, '/')) |
337 | errLogExit("invalid whitelist mount\n"); | 364 | errLogExit("invalid whitelist mount"); |
365 | // confirm the correct file is mounted on path | ||
366 | int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | ||
367 | if (fd4 == -1) | ||
368 | errExit("safe_fd"); | ||
369 | if (fstat(fd4, &s) == -1) | ||
370 | errExit("fstat"); | ||
371 | if (s.st_dev != wfilestat.st_dev || s.st_ino != wfilestat.st_ino) | ||
372 | errLogExit("invalid whitelist mount"); | ||
373 | close(fd4); | ||
338 | 374 | ||
339 | free(wfile); | 375 | free(wfile); |
340 | return; | 376 | return; |
341 | } | 377 | } |
342 | 378 | ||
343 | 379 | ||
344 | // whitelist for /home/user directory | ||
345 | void fs_whitelist(void) { | 380 | void fs_whitelist(void) { |
346 | char *homedir = cfg.homedir; | 381 | char *homedir = cfg.homedir; |
347 | assert(homedir); | 382 | assert(homedir); |
@@ -370,6 +405,7 @@ void fs_whitelist(void) { | |||
370 | 405 | ||
371 | // verify whitelist files, extract symbolic links, etc. | 406 | // verify whitelist files, extract symbolic links, etc. |
372 | EUID_USER(); | 407 | EUID_USER(); |
408 | struct stat s; | ||
373 | while (entry) { | 409 | while (entry) { |
374 | int nowhitelist_flag = 0; | 410 | int nowhitelist_flag = 0; |
375 | 411 | ||
@@ -512,10 +548,14 @@ void fs_whitelist(void) { | |||
512 | __LINE__, fname, cfg.homedir); | 548 | __LINE__, fname, cfg.homedir); |
513 | 549 | ||
514 | // both path and absolute path are under /home | 550 | // both path and absolute path are under /home |
515 | if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) != 0) { | 551 | if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0) { |
552 | // entire home directory is not allowed | ||
553 | if (*(fname + strlen(cfg.homedir)) != '/') | ||
554 | goto errexit; | ||
555 | } | ||
556 | else { | ||
516 | if (checkcfg(CFG_FOLLOW_SYMLINK_AS_USER)) { | 557 | if (checkcfg(CFG_FOLLOW_SYMLINK_AS_USER)) { |
517 | // check if the file is owned by the user | 558 | // check if the file is owned by the user |
518 | struct stat s; | ||
519 | if (stat(fname, &s) == 0 && s.st_uid != getuid()) | 559 | if (stat(fname, &s) == 0 && s.st_uid != getuid()) |
520 | goto errexit; | 560 | goto errexit; |
521 | } | 561 | } |
@@ -677,20 +717,25 @@ void fs_whitelist(void) { | |||
677 | free(nowhitelist); | 717 | free(nowhitelist); |
678 | 718 | ||
679 | EUID_ROOT(); | 719 | EUID_ROOT(); |
680 | // /home/user | 720 | // /home/user mountpoint |
681 | if (home_dir) { | 721 | if (home_dir) { |
682 | // keep a copy of real home dir in RUN_WHITELIST_HOME_USER_DIR | 722 | // check if /home/user directory exists |
683 | mkdir_attr(RUN_WHITELIST_HOME_USER_DIR, 0755, getuid(), getgid()); | 723 | if (stat(cfg.homedir, &s) == 0) { |
684 | if (mount(cfg.homedir, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) | 724 | // keep a copy of real home dir in RUN_WHITELIST_HOME_USER_DIR |
685 | errExit("mount bind"); | 725 | mkdir_attr(RUN_WHITELIST_HOME_USER_DIR, 0755, getuid(), getgid()); |
726 | if (mount(cfg.homedir, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
727 | errExit("mount bind"); | ||
686 | 728 | ||
687 | // mount a tmpfs and initialize /home/user | 729 | // mount a tmpfs and initialize /home/user |
688 | fs_private(); | 730 | fs_private(); |
731 | } | ||
732 | else | ||
733 | home_dir = 0; | ||
689 | } | 734 | } |
690 | 735 | ||
691 | // /tmp mountpoint | 736 | // /tmp mountpoint |
692 | if (tmp_dir) { | 737 | if (tmp_dir) { |
693 | // keep a copy of real /tmp directory in | 738 | // keep a copy of real /tmp directory in RUN_WHITELIST_TMP_DIR |
694 | mkdir_attr(RUN_WHITELIST_TMP_DIR, 1777, 0, 0); | 739 | mkdir_attr(RUN_WHITELIST_TMP_DIR, 1777, 0, 0); |
695 | if (mount("/tmp", RUN_WHITELIST_TMP_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) | 740 | if (mount("/tmp", RUN_WHITELIST_TMP_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) |
696 | errExit("mount bind"); | 741 | errExit("mount bind"); |
@@ -706,7 +751,6 @@ void fs_whitelist(void) { | |||
706 | // /media mountpoint | 751 | // /media mountpoint |
707 | if (media_dir) { | 752 | if (media_dir) { |
708 | // some distros don't have a /media directory | 753 | // some distros don't have a /media directory |
709 | struct stat s; | ||
710 | if (stat("/media", &s) == 0) { | 754 | if (stat("/media", &s) == 0) { |
711 | // keep a copy of real /media directory in RUN_WHITELIST_MEDIA_DIR | 755 | // keep a copy of real /media directory in RUN_WHITELIST_MEDIA_DIR |
712 | mkdir_attr(RUN_WHITELIST_MEDIA_DIR, 0755, 0, 0); | 756 | mkdir_attr(RUN_WHITELIST_MEDIA_DIR, 0755, 0, 0); |
@@ -727,7 +771,6 @@ void fs_whitelist(void) { | |||
727 | // /mnt mountpoint | 771 | // /mnt mountpoint |
728 | if (mnt_dir) { | 772 | if (mnt_dir) { |
729 | // check if /mnt directory exists | 773 | // check if /mnt directory exists |
730 | struct stat s; | ||
731 | if (stat("/mnt", &s) == 0) { | 774 | if (stat("/mnt", &s) == 0) { |
732 | // keep a copy of real /mnt directory in RUN_WHITELIST_MNT_DIR | 775 | // keep a copy of real /mnt directory in RUN_WHITELIST_MNT_DIR |
733 | mkdir_attr(RUN_WHITELIST_MNT_DIR, 0755, 0, 0); | 776 | mkdir_attr(RUN_WHITELIST_MNT_DIR, 0755, 0, 0); |
@@ -778,23 +821,27 @@ void fs_whitelist(void) { | |||
778 | 821 | ||
779 | // /opt mountpoint | 822 | // /opt mountpoint |
780 | if (opt_dir) { | 823 | if (opt_dir) { |
781 | // keep a copy of real /opt directory in RUN_WHITELIST_OPT_DIR | 824 | // check if /opt directory exists |
782 | mkdir_attr(RUN_WHITELIST_OPT_DIR, 0755, 0, 0); | 825 | if (stat("/opt", &s) == 0) { |
783 | if (mount("/opt", RUN_WHITELIST_OPT_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) | 826 | // keep a copy of real /opt directory in RUN_WHITELIST_OPT_DIR |
784 | errExit("mount bind"); | 827 | mkdir_attr(RUN_WHITELIST_OPT_DIR, 0755, 0, 0); |
828 | if (mount("/opt", RUN_WHITELIST_OPT_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
829 | errExit("mount bind"); | ||
785 | 830 | ||
786 | // mount tmpfs on /opt | 831 | // mount tmpfs on /opt |
787 | if (arg_debug || arg_debug_whitelists) | 832 | if (arg_debug || arg_debug_whitelists) |
788 | printf("Mounting tmpfs on /opt directory\n"); | 833 | printf("Mounting tmpfs on /opt directory\n"); |
789 | if (mount("tmpfs", "/opt", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | 834 | if (mount("tmpfs", "/opt", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) |
790 | errExit("mounting tmpfs on /opt"); | 835 | errExit("mounting tmpfs on /opt"); |
791 | fs_logger("tmpfs /opt"); | 836 | fs_logger("tmpfs /opt"); |
837 | } | ||
838 | else | ||
839 | opt_dir = 0; | ||
792 | } | 840 | } |
793 | 841 | ||
794 | // /srv mountpoint | 842 | // /srv mountpoint |
795 | if (srv_dir) { | 843 | if (srv_dir) { |
796 | // check if /srv directory exists | 844 | // check if /srv directory exists |
797 | struct stat s; | ||
798 | if (stat("/srv", &s) == 0) { | 845 | if (stat("/srv", &s) == 0) { |
799 | // keep a copy of real /srv directory in RUN_WHITELIST_SRV_DIR | 846 | // keep a copy of real /srv directory in RUN_WHITELIST_SRV_DIR |
800 | mkdir_attr(RUN_WHITELIST_SRV_DIR, 0755, 0, 0); | 847 | mkdir_attr(RUN_WHITELIST_SRV_DIR, 0755, 0, 0); |
@@ -815,7 +862,6 @@ void fs_whitelist(void) { | |||
815 | // /etc mountpoint | 862 | // /etc mountpoint |
816 | if (etc_dir) { | 863 | if (etc_dir) { |
817 | // check if /etc directory exists | 864 | // check if /etc directory exists |
818 | struct stat s; | ||
819 | if (stat("/etc", &s) == 0) { | 865 | if (stat("/etc", &s) == 0) { |
820 | // keep a copy of real /etc directory in RUN_WHITELIST_ETC_DIR | 866 | // keep a copy of real /etc directory in RUN_WHITELIST_ETC_DIR |
821 | mkdir_attr(RUN_WHITELIST_ETC_DIR, 0755, 0, 0); | 867 | mkdir_attr(RUN_WHITELIST_ETC_DIR, 0755, 0, 0); |
@@ -836,7 +882,6 @@ void fs_whitelist(void) { | |||
836 | // /usr/share mountpoint | 882 | // /usr/share mountpoint |
837 | if (share_dir) { | 883 | if (share_dir) { |
838 | // check if /usr/share directory exists | 884 | // check if /usr/share directory exists |
839 | struct stat s; | ||
840 | if (stat("/usr/share", &s) == 0) { | 885 | if (stat("/usr/share", &s) == 0) { |
841 | // keep a copy of real /usr/share directory in RUN_WHITELIST_ETC_DIR | 886 | // keep a copy of real /usr/share directory in RUN_WHITELIST_ETC_DIR |
842 | mkdir_attr(RUN_WHITELIST_SHARE_DIR, 0755, 0, 0); | 887 | mkdir_attr(RUN_WHITELIST_SHARE_DIR, 0755, 0, 0); |
@@ -857,7 +902,6 @@ void fs_whitelist(void) { | |||
857 | // /sys/module mountpoint | 902 | // /sys/module mountpoint |
858 | if (module_dir) { | 903 | if (module_dir) { |
859 | // check if /sys/module directory exists | 904 | // check if /sys/module directory exists |
860 | struct stat s; | ||
861 | if (stat("/sys/module", &s) == 0) { | 905 | if (stat("/sys/module", &s) == 0) { |
862 | // keep a copy of real /sys/module directory in RUN_WHITELIST_MODULE_DIR | 906 | // keep a copy of real /sys/module directory in RUN_WHITELIST_MODULE_DIR |
863 | mkdir_attr(RUN_WHITELIST_MODULE_DIR, 0755, 0, 0); | 907 | mkdir_attr(RUN_WHITELIST_MODULE_DIR, 0755, 0, 0); |
@@ -892,10 +936,9 @@ void fs_whitelist(void) { | |||
892 | // create the link if any | 936 | // create the link if any |
893 | if (entry->link) { | 937 | if (entry->link) { |
894 | // if the link is already there, do not bother | 938 | // if the link is already there, do not bother |
895 | struct stat s; | 939 | if (lstat(entry->link, &s) != 0) { |
896 | if (stat(entry->link, &s) != 0) { | ||
897 | // create the path if necessary | 940 | // create the path if necessary |
898 | mkpath(entry->link, s.st_mode); | 941 | mkpath(entry->link, 0755); |
899 | 942 | ||
900 | int rv = symlink(entry->data + 10, entry->link); | 943 | int rv = symlink(entry->data + 10, entry->link); |
901 | if (rv) | 944 | if (rv) |
diff --git a/src/firejail/util.c b/src/firejail/util.c index eb59e36be..1d36980bb 100644 --- a/src/firejail/util.c +++ b/src/firejail/util.c | |||
@@ -1116,20 +1116,26 @@ errexit: | |||
1116 | // user controlled paths. Passed flags are ignored if path is a top level directory. | 1116 | // user controlled paths. Passed flags are ignored if path is a top level directory. |
1117 | int safe_fd(const char *path, int flags) { | 1117 | int safe_fd(const char *path, int flags) { |
1118 | assert(path); | 1118 | assert(path); |
1119 | int fd; | 1119 | int fd = -1; |
1120 | 1120 | ||
1121 | // work with a copy of path | 1121 | // work with a copy of path |
1122 | char *dup = strdup(path); | 1122 | char *dup = strdup(path); |
1123 | if (dup == NULL) | 1123 | if (dup == NULL) |
1124 | errExit("strdup"); | 1124 | errExit("strdup"); |
1125 | if (*dup != '/') | 1125 | // reject relative path and empty string |
1126 | errExit("relative path"); // or empty string | 1126 | if (*dup != '/') { |
1127 | fprintf(stderr, "Error: invalid pathname: %s\n", path); | ||
1128 | exit(1); | ||
1129 | } | ||
1127 | 1130 | ||
1128 | char *p = strrchr(dup, '/'); | 1131 | char *p = strrchr(dup, '/'); |
1129 | if (p == NULL) | 1132 | if (p == NULL) |
1130 | errExit("strrchr"); | 1133 | errExit("strrchr"); |
1131 | if (*(p + 1) == '\0') | 1134 | // reject trailing slash and root dir |
1132 | errExit("trailing slash"); // or root dir | 1135 | if (*(p + 1) == '\0') { |
1136 | fprintf(stderr, "Error: invalid pathname: %s\n", path); | ||
1137 | exit(1); | ||
1138 | } | ||
1133 | 1139 | ||
1134 | int parentfd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); | 1140 | int parentfd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); |
1135 | if (parentfd == -1) | 1141 | if (parentfd == -1) |