diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/firejail/firejail.h | 3 | ||||
-rw-r--r-- | src/firejail/fs.c | 53 | ||||
-rw-r--r-- | src/firejail/main.c | 6 | ||||
-rw-r--r-- | src/firejail/profile.c | 4 | ||||
-rw-r--r-- | src/firejail/sandbox.c | 9 | ||||
-rw-r--r-- | src/firejail/util.c | 50 |
6 files changed, 124 insertions, 1 deletions
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index f988dc114..18d66b983 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h | |||
@@ -308,6 +308,7 @@ static inline int any_interface_configured(void) { | |||
308 | 308 | ||
309 | extern int arg_private; // mount private /home | 309 | extern int arg_private; // mount private /home |
310 | extern int arg_private_template; // private /home template | 310 | extern int arg_private_template; // private /home template |
311 | extern int arg_private_cache; // private home/.cache | ||
311 | extern int arg_debug; // print debug messages | 312 | extern int arg_debug; // print debug messages |
312 | extern int arg_debug_blacklists; // print debug messages for blacklists | 313 | extern int arg_debug_blacklists; // print debug messages for blacklists |
313 | extern int arg_debug_whitelists; // print debug messages for whitelists | 314 | extern int arg_debug_whitelists; // print debug messages for whitelists |
@@ -447,6 +448,7 @@ void fs_overlayfs(void); | |||
447 | void fs_chroot(const char *rootdir); | 448 | void fs_chroot(const char *rootdir); |
448 | void fs_check_chroot_dir(const char *rootdir); | 449 | void fs_check_chroot_dir(const char *rootdir); |
449 | void fs_private_tmp(void); | 450 | void fs_private_tmp(void); |
451 | void fs_private_cache(void); | ||
450 | 452 | ||
451 | // profile.c | 453 | // profile.c |
452 | // find and read the profile specified by name from dir directory | 454 | // find and read the profile specified by name from dir directory |
@@ -525,6 +527,7 @@ void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid); | |||
525 | unsigned extract_timeout(const char *str); | 527 | unsigned extract_timeout(const char *str); |
526 | void disable_file_or_dir(const char *fname); | 528 | void disable_file_or_dir(const char *fname); |
527 | void disable_file_path(const char *path, const char *file); | 529 | void disable_file_path(const char *path, const char *file); |
530 | int safe_fd(const char *path, int flags); | ||
528 | 531 | ||
529 | // Get info regarding the last kernel mount operation from /proc/self/mountinfo | 532 | // Get info regarding the last kernel mount operation from /proc/self/mountinfo |
530 | // The return value points to a static area, and will be overwritten by subsequent calls. | 533 | // The return value points to a static area, and will be overwritten by subsequent calls. |
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index 0562c7424..8db45f967 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -1340,3 +1340,56 @@ void fs_private_tmp(void) { | |||
1340 | 1340 | ||
1341 | 1341 | ||
1342 | } | 1342 | } |
1343 | |||
1344 | // this function is called from sandbox.c before blacklist/whitelist functions | ||
1345 | void fs_private_cache(void) { | ||
1346 | char *cache; | ||
1347 | if (asprintf(&cache, "%s/.cache", cfg.homedir) == -1) | ||
1348 | errExit("asprintf"); | ||
1349 | // check if ~/.cache is a valid destination | ||
1350 | struct stat s; | ||
1351 | if (is_link(cache)) { | ||
1352 | fwarning("~/.cache is a symbolic link, tmpfs not mounted\n"); | ||
1353 | return; | ||
1354 | } | ||
1355 | if (stat(cache, &s) == -1 || !S_ISDIR(s.st_mode)) { | ||
1356 | fwarning("no ~/.cache directory found, tmpfs not mounted\n"); | ||
1357 | return; | ||
1358 | } | ||
1359 | if (s.st_uid != getuid()) { | ||
1360 | fwarning("~/.cache is not owned by user, tmpfs not mounted\n"); | ||
1361 | return; | ||
1362 | } | ||
1363 | |||
1364 | // get a file descriptor for ~/.cache, fails if there is any symlink | ||
1365 | int fd = safe_fd(cache, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
1366 | if (fd == -1) | ||
1367 | errExit("safe_fd"); | ||
1368 | // confirm that actual mount destination is owned by the user | ||
1369 | if (fstat(fd, &s) == -1 || s.st_uid != getuid()) | ||
1370 | errExit("fstat"); | ||
1371 | |||
1372 | // mount a tmpfs on ~/.cache via the symbolic link in /proc/self/fd | ||
1373 | char *proc; | ||
1374 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
1375 | errExit("asprintf"); | ||
1376 | if (mount("tmpfs", proc, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, 0) < 0) | ||
1377 | errExit("mounting tmpfs"); | ||
1378 | fs_logger2("tmpfs", cache); | ||
1379 | free(proc); | ||
1380 | close(fd); | ||
1381 | // check the last mount operation | ||
1382 | MountData *mdata = get_last_mount(); | ||
1383 | assert(mdata); | ||
1384 | if (strcmp(mdata->fstype, "tmpfs") != 0 || strcmp(mdata->dir, cache) != 0) | ||
1385 | errLogExit("invalid .cache mount"); | ||
1386 | |||
1387 | // get a new file descriptor for ~/.cache, the old directory is masked by the tmpfs | ||
1388 | fd = safe_fd(cache, O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
1389 | if (fd == -1) | ||
1390 | errExit("safe_fd"); | ||
1391 | free(cache); | ||
1392 | // restore permissions | ||
1393 | SET_PERMS_FD(fd, s.st_uid, s.st_gid, s.st_mode); | ||
1394 | close(fd); | ||
1395 | } | ||
diff --git a/src/firejail/main.c b/src/firejail/main.c index 9d28f3352..7d4c33460 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c | |||
@@ -45,7 +45,8 @@ gid_t firejail_gid = 0; | |||
45 | static char child_stack[STACK_SIZE]; // space for child's stack | 45 | static char child_stack[STACK_SIZE]; // space for child's stack |
46 | Config cfg; // configuration | 46 | Config cfg; // configuration |
47 | int arg_private = 0; // mount private /home and /tmp directoryu | 47 | int arg_private = 0; // mount private /home and /tmp directoryu |
48 | int arg_private_template = 0; // mount private /home using a template | 48 | int arg_private_template = 0; // mount private /home using a template |
49 | int arg_private_cache = 0; // mount private home/.cache | ||
49 | int arg_debug = 0; // print debug messages | 50 | int arg_debug = 0; // print debug messages |
50 | int arg_debug_blacklists = 0; // print debug messages for blacklists | 51 | int arg_debug_blacklists = 0; // print debug messages for blacklists |
51 | int arg_debug_whitelists = 0; // print debug messages for whitelists | 52 | int arg_debug_whitelists = 0; // print debug messages for whitelists |
@@ -1676,6 +1677,9 @@ int main(int argc, char **argv) { | |||
1676 | else if (strcmp(argv[i], "--private-tmp") == 0) { | 1677 | else if (strcmp(argv[i], "--private-tmp") == 0) { |
1677 | arg_private_tmp = 1; | 1678 | arg_private_tmp = 1; |
1678 | } | 1679 | } |
1680 | else if (strcmp(argv[i], "--private-cache") == 0) { | ||
1681 | arg_private_cache = 1; | ||
1682 | } | ||
1679 | 1683 | ||
1680 | //************************************* | 1684 | //************************************* |
1681 | // hostname, etc | 1685 | // hostname, etc |
diff --git a/src/firejail/profile.c b/src/firejail/profile.c index 7b59cd48c..04519483c 100644 --- a/src/firejail/profile.c +++ b/src/firejail/profile.c | |||
@@ -217,6 +217,10 @@ int profile_check_line(char *ptr, int lineno, const char *fname) { | |||
217 | arg_allusers = 1; | 217 | arg_allusers = 1; |
218 | return 0; | 218 | return 0; |
219 | } | 219 | } |
220 | else if (strcmp(ptr, "private-cache") == 0) { | ||
221 | arg_private_cache = 1; | ||
222 | return 0; | ||
223 | } | ||
220 | else if (strcmp(ptr, "private-dev") == 0) { | 224 | else if (strcmp(ptr, "private-dev") == 0) { |
221 | arg_private_dev = 1; | 225 | arg_private_dev = 1; |
222 | return 0; | 226 | return 0; |
diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c index 1498007eb..fdb0babc8 100644 --- a/src/firejail/sandbox.c +++ b/src/firejail/sandbox.c | |||
@@ -833,6 +833,15 @@ int sandbox(void* sandbox_arg) { | |||
833 | } | 833 | } |
834 | } | 834 | } |
835 | 835 | ||
836 | if (arg_private_cache) { | ||
837 | if (cfg.chrootdir) | ||
838 | fwarning("private-cache feature is disabled in chroot\n"); | ||
839 | else if (arg_overlay) | ||
840 | fwarning("private-cache feature is disabled in overlay\n"); | ||
841 | else | ||
842 | fs_private_cache(); | ||
843 | } | ||
844 | |||
836 | if (arg_private_tmp) { | 845 | if (arg_private_tmp) { |
837 | // private-tmp is implemented as a whitelist | 846 | // private-tmp is implemented as a whitelist |
838 | EUID_USER(); | 847 | EUID_USER(); |
diff --git a/src/firejail/util.c b/src/firejail/util.c index 8b3996927..f5a252514 100644 --- a/src/firejail/util.c +++ b/src/firejail/util.c | |||
@@ -1111,3 +1111,53 @@ errexit: | |||
1111 | fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); | 1111 | fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); |
1112 | exit(1); | 1112 | exit(1); |
1113 | } | 1113 | } |
1114 | |||
1115 | // The returned file descriptor should be suitable for privileged operations on | ||
1116 | // user controlled paths. Passed flags are ignored if path is a top level directory. | ||
1117 | int safe_fd(const char *path, int flags) { | ||
1118 | assert(path); | ||
1119 | int fd; | ||
1120 | |||
1121 | // work with a copy of path | ||
1122 | char *dup = strdup(path); | ||
1123 | if (dup == NULL) | ||
1124 | errExit("strdup"); | ||
1125 | if (*dup != '/') | ||
1126 | errExit("relative path"); // or empty string | ||
1127 | |||
1128 | char *p = strrchr(dup, '/'); | ||
1129 | if (p == NULL) | ||
1130 | errExit("strrchr"); | ||
1131 | if (*(p + 1) == '\0') | ||
1132 | errExit("trailing slash"); // or root dir | ||
1133 | |||
1134 | int parentfd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
1135 | if (parentfd == -1) | ||
1136 | errExit("open"); | ||
1137 | |||
1138 | // if there is more than one path segment, keep the last one for later | ||
1139 | if (p != dup) | ||
1140 | *p = '\0'; | ||
1141 | |||
1142 | // traverse the path, return -1 if a symlink is encountered | ||
1143 | char *tok = strtok(dup, "/"); | ||
1144 | if (tok == NULL) | ||
1145 | errExit("strtok"); | ||
1146 | while (tok) { | ||
1147 | fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
1148 | close(parentfd); | ||
1149 | if (fd == -1) { | ||
1150 | free(dup); | ||
1151 | return -1; | ||
1152 | } | ||
1153 | parentfd = fd; | ||
1154 | tok = strtok(NULL, "/"); | ||
1155 | } | ||
1156 | if (p != dup) { | ||
1157 | // open last path segment | ||
1158 | fd = openat(parentfd, p + 1, flags|O_NOFOLLOW); | ||
1159 | close(parentfd); | ||
1160 | } | ||
1161 | free(dup); | ||
1162 | return fd; // -1 if open failed | ||
1163 | } | ||