diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/firejail/fs_whitelist.c | 74 |
1 files changed, 43 insertions, 31 deletions
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c index c7dbe6496..77bb5e5bb 100644 --- a/src/firejail/fs_whitelist.c +++ b/src/firejail/fs_whitelist.c | |||
@@ -104,21 +104,19 @@ static int whitelist_mkpath(const char* path, mode_t mode) { | |||
104 | return fd; | 104 | return fd; |
105 | } | 105 | } |
106 | 106 | ||
107 | static void whitelist_file(int dirfd, const char *topdir, const char *relpath, const char *path) { | 107 | static void whitelist_file(int dirfd, const char *relpath, const char *path) { |
108 | assert(topdir && relpath && path); | 108 | assert(relpath && path); |
109 | |||
110 | if (arg_debug || arg_debug_whitelists) | ||
111 | printf("Debug %d: dirfd: %d; topdir: %s; relpath: %s; path: %s\n", __LINE__, dirfd, topdir, relpath, path); | ||
112 | 109 | ||
113 | // open mount source, using a file descriptor that refers to the | 110 | // open mount source, using a file descriptor that refers to the |
114 | // top level directory | 111 | // top level directory |
115 | // as the top level directory was opened before mounting the tmpfs | 112 | // as the top level directory was opened before mounting the tmpfs |
116 | // we still have full access to all directory contents | 113 | // we still have full access to all directory contents |
117 | // take care to not follow symbolic links | 114 | // take care to not follow symbolic links (dirfd was obtained without |
115 | // following a link, too) | ||
118 | int fd = safer_openat(dirfd, relpath, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 116 | int fd = safer_openat(dirfd, relpath, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
119 | if (fd == -1) { | 117 | if (fd == -1) { |
120 | if (arg_debug || arg_debug_whitelists) | 118 | if (arg_debug || arg_debug_whitelists) |
121 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | 119 | printf("Debug %d: skip whitelist %s\n", __LINE__, path); |
122 | return; | 120 | return; |
123 | } | 121 | } |
124 | struct stat s; | 122 | struct stat s; |
@@ -126,14 +124,15 @@ static void whitelist_file(int dirfd, const char *topdir, const char *relpath, c | |||
126 | errExit("fstat"); | 124 | errExit("fstat"); |
127 | if (S_ISLNK(s.st_mode)) { | 125 | if (S_ISLNK(s.st_mode)) { |
128 | if (arg_debug || arg_debug_whitelists) | 126 | if (arg_debug || arg_debug_whitelists) |
129 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | 127 | printf("Debug %d: skip whitelist %s\n", __LINE__, path); |
130 | close(fd); | 128 | close(fd); |
131 | return; | 129 | return; |
132 | } | 130 | } |
133 | 131 | ||
134 | // create mount target as root, except if inside home or run/user/$UID directory | 132 | // create mount target as root, except if inside home or run/user/$UID directory |
135 | int userprivs = 0; | 133 | int userprivs = 0; |
136 | if (strcmp(topdir, cfg.homedir) == 0 || strcmp(topdir, runuser) == 0) { | 134 | if ((strncmp(path, cfg.homedir, homedir_len) == 0 && path[homedir_len] == '/') || |
135 | (strncmp(path, runuser, runuser_len) == 0 && path[runuser_len] == '/')) { | ||
137 | EUID_USER(); | 136 | EUID_USER(); |
138 | userprivs = 1; | 137 | userprivs = 1; |
139 | } | 138 | } |
@@ -145,7 +144,7 @@ static void whitelist_file(int dirfd, const char *topdir, const char *relpath, c | |||
145 | // if there is a symlink somewhere in the path of the mount target, | 144 | // if there is a symlink somewhere in the path of the mount target, |
146 | // assume the file is whitelisted already | 145 | // assume the file is whitelisted already |
147 | if (arg_debug || arg_debug_whitelists) | 146 | if (arg_debug || arg_debug_whitelists) |
148 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | 147 | printf("Debug %d: skip whitelist %s\n", __LINE__, path); |
149 | close(fd); | 148 | close(fd); |
150 | if (userprivs) | 149 | if (userprivs) |
151 | EUID_ROOT(); | 150 | EUID_ROOT(); |
@@ -163,7 +162,7 @@ static void whitelist_file(int dirfd, const char *topdir, const char *relpath, c | |||
163 | if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) { | 162 | if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) { |
164 | if (arg_debug || arg_debug_whitelists) { | 163 | if (arg_debug || arg_debug_whitelists) { |
165 | perror("mkdir"); | 164 | perror("mkdir"); |
166 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | 165 | printf("Debug %d: skip whitelist %s\n", __LINE__, path); |
167 | } | 166 | } |
168 | close(fd); | 167 | close(fd); |
169 | close(fd2); | 168 | close(fd2); |
@@ -181,7 +180,7 @@ static void whitelist_file(int dirfd, const char *topdir, const char *relpath, c | |||
181 | if (fd3 == -1) { | 180 | if (fd3 == -1) { |
182 | if (errno != EEXIST && (arg_debug || arg_debug_whitelists)) { | 181 | if (errno != EEXIST && (arg_debug || arg_debug_whitelists)) { |
183 | perror("open"); | 182 | perror("open"); |
184 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | 183 | printf("Debug %d: skip whitelist %s\n", __LINE__, path); |
185 | } | 184 | } |
186 | close(fd); | 185 | close(fd); |
187 | close(fd2); | 186 | close(fd2); |
@@ -229,15 +228,13 @@ static void whitelist_file(int dirfd, const char *topdir, const char *relpath, c | |||
229 | fs_logger2("whitelist", path); | 228 | fs_logger2("whitelist", path); |
230 | } | 229 | } |
231 | 230 | ||
232 | static void whitelist_symlink(const char *topdir, const char *link, const char *target) { | 231 | static void whitelist_symlink(const char *link, const char *target) { |
233 | assert(topdir && link && target); | 232 | assert(link && target); |
234 | |||
235 | if (arg_debug || arg_debug_whitelists) | ||
236 | printf("Debug %d: topdir: %s; link: %s; target: %s\n", __LINE__, topdir, link, target); | ||
237 | 233 | ||
238 | // create files as root, except if inside home or run/user/$UID directory | 234 | // create files as root, except if inside home or run/user/$UID directory |
239 | int userprivs = 0; | 235 | int userprivs = 0; |
240 | if (strcmp(topdir, cfg.homedir) == 0 || strcmp(topdir, runuser) == 0) { | 236 | if ((strncmp(link, cfg.homedir, homedir_len) == 0 && link[homedir_len] == '/') || |
237 | (strncmp(link, runuser, runuser_len) == 0 && link[runuser_len] == '/')) { | ||
241 | EUID_USER(); | 238 | EUID_USER(); |
242 | userprivs = 1; | 239 | userprivs = 1; |
243 | } | 240 | } |
@@ -307,13 +304,17 @@ static void globbing(const char *pattern) { | |||
307 | } | 304 | } |
308 | 305 | ||
309 | // mount tmpfs on all top level directories | 306 | // mount tmpfs on all top level directories |
307 | // home directories *inside* /run/user/$UID are not fully supported | ||
310 | static void tmpfs_topdirs(const TopDir *topdirs) { | 308 | static void tmpfs_topdirs(const TopDir *topdirs) { |
311 | int tmpfs_home = 0; | 309 | int tmpfs_home = 0; |
312 | int tmpfs_runuser = 0; | 310 | int tmpfs_runuser = 0; |
313 | 311 | ||
314 | int i; | 312 | int i; |
315 | for (i = 0; i < TOP_MAX && topdirs[i].path; i++) { | 313 | for (i = 0; i < TOP_MAX && topdirs[i].path; i++) { |
316 | // do user home and /run/user/$UID last | 314 | // do nested top level directories last |
315 | // this way '--whitelist=nested_top_level_dir' | ||
316 | // yields the full, unmodified directory | ||
317 | // instead of the tmpfs | ||
317 | if (strcmp(topdirs[i].path, cfg.homedir) == 0) { | 318 | if (strcmp(topdirs[i].path, cfg.homedir) == 0) { |
318 | tmpfs_home = 1; | 319 | tmpfs_home = 1; |
319 | continue; | 320 | continue; |
@@ -352,7 +353,7 @@ static void tmpfs_topdirs(const TopDir *topdirs) { | |||
352 | // restore /run/user/$UID directory | 353 | // restore /run/user/$UID directory |
353 | // get path relative to /run | 354 | // get path relative to /run |
354 | const char *rel = runuser + 5; | 355 | const char *rel = runuser + 5; |
355 | whitelist_file(topdirs[i].fd, topdirs[i].path, rel, runuser); | 356 | whitelist_file(topdirs[i].fd, rel, runuser); |
356 | } | 357 | } |
357 | else if (strcmp(topdirs[i].path, "/tmp") == 0) { | 358 | else if (strcmp(topdirs[i].path, "/tmp") == 0) { |
358 | // fix pam-tmpdir (#2685) | 359 | // fix pam-tmpdir (#2685) |
@@ -376,11 +377,12 @@ static void tmpfs_topdirs(const TopDir *topdirs) { | |||
376 | 377 | ||
377 | // restore user home directory if it is masked by the tmpfs | 378 | // restore user home directory if it is masked by the tmpfs |
378 | // creates path owned by root | 379 | // creates path owned by root |
380 | // does nothing if user home directory doesn't exist | ||
379 | size_t topdir_len = strlen(topdirs[i].path); | 381 | size_t topdir_len = strlen(topdirs[i].path); |
380 | if (strncmp(topdirs[i].path, cfg.homedir, topdir_len) == 0 && cfg.homedir[topdir_len] == '/') { | 382 | if (strncmp(topdirs[i].path, cfg.homedir, topdir_len) == 0 && cfg.homedir[topdir_len] == '/') { |
381 | // get path relative to top level directory | 383 | // get path relative to top level directory |
382 | const char *rel = cfg.homedir + topdir_len + 1; | 384 | const char *rel = cfg.homedir + topdir_len + 1; |
383 | whitelist_file(topdirs[i].fd, topdirs[i].path, rel, cfg.homedir); | 385 | whitelist_file(topdirs[i].fd, rel, cfg.homedir); |
384 | } | 386 | } |
385 | 387 | ||
386 | selinux_relabel_path(topdirs[i].path, topdirs[i].path); | 388 | selinux_relabel_path(topdirs[i].path, topdirs[i].path); |
@@ -388,7 +390,8 @@ static void tmpfs_topdirs(const TopDir *topdirs) { | |||
388 | 390 | ||
389 | // user home directory | 391 | // user home directory |
390 | if (tmpfs_home) | 392 | if (tmpfs_home) |
391 | fs_private(); // checks owner if outside /home | 393 | // checks owner if outside /home |
394 | fs_private(); | ||
392 | 395 | ||
393 | // /run/user/$UID directory | 396 | // /run/user/$UID directory |
394 | if (tmpfs_runuser) { | 397 | if (tmpfs_runuser) { |
@@ -437,8 +440,7 @@ static TopDir *add_topdir(const char *dir, TopDir *topdirs, const char *path) { | |||
437 | } | 440 | } |
438 | // do nothing if directory is disabled by administrator | 441 | // do nothing if directory is disabled by administrator |
439 | if (reject_topdir(dir)) { | 442 | if (reject_topdir(dir)) { |
440 | fwarning("skipping whitelist %s because\n" | 443 | fmessage("Whitelist top level directory %s is disabled in Firejail configuration file\n", dir); |
441 | "whitelist top level directory is disabled in Firejail configuration file\n", path); | ||
442 | return NULL; | 444 | return NULL; |
443 | } | 445 | } |
444 | 446 | ||
@@ -453,15 +455,14 @@ static TopDir *add_topdir(const char *dir, TopDir *topdirs, const char *path) { | |||
453 | TopDir *rv = topdirs + cnt; | 455 | TopDir *rv = topdirs + cnt; |
454 | cnt++; | 456 | cnt++; |
455 | 457 | ||
456 | char *dup = strdup(dir); | 458 | rv->path = strdup(dir); |
457 | if (!dup) | 459 | if (!rv->path) |
458 | errExit("strdup"); | 460 | errExit("strdup"); |
459 | rv->path = dup; | ||
460 | 461 | ||
461 | // open the directory, don't follow symbolic links | 462 | // open the directory, don't follow symbolic links |
462 | rv->fd = safer_openat(-1, dup, O_PATH|O_NOFOLLOW|O_DIRECTORY|O_CLOEXEC); | 463 | rv->fd = safer_openat(-1, rv->path, O_PATH|O_NOFOLLOW|O_DIRECTORY|O_CLOEXEC); |
463 | if (rv->fd == -1) { | 464 | if (rv->fd == -1) { |
464 | fprintf(stderr, "Error: cannot open %s\n", dup); | 465 | fprintf(stderr, "Error: cannot open %s\n", rv->path); |
465 | exit(1); | 466 | exit(1); |
466 | } | 467 | } |
467 | 468 | ||
@@ -592,6 +593,10 @@ void fs_whitelist(void) { | |||
592 | if (strstr(new_name, "..")) | 593 | if (strstr(new_name, "..")) |
593 | whitelist_error(new_name); | 594 | whitelist_error(new_name); |
594 | 595 | ||
596 | // /run/firejail is not allowed | ||
597 | if (strncmp(new_name, RUN_FIREJAIL_DIR, strlen(RUN_FIREJAIL_DIR)) == 0) | ||
598 | whitelist_error(new_name); | ||
599 | |||
595 | TopDir *current_top = NULL; | 600 | TopDir *current_top = NULL; |
596 | if (!nowhitelist_flag) { | 601 | if (!nowhitelist_flag) { |
597 | // extract whitelist top level directory | 602 | // extract whitelist top level directory |
@@ -649,6 +654,10 @@ void fs_whitelist(void) { | |||
649 | continue; | 654 | continue; |
650 | } | 655 | } |
651 | 656 | ||
657 | // /run/firejail is not allowed | ||
658 | if (strncmp(fname, RUN_FIREJAIL_DIR, strlen(RUN_FIREJAIL_DIR)) == 0) | ||
659 | whitelist_error(fname); | ||
660 | |||
652 | if (nowhitelist_flag) { | 661 | if (nowhitelist_flag) { |
653 | // store the path in nowhitelist array | 662 | // store the path in nowhitelist array |
654 | if (arg_debug || arg_debug_whitelists) | 663 | if (arg_debug || arg_debug_whitelists) |
@@ -727,12 +736,15 @@ void fs_whitelist(void) { | |||
727 | if (strncmp(file, topdir, topdir_len) == 0 && file[topdir_len] == '/') { | 736 | if (strncmp(file, topdir, topdir_len) == 0 && file[topdir_len] == '/') { |
728 | // get path relative to top level directory | 737 | // get path relative to top level directory |
729 | const char *rel = file + topdir_len + 1; | 738 | const char *rel = file + topdir_len + 1; |
730 | whitelist_file(dirfd, topdir, rel, file); | 739 | |
740 | if (arg_debug || arg_debug_whitelists) | ||
741 | printf("Debug %d: file: %s; dirfd: %d; topdir: %s; rel: %s\n", __LINE__, file, dirfd, topdir, rel); | ||
742 | whitelist_file(dirfd, rel, file); | ||
731 | } | 743 | } |
732 | 744 | ||
733 | // create the link if any | 745 | // create the link if any |
734 | if (link) | 746 | if (link) |
735 | whitelist_symlink(topdir, link, file); | 747 | whitelist_symlink(link, file); |
736 | 748 | ||
737 | free(link); | 749 | free(link); |
738 | free(file); | 750 | free(file); |