diff options
-rw-r--r-- | src/firejail/firejail.h | 1 | ||||
-rw-r--r-- | src/firejail/fs_whitelist.c | 107 | ||||
-rw-r--r-- | src/firejail/macros.c | 2 | ||||
-rw-r--r-- | status | 23 |
4 files changed, 76 insertions, 57 deletions
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 0dbe1f896..4562dd8c2 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h | |||
@@ -456,6 +456,7 @@ char *expand_home(const char *path, const char *homedir); | |||
456 | char *resolve_macro(const char *name); | 456 | char *resolve_macro(const char *name); |
457 | void invalid_filename(const char *fname, int globbing); | 457 | void invalid_filename(const char *fname, int globbing); |
458 | int is_macro(const char *name); | 458 | int is_macro(const char *name); |
459 | int macro_id(const char *name); | ||
459 | 460 | ||
460 | // util.c | 461 | // util.c |
461 | void errLogExit(char* fmt, ...); | 462 | void errLogExit(char* fmt, ...); |
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c index 1fd1fb675..6dd4a7e2d 100644 --- a/src/firejail/fs_whitelist.c +++ b/src/firejail/fs_whitelist.c | |||
@@ -35,27 +35,16 @@ | |||
35 | #define EMPTY_STRING ("") | 35 | #define EMPTY_STRING ("") |
36 | #define MAXBUF 4098 | 36 | #define MAXBUF 4098 |
37 | 37 | ||
38 | // returns mallocated memory | ||
39 | char *parse_nowhitelist(int nowhitelist_flag, char *ptr1) { | ||
40 | char *rv; | ||
41 | if (nowhitelist_flag) { | ||
42 | if (asprintf(&rv, "nowhitelist ~/%s", ptr1) == -1) | ||
43 | errExit("asprintf"); | ||
44 | } | ||
45 | else { | ||
46 | if (asprintf(&rv, "whitelist ~/%s", ptr1) == -1) | ||
47 | errExit("asprintf"); | ||
48 | } | ||
49 | return rv; | ||
50 | } | ||
51 | 38 | ||
52 | static int mkpath(const char* path, mode_t mode) { | 39 | static int mkpath(const char* path, mode_t mode) { |
53 | assert(path && *path); | 40 | assert(path && *path); |
54 | mode |= 0111; | 41 | mode |= 0111; |
55 | 42 | ||
56 | // create directories with uid/gid as root or as current user if inside home directory | 43 | // create directories with uid/gid as root or as current user if inside home directory |
44 | int userhome = 0; | ||
57 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) == 0) { | 45 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) == 0) { |
58 | EUID_USER(); | 46 | EUID_USER(); |
47 | userhome = 1; | ||
59 | } | 48 | } |
60 | 49 | ||
61 | // work on a copy of the path | 50 | // work on a copy of the path |
@@ -90,7 +79,9 @@ static int mkpath(const char* path, mode_t mode) { | |||
90 | perror("mkdir"); | 79 | perror("mkdir"); |
91 | close(parentfd); | 80 | close(parentfd); |
92 | free(dup); | 81 | free(dup); |
93 | EUID_ROOT(); | 82 | if (userhome) { |
83 | EUID_ROOT(); | ||
84 | } | ||
94 | return -1; | 85 | return -1; |
95 | } | 86 | } |
96 | } | 87 | } |
@@ -103,7 +94,9 @@ static int mkpath(const char* path, mode_t mode) { | |||
103 | perror("open"); | 94 | perror("open"); |
104 | close(parentfd); | 95 | close(parentfd); |
105 | free(dup); | 96 | free(dup); |
106 | EUID_ROOT(); | 97 | if (userhome) { |
98 | EUID_ROOT(); | ||
99 | } | ||
107 | return -1; | 100 | return -1; |
108 | } | 101 | } |
109 | // move on to next path segment | 102 | // move on to next path segment |
@@ -116,7 +109,9 @@ static int mkpath(const char* path, mode_t mode) { | |||
116 | fs_logger2("mkpath", path); | 109 | fs_logger2("mkpath", path); |
117 | 110 | ||
118 | free(dup); | 111 | free(dup); |
119 | EUID_ROOT(); | 112 | if (userhome) { |
113 | EUID_ROOT(); | ||
114 | } | ||
120 | return fd; | 115 | return fd; |
121 | } | 116 | } |
122 | 117 | ||
@@ -128,11 +123,12 @@ static void whitelist_path(ProfileEntry *entry) { | |||
128 | char *wfile = NULL; | 123 | char *wfile = NULL; |
129 | 124 | ||
130 | if (entry->home_dir) { | 125 | if (entry->home_dir) { |
131 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) != 0) | 126 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) != 0 || path[strlen(cfg.homedir)] != '/') |
132 | // symlink pointing outside /home, skip the mount | 127 | // either symlink pointing outside home directory |
128 | // or entire home directory, skip the mount | ||
133 | return; | 129 | return; |
134 | 130 | ||
135 | fname = path + strlen(cfg.homedir); | 131 | fname = path + strlen(cfg.homedir) + 1; // strlen("/home/user/") |
136 | 132 | ||
137 | if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_HOME_USER_DIR, fname) == -1) | 133 | if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_HOME_USER_DIR, fname) == -1) |
138 | errExit("asprintf"); | 134 | errExit("asprintf"); |
@@ -304,13 +300,15 @@ static void whitelist_path(ProfileEntry *entry) { | |||
304 | // check the last mount operation | 300 | // check the last mount operation |
305 | MountData *mptr = get_last_mount(); // will do exit(1) if the mount cannot be found | 301 | MountData *mptr = get_last_mount(); // will do exit(1) if the mount cannot be found |
306 | 302 | ||
303 | // confirm the file was mounted on the right target | ||
304 | // strcmp does not work here, because mptr->dir can be a child mount | ||
307 | if (strncmp(mptr->dir, path, strlen(path)) != 0) | 305 | if (strncmp(mptr->dir, path, strlen(path)) != 0) |
308 | errLogExit("invalid whitelist mount"); | 306 | errLogExit("invalid whitelist mount"); |
309 | // No mounts are allowed on top level directories. A destination such as "/etc" is very bad! | 307 | // No mounts are allowed on top level directories. A destination such as "/etc" is very bad! |
310 | // - there should be more than one '/' char in dest string | 308 | // - there should be more than one '/' char in dest string |
311 | if (mptr->dir == strrchr(mptr->dir, '/')) | 309 | if (mptr->dir == strrchr(mptr->dir, '/')) |
312 | errLogExit("invalid whitelist mount"); | 310 | errLogExit("invalid whitelist mount"); |
313 | // confirm the correct file is mounted on path | 311 | // confirm the right file was mounted |
314 | int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 312 | int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
315 | if (fd4 == -1) | 313 | if (fd4 == -1) |
316 | errExit("safe_fd"); | 314 | errExit("safe_fd"); |
@@ -369,37 +367,31 @@ void fs_whitelist(void) { | |||
369 | } | 367 | } |
370 | char *dataptr = (nowhitelist_flag)? entry->data + 12: entry->data + 10; | 368 | char *dataptr = (nowhitelist_flag)? entry->data + 12: entry->data + 10; |
371 | 369 | ||
372 | // resolve macros | 370 | // replace ~/ or ${HOME} into /home/username or resolve macro |
373 | if (is_macro(dataptr)) { | 371 | new_name = expand_home(dataptr, cfg.homedir); |
374 | char *tmp = resolve_macro(dataptr); // returns allocated mem | 372 | assert(new_name); |
375 | if (tmp != NULL) { | 373 | |
376 | char *tmp1 = parse_nowhitelist(nowhitelist_flag, tmp); | 374 | // skip command if resolving the macro was not successful |
377 | assert(tmp1); | 375 | if (is_macro(new_name) && macro_id(new_name) > -1) { |
378 | free(tmp); | 376 | // mount empty home directory and print a warning |
379 | tmp = tmp1; | 377 | if (!nowhitelist_flag && !arg_private) { |
380 | } | 378 | home_dir = 1; |
381 | if (tmp) { | 379 | if (!arg_quiet) { |
382 | entry->data = tmp; | ||
383 | dataptr = (nowhitelist_flag)? entry->data + 12: entry->data + 10; | ||
384 | } | ||
385 | else { | ||
386 | if (!nowhitelist_flag && !arg_quiet && !arg_private) { | ||
387 | fprintf(stderr, "***\n"); | 380 | fprintf(stderr, "***\n"); |
388 | fprintf(stderr, "*** Warning: cannot whitelist %s directory\n", dataptr); | 381 | fprintf(stderr, "*** Warning: cannot whitelist %s directory\n", new_name); |
389 | fprintf(stderr, "*** Any file saved in this directory will be lost when the sandbox is closed.\n"); | 382 | fprintf(stderr, "*** Any file saved in this directory will be lost when the sandbox is closed.\n"); |
390 | fprintf(stderr, "***\n"); | 383 | fprintf(stderr, "***\n"); |
391 | } | 384 | } |
392 | entry->data = EMPTY_STRING; | ||
393 | continue; | ||
394 | } | 385 | } |
386 | entry->data = EMPTY_STRING; | ||
387 | entry = entry->next; | ||
388 | free(new_name); | ||
389 | continue; | ||
395 | } | 390 | } |
396 | 391 | ||
397 | // replace ~/ or ${HOME} into /home/username | ||
398 | new_name = expand_home(dataptr, cfg.homedir); | ||
399 | assert(new_name); | ||
400 | |||
401 | // remove trailing slashes and single dots | 392 | // remove trailing slashes and single dots |
402 | trim_trailing_slash_or_dot(new_name); | 393 | if (!nowhitelist_flag) |
394 | trim_trailing_slash_or_dot(new_name); | ||
403 | 395 | ||
404 | if (arg_debug || arg_debug_whitelists) | 396 | if (arg_debug || arg_debug_whitelists) |
405 | fprintf(stderr, "Debug %d: new_name #%s#, %s\n", __LINE__, new_name, (nowhitelist_flag)? "nowhitelist": "whitelist"); | 397 | fprintf(stderr, "Debug %d: new_name #%s#, %s\n", __LINE__, new_name, (nowhitelist_flag)? "nowhitelist": "whitelist"); |
@@ -412,7 +404,7 @@ void fs_whitelist(void) { | |||
412 | } | 404 | } |
413 | 405 | ||
414 | // extract the absolute path of the file | 406 | // extract the absolute path of the file |
415 | // realpath function will fail with ENOENT if the file is not found | 407 | // realpath function will fail with ENOENT if the file is not found or with EACCES if user has no permission |
416 | // special processing for /dev/fd, /dev/stdin, /dev/stdout and /dev/stderr | 408 | // special processing for /dev/fd, /dev/stdin, /dev/stdout and /dev/stderr |
417 | char *fname; | 409 | char *fname; |
418 | if (strcmp(new_name, "/dev/fd") == 0) | 410 | if (strcmp(new_name, "/dev/fd") == 0) |
@@ -438,7 +430,7 @@ void fs_whitelist(void) { | |||
438 | 430 | ||
439 | // if 1 the file was not found; mount an empty directory | 431 | // if 1 the file was not found; mount an empty directory |
440 | if (!nowhitelist_flag) { | 432 | if (!nowhitelist_flag) { |
441 | if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) { | 433 | if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0 && new_name[strlen(cfg.homedir)] == '/') { |
442 | if(!arg_private) | 434 | if(!arg_private) |
443 | home_dir = 1; | 435 | home_dir = 1; |
444 | } | 436 | } |
@@ -465,6 +457,8 @@ void fs_whitelist(void) { | |||
465 | } | 457 | } |
466 | 458 | ||
467 | entry->data = EMPTY_STRING; | 459 | entry->data = EMPTY_STRING; |
460 | entry = entry->next; | ||
461 | free(new_name); | ||
468 | continue; | 462 | continue; |
469 | } | 463 | } |
470 | else if (arg_debug_whitelists) | 464 | else if (arg_debug_whitelists) |
@@ -483,18 +477,22 @@ void fs_whitelist(void) { | |||
483 | } | 477 | } |
484 | nowhitelist[nowhitelist_c++] = fname; | 478 | nowhitelist[nowhitelist_c++] = fname; |
485 | entry->data = EMPTY_STRING; | 479 | entry->data = EMPTY_STRING; |
480 | entry = entry->next; | ||
481 | free(new_name); | ||
486 | continue; | 482 | continue; |
487 | } | 483 | } |
488 | 484 | ||
489 | // check for supported directories | 485 | // check for supported directories |
490 | if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) { | 486 | if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0 && new_name[strlen(cfg.homedir)] == '/') { |
491 | // whitelisting home directory is disabled if --private option is present | 487 | // whitelisting home directory is disabled if --private option is present |
492 | if (arg_private) { | 488 | if (arg_private) { |
493 | if (arg_debug || arg_debug_whitelists) | 489 | if (arg_debug || arg_debug_whitelists) |
494 | printf("\"%s\" disabled by --private\n", entry->data); | 490 | printf("\"%s\" disabled by --private\n", entry->data); |
495 | 491 | ||
496 | entry->data = EMPTY_STRING; | 492 | entry->data = EMPTY_STRING; |
493 | entry = entry->next; | ||
497 | free(fname); | 494 | free(fname); |
495 | free(new_name); | ||
498 | continue; | 496 | continue; |
499 | } | 497 | } |
500 | 498 | ||
@@ -504,17 +502,10 @@ void fs_whitelist(void) { | |||
504 | fprintf(stderr, "Debug %d: fname #%s#, cfg.homedir #%s#\n", | 502 | fprintf(stderr, "Debug %d: fname #%s#, cfg.homedir #%s#\n", |
505 | __LINE__, fname, cfg.homedir); | 503 | __LINE__, fname, cfg.homedir); |
506 | 504 | ||
507 | // both path and absolute path are under /home | 505 | // both path and absolute path are in user home, |
508 | if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0) { | 506 | // if not check if the symlink destination is owned by the user |
509 | // entire home directory is not allowed | 507 | if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) != 0 || fname[strlen(cfg.homedir)] != '/') { |
510 | if (*(fname + strlen(cfg.homedir)) != '/') { | ||
511 | free(fname); | ||
512 | goto errexit; | ||
513 | } | ||
514 | } | ||
515 | else { | ||
516 | if (checkcfg(CFG_FOLLOW_SYMLINK_AS_USER)) { | 508 | if (checkcfg(CFG_FOLLOW_SYMLINK_AS_USER)) { |
517 | // check if the file is owned by the user | ||
518 | if (stat(fname, &s) == 0 && s.st_uid != getuid()) { | 509 | if (stat(fname, &s) == 0 && s.st_uid != getuid()) { |
519 | free(fname); | 510 | free(fname); |
520 | goto errexit; | 511 | goto errexit; |
@@ -659,7 +650,9 @@ void fs_whitelist(void) { | |||
659 | if (arg_debug || arg_debug_whitelists) | 650 | if (arg_debug || arg_debug_whitelists) |
660 | printf("Skip nowhitelisted path %s\n", fname); | 651 | printf("Skip nowhitelisted path %s\n", fname); |
661 | entry->data = EMPTY_STRING; | 652 | entry->data = EMPTY_STRING; |
653 | entry = entry->next; | ||
662 | free(fname); | 654 | free(fname); |
655 | free(new_name); | ||
663 | continue; | 656 | continue; |
664 | } | 657 | } |
665 | } | 658 | } |
@@ -916,6 +909,7 @@ void fs_whitelist(void) { | |||
916 | if (arg_debug || arg_debug_whitelists) | 909 | if (arg_debug || arg_debug_whitelists) |
917 | printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link); | 910 | printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link); |
918 | free(entry->link); | 911 | free(entry->link); |
912 | entry->link = NULL; | ||
919 | entry = entry->next; | 913 | entry = entry->next; |
920 | continue; | 914 | continue; |
921 | } | 915 | } |
@@ -934,6 +928,7 @@ void fs_whitelist(void) { | |||
934 | close(fd); | 928 | close(fd); |
935 | } | 929 | } |
936 | free(entry->link); | 930 | free(entry->link); |
931 | entry->link = NULL; | ||
937 | } | 932 | } |
938 | 933 | ||
939 | entry = entry->next; | 934 | entry = entry->next; |
diff --git a/src/firejail/macros.c b/src/firejail/macros.c index 27893938f..4bf3d3589 100644 --- a/src/firejail/macros.c +++ b/src/firejail/macros.c | |||
@@ -69,7 +69,7 @@ Macro macro[] = { | |||
69 | }; | 69 | }; |
70 | 70 | ||
71 | // return -1 if not found | 71 | // return -1 if not found |
72 | static int macro_id(const char *name) { | 72 | int macro_id(const char *name) { |
73 | int i = 0; | 73 | int i = 0; |
74 | while (macro[i].name != NULL) { | 74 | while (macro[i].name != NULL) { |
75 | if (strcmp(name, macro[i].name) == 0) | 75 | if (strcmp(name, macro[i].name) == 0) |
@@ -1,3 +1,26 @@ | |||
1 | done: cleanup | ||
2 | done: regression: fix whitelisting of symlinks to other home dirs, small improvements | ||
3 | done: tiny memleaks | ||
4 | done: incomplete fix: whitelisting of symlinks to other home dirs | ||
5 | done: mount empty home if macro can't be whitelisted | ||
6 | |||
7 | todo: tests: skip audit.exp if tests are already running in a pid namespace (renerh) | ||
8 | |||
9 | todo: AppArmor: Allow writing to removable media | ||
10 | |||
11 | todo: configure.ac: set sysconfdir only if none was specified manually | ||
12 | |||
13 | todo: Firejail should look for processes with names exactly named "firejail | ||
14 | |||
15 | todo: Update appimage size calculation to newest code from libappimage. | ||
16 | |||
17 | todo: clean /run/user directory (smitsohu) | ||
18 | - firejail.h | ||
19 | -restrict_users.c | ||
20 | |||
21 | |||
22 | |||
23 | |||
1 | Oct 9, 0.9.56.1, mainline merge | 24 | Oct 9, 0.9.56.1, mainline merge |
2 | 25 | ||
3 | Sep 26 mainline merge | 26 | Sep 26 mainline merge |