aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/firejail/fs_home.c2
-rw-r--r--src/firejail/fs_whitelist.c197
-rw-r--r--src/firejail/util.c53
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
52static int mkpath(const char* path, mode_t mode) { 52static 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
96static void whitelist_path(ProfileEntry *entry) { 125static 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
1108errexit: 1169errexit:
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.
1327int safe_fd(const char *path, int flags) { 1327int 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
1393errexit:
1394 fprintf(stderr, "Error: cannot open \"%s\", invalid filename\n", path);
1395 exit(1);
1379} 1396}