diff options
-rw-r--r-- | src/firejail/fs_home.c | 2 | ||||
-rw-r--r-- | src/firejail/fs_whitelist.c | 197 | ||||
-rw-r--r-- | src/firejail/util.c | 53 |
3 files changed, 166 insertions, 86 deletions
diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c index 3afa3bf0c..09931bd56 100644 --- a/src/firejail/fs_home.c +++ b/src/firejail/fs_home.c | |||
@@ -290,6 +290,8 @@ void fs_private(void) { | |||
290 | if (u == 0 && arg_allusers) // allow --allusers when starting the sandbox as root | 290 | if (u == 0 && arg_allusers) // allow --allusers when starting the sandbox as root |
291 | ; | 291 | ; |
292 | else { | 292 | else { |
293 | if (arg_allusers) | ||
294 | fwarning("--allusers disabled by --private or --whitelist\n"); | ||
293 | if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | 295 | if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) |
294 | errExit("mounting home directory"); | 296 | errExit("mounting home directory"); |
295 | fs_logger("tmpfs /home"); | 297 | fs_logger("tmpfs /home"); |
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c index 3cc116c78..8d7d45c13 100644 --- a/src/firejail/fs_whitelist.c +++ b/src/firejail/fs_whitelist.c | |||
@@ -51,46 +51,75 @@ char *parse_nowhitelist(int nowhitelist_flag, char *ptr1) { | |||
51 | 51 | ||
52 | static int mkpath(const char* path, mode_t mode) { | 52 | static int mkpath(const char* path, mode_t mode) { |
53 | assert(path && *path); | 53 | assert(path && *path); |
54 | |||
55 | mode |= 0111; | 54 | mode |= 0111; |
56 | 55 | ||
57 | // create directories with uid/gid as root or as current user if inside home directory | 56 | // create directories with uid/gid as root or as current user if inside home directory |
58 | uid_t uid = getuid(); | 57 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) == 0) { |
59 | gid_t gid = getgid(); | 58 | EUID_USER(); |
60 | if (strncmp(path, cfg.homedir, strlen(cfg.homedir)) != 0) { | ||
61 | uid = 0; | ||
62 | gid = 0; | ||
63 | } | 59 | } |
64 | 60 | ||
65 | // work on a copy of the path | 61 | // work on a copy of the path |
66 | char *file_path = strdup(path); | 62 | char *dup = strdup(path); |
67 | if (!file_path) | 63 | if (!dup) |
68 | errExit("strdup"); | 64 | errExit("strdup"); |
69 | 65 | ||
70 | char* p; | 66 | // don't create the last path element |
67 | char *p = strrchr(dup, '/'); | ||
68 | if (!p) | ||
69 | errExit("strrchr"); | ||
70 | *p = '\0'; | ||
71 | |||
72 | int parentfd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
73 | if (parentfd == -1) | ||
74 | errExit("open"); | ||
75 | |||
76 | // traverse the path, return -1 if a symlink is encountered | ||
71 | int done = 0; | 77 | int done = 0; |
72 | for (p=strchr(file_path+1, '/'); p; p=strchr(p+1, '/')) { | 78 | int fd = -1; |
73 | *p='\0'; | 79 | char *tok = strtok(dup, "/"); |
74 | if (mkdir(file_path, mode)==-1) { | 80 | if (!tok) |
81 | errExit("strtok"); | ||
82 | while (tok) { | ||
83 | // skip all instances of "/./" | ||
84 | if (strcmp(tok, ".") == 0) { | ||
85 | tok = strtok(NULL, "/"); | ||
86 | continue; | ||
87 | } | ||
88 | // create the directory if necessary | ||
89 | if (mkdirat(parentfd, tok, mode) == -1) { | ||
75 | if (errno != EEXIST) { | 90 | if (errno != EEXIST) { |
76 | *p='/'; | 91 | if (arg_debug || arg_debug_whitelists) |
77 | free(file_path); | 92 | perror("mkdir"); |
93 | close(parentfd); | ||
94 | free(dup); | ||
95 | EUID_ROOT(); | ||
78 | return -1; | 96 | return -1; |
79 | } | 97 | } |
80 | } | 98 | } |
81 | else { | 99 | else |
82 | if (set_perms(file_path, uid, gid, mode)) | ||
83 | errExit("set_perms"); | ||
84 | done = 1; | 100 | done = 1; |
101 | // open the directory | ||
102 | fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
103 | if (fd == -1) { | ||
104 | if (arg_debug || arg_debug_whitelists) | ||
105 | perror("open"); | ||
106 | close(parentfd); | ||
107 | free(dup); | ||
108 | EUID_ROOT(); | ||
109 | return -1; | ||
85 | } | 110 | } |
86 | 111 | // move on to next path segment | |
87 | *p='/'; | 112 | close(parentfd); |
113 | parentfd = fd; | ||
114 | tok = strtok(NULL, "/"); | ||
88 | } | 115 | } |
116 | |||
89 | if (done) | 117 | if (done) |
90 | fs_logger2("mkpath", path); | 118 | fs_logger2("mkpath", path); |
91 | 119 | ||
92 | free(file_path); | 120 | free(dup); |
93 | return 0; | 121 | EUID_ROOT(); |
122 | return fd; | ||
94 | } | 123 | } |
95 | 124 | ||
96 | static void whitelist_path(ProfileEntry *entry) { | 125 | static void whitelist_path(ProfileEntry *entry) { |
@@ -184,13 +213,18 @@ static void whitelist_path(ProfileEntry *entry) { | |||
184 | } | 213 | } |
185 | assert(wfile); | 214 | assert(wfile); |
186 | 215 | ||
187 | // check if the file exists, confirm again there is no symlink | 216 | if (arg_debug || arg_debug_whitelists) |
217 | printf("Whitelisting %s\n", path); | ||
218 | |||
219 | // confirm again the mount source exists and there is no symlink | ||
188 | struct stat wfilestat; | 220 | struct stat wfilestat; |
189 | #ifndef TEST_MOUNTINFO | 221 | #ifndef TEST_MOUNTINFO |
190 | EUID_USER(); | 222 | EUID_USER(); |
191 | int fd = safe_fd(wfile, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 223 | int fd = safe_fd(wfile, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
192 | EUID_ROOT(); | 224 | EUID_ROOT(); |
193 | if (fd == -1) { | 225 | if (fd == -1) { |
226 | if (arg_debug || arg_debug_whitelists) | ||
227 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | ||
194 | free(wfile); | 228 | free(wfile); |
195 | return; | 229 | return; |
196 | } | 230 | } |
@@ -198,57 +232,64 @@ static void whitelist_path(ProfileEntry *entry) { | |||
198 | errExit("fstat"); | 232 | errExit("fstat"); |
199 | close(fd); | 233 | close(fd); |
200 | if (S_ISLNK(wfilestat.st_mode)) { | 234 | if (S_ISLNK(wfilestat.st_mode)) { |
235 | if (arg_debug || arg_debug_whitelists) | ||
236 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | ||
201 | free(wfile); | 237 | free(wfile); |
202 | return; | 238 | return; |
203 | } | 239 | } |
204 | #endif | 240 | #endif |
205 | 241 | ||
206 | if (arg_debug || arg_debug_whitelists) | 242 | // create path of the mount target if necessary |
207 | printf("Whitelisting %s\n", path); | 243 | int fd2 = mkpath(path, 0755); |
244 | if (fd2 == -1) { | ||
245 | // something went wrong during path creation or a symlink was found; | ||
246 | // if there is a symlink somewhere in the path of the mount target, | ||
247 | // assume the file is whitelisted already | ||
248 | if (arg_debug || arg_debug_whitelists) | ||
249 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | ||
250 | free(wfile); | ||
251 | return; | ||
252 | } | ||
208 | 253 | ||
209 | // create the path if necessary | 254 | // get file name of the mount target |
210 | struct stat s; | 255 | const char *file = gnu_basename(path); |
211 | if (stat(path, &s) == -1) { | 256 | |
212 | mkpath(path, 0755); | 257 | // create the mount target if necessary and open it, a symlink is rejected |
213 | if (S_ISDIR(wfilestat.st_mode)) { | 258 | int fd3 = -1; |
214 | int rv = mkdir(path, 0755); | 259 | if (S_ISDIR(wfilestat.st_mode)) { |
215 | if (rv) { | 260 | // directory foo can exist already: |
216 | fprintf(stderr, "Error: cannot create directory %s\n", path); | 261 | // firejail --whitelist=/foo/bar --whitelist=/foo |
217 | exit(1); | 262 | if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) { |
218 | } | 263 | if (arg_debug || arg_debug_whitelists) { |
219 | } | 264 | perror("mkdir"); |
220 | else { | 265 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); |
221 | int fd2 = open(path, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR); | ||
222 | if (fd2 == -1) { | ||
223 | fprintf(stderr, "Error: cannot create empty file %s\n", path); | ||
224 | exit(1); | ||
225 | } | 266 | } |
226 | close(fd2); | 267 | close(fd2); |
268 | free(wfile); | ||
269 | return; | ||
227 | } | 270 | } |
271 | fd3 = openat(fd2, file, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
228 | } | 272 | } |
229 | else { | 273 | else { |
230 | if (!S_ISDIR(s.st_mode)) { | 274 | // create an empty file, fails with EEXIST if it is whitelisted already: |
231 | free(wfile); | 275 | // firejail --whitelist=/foo --whitelist=/foo/bar |
232 | return; // the file is already present | 276 | fd3 = openat(fd2, file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR); |
233 | } | ||
234 | } | 277 | } |
235 | 278 | ||
236 | fs_logger2("whitelist", path); | ||
237 | |||
238 | // get a file descriptor for path; if path contains anything other than directories | ||
239 | // or a regular file, assume it is whitelisted already | ||
240 | int fd3 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | ||
241 | if (fd3 == -1) { | 279 | if (fd3 == -1) { |
280 | if (arg_debug || arg_debug_whitelists) { | ||
281 | if (errno != EEXIST) { | ||
282 | perror("open"); | ||
283 | printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); | ||
284 | } | ||
285 | } | ||
286 | close(fd2); | ||
242 | free(wfile); | 287 | free(wfile); |
243 | return; | 288 | return; |
244 | } | 289 | } |
245 | if (fstat(fd3, &s) == -1) | 290 | close(fd2); |
246 | errExit("fstat"); | 291 | |
247 | if (!(S_ISDIR(s.st_mode) || S_ISREG(s.st_mode))) { | 292 | fs_logger2("whitelist", path); |
248 | free(wfile); | ||
249 | close(fd3); | ||
250 | return; | ||
251 | } | ||
252 | 293 | ||
253 | // mount via the link in /proc/self/fd | 294 | // mount via the link in /proc/self/fd |
254 | char *proc; | 295 | char *proc; |
@@ -272,6 +313,7 @@ static void whitelist_path(ProfileEntry *entry) { | |||
272 | int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 313 | int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
273 | if (fd4 == -1) | 314 | if (fd4 == -1) |
274 | errExit("safe_fd"); | 315 | errExit("safe_fd"); |
316 | struct stat s; | ||
275 | if (fstat(fd4, &s) == -1) | 317 | if (fstat(fd4, &s) == -1) |
276 | errExit("fstat"); | 318 | errExit("fstat"); |
277 | if (s.st_dev != wfilestat.st_dev || s.st_ino != wfilestat.st_ino) | 319 | if (s.st_dev != wfilestat.st_dev || s.st_ino != wfilestat.st_ino) |
@@ -525,6 +567,14 @@ void fs_whitelist(void) { | |||
525 | goto errexit; | 567 | goto errexit; |
526 | } | 568 | } |
527 | 569 | ||
570 | // no trailing slash and no trailing dot | ||
571 | char *p = strrchr(new_name, '/'); | ||
572 | if (!p) | ||
573 | errExit("strrchr"); | ||
574 | if (*(p + 1) == '\0') | ||
575 | goto errexit; | ||
576 | if (*(p + 1) == '.' && *(p + 2) == '\0') | ||
577 | goto errexit; | ||
528 | 578 | ||
529 | // extract the absolute path of the file | 579 | // extract the absolute path of the file |
530 | // realpath function will fail with ENOENT if the file is not found | 580 | // realpath function will fail with ENOENT if the file is not found |
@@ -601,7 +651,6 @@ void fs_whitelist(void) { | |||
601 | continue; | 651 | continue; |
602 | } | 652 | } |
603 | 653 | ||
604 | |||
605 | // check for supported directories | 654 | // check for supported directories |
606 | if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) { | 655 | if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) { |
607 | // whitelisting home directory is disabled if --private option is present | 656 | // whitelisting home directory is disabled if --private option is present |
@@ -621,7 +670,7 @@ void fs_whitelist(void) { | |||
621 | 670 | ||
622 | // both path and absolute path are under /home | 671 | // both path and absolute path are under /home |
623 | if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0) { | 672 | if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0) { |
624 | // avoid naming issues, also entire home dirs are not allowed | 673 | // entire home directory is not allowed |
625 | if (*(fname + strlen(cfg.homedir)) != '/') | 674 | if (*(fname + strlen(cfg.homedir)) != '/') |
626 | goto errexit; | 675 | goto errexit; |
627 | } | 676 | } |
@@ -768,7 +817,7 @@ void fs_whitelist(void) { | |||
768 | entry->link = new_name; | 817 | entry->link = new_name; |
769 | else { | 818 | else { |
770 | free(new_name); | 819 | free(new_name); |
771 | new_name = NULL; | 820 | entry->link = NULL; |
772 | } | 821 | } |
773 | 822 | ||
774 | // change file name in entry->data | 823 | // change file name in entry->data |
@@ -798,7 +847,7 @@ void fs_whitelist(void) { | |||
798 | if (mount(cfg.homedir, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) | 847 | if (mount(cfg.homedir, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) |
799 | errExit("mount bind"); | 848 | errExit("mount bind"); |
800 | 849 | ||
801 | // mount a tmpfs and initialize /home/user | 850 | // mount a tmpfs and initialize /home/user, overrides --allusers |
802 | fs_private(); | 851 | fs_private(); |
803 | } | 852 | } |
804 | else | 853 | else |
@@ -1010,14 +1059,29 @@ void fs_whitelist(void) { | |||
1010 | // if the link is already there, do not bother | 1059 | // if the link is already there, do not bother |
1011 | if (lstat(entry->link, &s) != 0) { | 1060 | if (lstat(entry->link, &s) != 0) { |
1012 | // create the path if necessary | 1061 | // create the path if necessary |
1013 | mkpath(entry->link, 0755); | 1062 | int fd = mkpath(entry->link, 0755); |
1014 | 1063 | if (fd == -1) { | |
1015 | int rv = symlink(entry->data + 10, entry->link); | 1064 | if (arg_debug || arg_debug_whitelists) |
1016 | if (rv) | 1065 | printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link); |
1017 | fprintf(stderr, "Warning cannot create symbolic link %s\n", entry->link); | 1066 | free(entry->link); |
1067 | entry = entry->next; | ||
1068 | continue; | ||
1069 | } | ||
1070 | // get file name of symlink | ||
1071 | const char *file = gnu_basename(entry->link); | ||
1072 | // create the link | ||
1073 | int rv = symlinkat(entry->data + 10, fd, file); | ||
1074 | if (rv) { | ||
1075 | if (arg_debug || arg_debug_whitelists) { | ||
1076 | perror("symlink"); | ||
1077 | printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link); | ||
1078 | } | ||
1079 | } | ||
1018 | else if (arg_debug || arg_debug_whitelists) | 1080 | else if (arg_debug || arg_debug_whitelists) |
1019 | printf("Created symbolic link %s -> %s\n", entry->link, entry->data + 10); | 1081 | printf("Created symbolic link %s -> %s\n", entry->link, entry->data + 10); |
1082 | close(fd); | ||
1020 | } | 1083 | } |
1084 | free(entry->link); | ||
1021 | } | 1085 | } |
1022 | 1086 | ||
1023 | entry = entry->next; | 1087 | entry = entry->next; |
@@ -1100,9 +1164,6 @@ void fs_whitelist(void) { | |||
1100 | fs_logger2("tmpfs", RUN_WHITELIST_MODULE_DIR); | 1164 | fs_logger2("tmpfs", RUN_WHITELIST_MODULE_DIR); |
1101 | } | 1165 | } |
1102 | 1166 | ||
1103 | if (new_name) | ||
1104 | free(new_name); | ||
1105 | |||
1106 | return; | 1167 | return; |
1107 | 1168 | ||
1108 | errexit: | 1169 | errexit: |
diff --git a/src/firejail/util.c b/src/firejail/util.c index 2aa4c26a7..d79e955a7 100644 --- a/src/firejail/util.c +++ b/src/firejail/util.c | |||
@@ -1326,54 +1326,71 @@ void disable_file_path(const char *path, const char *file) { | |||
1326 | // user controlled paths. Passed flags are ignored if path is a top level directory. | 1326 | // user controlled paths. Passed flags are ignored if path is a top level directory. |
1327 | int safe_fd(const char *path, int flags) { | 1327 | int safe_fd(const char *path, int flags) { |
1328 | assert(path); | 1328 | assert(path); |
1329 | int fd = -1; | 1329 | |
1330 | // reject empty string, relative path | ||
1331 | if (*path != '/') | ||
1332 | goto errexit; | ||
1333 | // reject ".." | ||
1334 | if (strstr(path, "..")) | ||
1335 | goto errexit; | ||
1330 | 1336 | ||
1331 | // work with a copy of path | 1337 | // work with a copy of path |
1332 | char *dup = strdup(path); | 1338 | char *dup = strdup(path); |
1333 | if (dup == NULL) | 1339 | if (dup == NULL) |
1334 | errExit("strdup"); | 1340 | errExit("strdup"); |
1335 | // reject relative path and empty string | ||
1336 | if (*dup != '/') { | ||
1337 | fprintf(stderr, "Error: invalid pathname: %s\n", path); | ||
1338 | exit(1); | ||
1339 | } | ||
1340 | 1341 | ||
1341 | char *p = strrchr(dup, '/'); | 1342 | char *p = strrchr(dup, '/'); |
1342 | if (p == NULL) | 1343 | if (p == NULL) |
1343 | errExit("strrchr"); | 1344 | errExit("strrchr"); |
1344 | // reject trailing slash and root dir | 1345 | // reject trailing slash, root directory |
1345 | if (*(p + 1) == '\0') { | 1346 | if (*(p + 1) == '\0') |
1346 | fprintf(stderr, "Error: invalid pathname: %s\n", path); | 1347 | goto errexit; |
1347 | exit(1); | 1348 | // reject trailing dot |
1348 | } | 1349 | if (*(p + 1) == '.' && *(p + 2) == '\0') |
1350 | goto errexit; | ||
1351 | // if there is more than one path segment, keep the last one for later | ||
1352 | if (p != dup) | ||
1353 | *p = '\0'; | ||
1349 | 1354 | ||
1350 | int parentfd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); | 1355 | int parentfd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); |
1351 | if (parentfd == -1) | 1356 | if (parentfd == -1) |
1352 | errExit("open"); | 1357 | errExit("open"); |
1353 | 1358 | ||
1354 | // if there is more than one path segment, keep the last one for later | 1359 | // traverse the path and return -1 if a symlink is encountered |
1355 | if (p != dup) | 1360 | int entered = 0; |
1356 | *p = '\0'; | 1361 | int fd = -1; |
1357 | |||
1358 | // traverse the path, return -1 if a symlink is encountered | ||
1359 | char *tok = strtok(dup, "/"); | 1362 | char *tok = strtok(dup, "/"); |
1360 | if (tok == NULL) | ||
1361 | errExit("strtok"); | ||
1362 | while (tok) { | 1363 | while (tok) { |
1364 | // skip all "/./" | ||
1365 | if (strcmp(tok, ".") == 0) { | ||
1366 | tok = strtok(NULL, "/"); | ||
1367 | continue; | ||
1368 | } | ||
1369 | entered = 1; | ||
1370 | |||
1371 | // open the directory | ||
1363 | fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | 1372 | fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); |
1364 | close(parentfd); | 1373 | close(parentfd); |
1365 | if (fd == -1) { | 1374 | if (fd == -1) { |
1366 | free(dup); | 1375 | free(dup); |
1367 | return -1; | 1376 | return -1; |
1368 | } | 1377 | } |
1378 | |||
1369 | parentfd = fd; | 1379 | parentfd = fd; |
1370 | tok = strtok(NULL, "/"); | 1380 | tok = strtok(NULL, "/"); |
1371 | } | 1381 | } |
1372 | if (p != dup) { | 1382 | if (p != dup) { |
1383 | // consistent flags for top level directories (////foo, /.///foo) | ||
1384 | if (!entered) | ||
1385 | flags = O_PATH|O_DIRECTORY|O_CLOEXEC; | ||
1373 | // open last path segment | 1386 | // open last path segment |
1374 | fd = openat(parentfd, p + 1, flags|O_NOFOLLOW); | 1387 | fd = openat(parentfd, p + 1, flags|O_NOFOLLOW); |
1375 | close(parentfd); | 1388 | close(parentfd); |
1376 | } | 1389 | } |
1377 | free(dup); | 1390 | free(dup); |
1378 | return fd; // -1 if open failed | 1391 | return fd; // -1 if open failed |
1392 | |||
1393 | errexit: | ||
1394 | fprintf(stderr, "Error: cannot open \"%s\", invalid filename\n", path); | ||
1395 | exit(1); | ||
1379 | } | 1396 | } |