diff options
author | smitsohu <smitsohu@gmail.com> | 2018-10-01 01:04:30 +0200 |
---|---|---|
committer | smitsohu <smitsohu@gmail.com> | 2018-10-01 01:04:30 +0200 |
commit | 51eeef2059f00de117472046601e10a9fd958d51 (patch) | |
tree | 1cafc226fce3784518f691f250e92789c5af4a99 /src | |
parent | cleanup (diff) | |
download | firejail-51eeef2059f00de117472046601e10a9fd958d51.tar.gz firejail-51eeef2059f00de117472046601e10a9fd958d51.tar.zst firejail-51eeef2059f00de117472046601e10a9fd958d51.zip |
regression: fix whitelisting of symlinks to other home dirs, small improvements
handling of home dir paths is more explicit and rigorous now, which should
make it easier to audit. Also this should come handy if one day fs_private()
supports home directories outside /home
rf. #2123
Diffstat (limited to 'src')
-rw-r--r-- | src/firejail/fs_whitelist.c | 47 |
1 files changed, 27 insertions, 20 deletions
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c index 9bedcc708..531cc4e48 100644 --- a/src/firejail/fs_whitelist.c +++ b/src/firejail/fs_whitelist.c | |||
@@ -41,8 +41,10 @@ static int mkpath(const char* path, mode_t mode) { | |||
41 | mode |= 0111; | 41 | mode |= 0111; |
42 | 42 | ||
43 | // 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; | ||
44 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) == 0) { | 45 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) == 0) { |
45 | EUID_USER(); | 46 | EUID_USER(); |
47 | userhome = 1; | ||
46 | } | 48 | } |
47 | 49 | ||
48 | // work on a copy of the path | 50 | // work on a copy of the path |
@@ -77,7 +79,9 @@ static int mkpath(const char* path, mode_t mode) { | |||
77 | perror("mkdir"); | 79 | perror("mkdir"); |
78 | close(parentfd); | 80 | close(parentfd); |
79 | free(dup); | 81 | free(dup); |
80 | EUID_ROOT(); | 82 | if (userhome) { |
83 | EUID_ROOT(); | ||
84 | } | ||
81 | return -1; | 85 | return -1; |
82 | } | 86 | } |
83 | } | 87 | } |
@@ -90,7 +94,9 @@ static int mkpath(const char* path, mode_t mode) { | |||
90 | perror("open"); | 94 | perror("open"); |
91 | close(parentfd); | 95 | close(parentfd); |
92 | free(dup); | 96 | free(dup); |
93 | EUID_ROOT(); | 97 | if (userhome) { |
98 | EUID_ROOT(); | ||
99 | } | ||
94 | return -1; | 100 | return -1; |
95 | } | 101 | } |
96 | // move on to next path segment | 102 | // move on to next path segment |
@@ -103,7 +109,9 @@ static int mkpath(const char* path, mode_t mode) { | |||
103 | fs_logger2("mkpath", path); | 109 | fs_logger2("mkpath", path); |
104 | 110 | ||
105 | free(dup); | 111 | free(dup); |
106 | EUID_ROOT(); | 112 | if (userhome) { |
113 | EUID_ROOT(); | ||
114 | } | ||
107 | return fd; | 115 | return fd; |
108 | } | 116 | } |
109 | 117 | ||
@@ -115,11 +123,12 @@ static void whitelist_path(ProfileEntry *entry) { | |||
115 | char *wfile = NULL; | 123 | char *wfile = NULL; |
116 | 124 | ||
117 | if (entry->home_dir) { | 125 | if (entry->home_dir) { |
118 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) != 0) | 126 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) != 0 || path[strlen(cfg.homedir)] != '/') |
119 | // symlink pointing outside /home, skip the mount | 127 | // either symlink pointing outside home directory |
128 | // or entire home directory, skip the mount | ||
120 | return; | 129 | return; |
121 | 130 | ||
122 | fname = path + strlen(cfg.homedir); | 131 | fname = path + strlen(cfg.homedir) + 1; // strlen("/home/user/") |
123 | 132 | ||
124 | 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) |
125 | errExit("asprintf"); | 134 | errExit("asprintf"); |
@@ -291,13 +300,15 @@ static void whitelist_path(ProfileEntry *entry) { | |||
291 | // check the last mount operation | 300 | // check the last mount operation |
292 | 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 |
293 | 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 | ||
294 | if (strncmp(mptr->dir, path, strlen(path)) != 0) | 305 | if (strncmp(mptr->dir, path, strlen(path)) != 0) |
295 | errLogExit("invalid whitelist mount"); | 306 | errLogExit("invalid whitelist mount"); |
296 | // 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! |
297 | // - there should be more than one '/' char in dest string | 308 | // - there should be more than one '/' char in dest string |
298 | if (mptr->dir == strrchr(mptr->dir, '/')) | 309 | if (mptr->dir == strrchr(mptr->dir, '/')) |
299 | errLogExit("invalid whitelist mount"); | 310 | errLogExit("invalid whitelist mount"); |
300 | // confirm the correct file is mounted on path | 311 | // confirm the right file was mounted |
301 | int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 312 | int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
302 | if (fd4 == -1) | 313 | if (fd4 == -1) |
303 | errExit("safe_fd"); | 314 | errExit("safe_fd"); |
@@ -374,7 +385,8 @@ void fs_whitelist(void) { | |||
374 | } | 385 | } |
375 | 386 | ||
376 | // remove trailing slashes and single dots | 387 | // remove trailing slashes and single dots |
377 | trim_trailing_slash_or_dot(new_name); | 388 | if (!nowhitelist_flag) |
389 | trim_trailing_slash_or_dot(new_name); | ||
378 | 390 | ||
379 | if (arg_debug || arg_debug_whitelists) | 391 | if (arg_debug || arg_debug_whitelists) |
380 | fprintf(stderr, "Debug %d: new_name #%s#, %s\n", __LINE__, new_name, (nowhitelist_flag)? "nowhitelist": "whitelist"); | 392 | fprintf(stderr, "Debug %d: new_name #%s#, %s\n", __LINE__, new_name, (nowhitelist_flag)? "nowhitelist": "whitelist"); |
@@ -387,7 +399,7 @@ void fs_whitelist(void) { | |||
387 | } | 399 | } |
388 | 400 | ||
389 | // extract the absolute path of the file | 401 | // extract the absolute path of the file |
390 | // realpath function will fail with ENOENT if the file is not found | 402 | // realpath function will fail with ENOENT if the file is not found or with EACCES if user has no permission |
391 | // special processing for /dev/fd, /dev/stdin, /dev/stdout and /dev/stderr | 403 | // special processing for /dev/fd, /dev/stdin, /dev/stdout and /dev/stderr |
392 | char *fname; | 404 | char *fname; |
393 | if (strcmp(new_name, "/dev/fd") == 0) | 405 | if (strcmp(new_name, "/dev/fd") == 0) |
@@ -462,7 +474,7 @@ void fs_whitelist(void) { | |||
462 | } | 474 | } |
463 | 475 | ||
464 | // check for supported directories | 476 | // check for supported directories |
465 | if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) { | 477 | if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0 && new_name[strlen(cfg.homedir)] == '/') { |
466 | // whitelisting home directory is disabled if --private option is present | 478 | // whitelisting home directory is disabled if --private option is present |
467 | if (arg_private) { | 479 | if (arg_private) { |
468 | if (arg_debug || arg_debug_whitelists) | 480 | if (arg_debug || arg_debug_whitelists) |
@@ -479,17 +491,10 @@ void fs_whitelist(void) { | |||
479 | fprintf(stderr, "Debug %d: fname #%s#, cfg.homedir #%s#\n", | 491 | fprintf(stderr, "Debug %d: fname #%s#, cfg.homedir #%s#\n", |
480 | __LINE__, fname, cfg.homedir); | 492 | __LINE__, fname, cfg.homedir); |
481 | 493 | ||
482 | // both path and absolute path are under /home | 494 | // both path and absolute path are in user home, |
483 | if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0) { | 495 | // if not check if the symlink destination is owned by the user |
484 | // entire home directory is not allowed | 496 | if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) != 0 || fname[strlen(cfg.homedir)] != '/') { |
485 | if (*(fname + strlen(cfg.homedir)) != '/') { | ||
486 | free(fname); | ||
487 | goto errexit; | ||
488 | } | ||
489 | } | ||
490 | else { | ||
491 | if (checkcfg(CFG_FOLLOW_SYMLINK_AS_USER)) { | 497 | if (checkcfg(CFG_FOLLOW_SYMLINK_AS_USER)) { |
492 | // check if the file is owned by the user | ||
493 | if (stat(fname, &s) == 0 && s.st_uid != getuid()) { | 498 | if (stat(fname, &s) == 0 && s.st_uid != getuid()) { |
494 | free(fname); | 499 | free(fname); |
495 | goto errexit; | 500 | goto errexit; |
@@ -891,6 +896,7 @@ void fs_whitelist(void) { | |||
891 | if (arg_debug || arg_debug_whitelists) | 896 | if (arg_debug || arg_debug_whitelists) |
892 | printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link); | 897 | printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link); |
893 | free(entry->link); | 898 | free(entry->link); |
899 | entry->link = NULL; | ||
894 | entry = entry->next; | 900 | entry = entry->next; |
895 | continue; | 901 | continue; |
896 | } | 902 | } |
@@ -909,6 +915,7 @@ void fs_whitelist(void) { | |||
909 | close(fd); | 915 | close(fd); |
910 | } | 916 | } |
911 | free(entry->link); | 917 | free(entry->link); |
918 | entry->link = NULL; | ||
912 | } | 919 | } |
913 | 920 | ||
914 | entry = entry->next; | 921 | entry = entry->next; |