From df5c9f035ac3353ac1bfb9ceda71cbf61e8cac81 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Mon, 4 Jun 2018 14:09:39 +0200 Subject: add private-cache option implementation is based on an idea of James Henstridge, Canonical --- src/firejail/firejail.h | 3 +++ src/firejail/fs.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ src/firejail/main.c | 6 +++++- src/firejail/profile.c | 4 ++++ src/firejail/sandbox.c | 9 +++++++++ src/firejail/util.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 124 insertions(+), 1 deletion(-) 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) { extern int arg_private; // mount private /home extern int arg_private_template; // private /home template +extern int arg_private_cache; // private home/.cache extern int arg_debug; // print debug messages extern int arg_debug_blacklists; // print debug messages for blacklists extern int arg_debug_whitelists; // print debug messages for whitelists @@ -447,6 +448,7 @@ void fs_overlayfs(void); void fs_chroot(const char *rootdir); void fs_check_chroot_dir(const char *rootdir); void fs_private_tmp(void); +void fs_private_cache(void); // profile.c // 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); unsigned extract_timeout(const char *str); void disable_file_or_dir(const char *fname); void disable_file_path(const char *path, const char *file); +int safe_fd(const char *path, int flags); // Get info regarding the last kernel mount operation from /proc/self/mountinfo // 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) { } + +// this function is called from sandbox.c before blacklist/whitelist functions +void fs_private_cache(void) { + char *cache; + if (asprintf(&cache, "%s/.cache", cfg.homedir) == -1) + errExit("asprintf"); + // check if ~/.cache is a valid destination + struct stat s; + if (is_link(cache)) { + fwarning("~/.cache is a symbolic link, tmpfs not mounted\n"); + return; + } + if (stat(cache, &s) == -1 || !S_ISDIR(s.st_mode)) { + fwarning("no ~/.cache directory found, tmpfs not mounted\n"); + return; + } + if (s.st_uid != getuid()) { + fwarning("~/.cache is not owned by user, tmpfs not mounted\n"); + return; + } + + // get a file descriptor for ~/.cache, fails if there is any symlink + int fd = safe_fd(cache, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); + if (fd == -1) + errExit("safe_fd"); + // confirm that actual mount destination is owned by the user + if (fstat(fd, &s) == -1 || s.st_uid != getuid()) + errExit("fstat"); + + // mount a tmpfs on ~/.cache via the symbolic link in /proc/self/fd + char *proc; + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) + errExit("asprintf"); + if (mount("tmpfs", proc, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, 0) < 0) + errExit("mounting tmpfs"); + fs_logger2("tmpfs", cache); + free(proc); + close(fd); + // check the last mount operation + MountData *mdata = get_last_mount(); + assert(mdata); + if (strcmp(mdata->fstype, "tmpfs") != 0 || strcmp(mdata->dir, cache) != 0) + errLogExit("invalid .cache mount"); + + // get a new file descriptor for ~/.cache, the old directory is masked by the tmpfs + fd = safe_fd(cache, O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); + if (fd == -1) + errExit("safe_fd"); + free(cache); + // restore permissions + SET_PERMS_FD(fd, s.st_uid, s.st_gid, s.st_mode); + close(fd); +} 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; static char child_stack[STACK_SIZE]; // space for child's stack Config cfg; // configuration int arg_private = 0; // mount private /home and /tmp directoryu -int arg_private_template = 0; // mount private /home using a template +int arg_private_template = 0; // mount private /home using a template +int arg_private_cache = 0; // mount private home/.cache int arg_debug = 0; // print debug messages int arg_debug_blacklists = 0; // print debug messages for blacklists int arg_debug_whitelists = 0; // print debug messages for whitelists @@ -1676,6 +1677,9 @@ int main(int argc, char **argv) { else if (strcmp(argv[i], "--private-tmp") == 0) { arg_private_tmp = 1; } + else if (strcmp(argv[i], "--private-cache") == 0) { + arg_private_cache = 1; + } //************************************* // 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) { arg_allusers = 1; return 0; } + else if (strcmp(ptr, "private-cache") == 0) { + arg_private_cache = 1; + return 0; + } else if (strcmp(ptr, "private-dev") == 0) { arg_private_dev = 1; 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) { } } + if (arg_private_cache) { + if (cfg.chrootdir) + fwarning("private-cache feature is disabled in chroot\n"); + else if (arg_overlay) + fwarning("private-cache feature is disabled in overlay\n"); + else + fs_private_cache(); + } + if (arg_private_tmp) { // private-tmp is implemented as a whitelist 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: fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); exit(1); } + +// The returned file descriptor should be suitable for privileged operations on +// user controlled paths. Passed flags are ignored if path is a top level directory. +int safe_fd(const char *path, int flags) { + assert(path); + int fd; + + // work with a copy of path + char *dup = strdup(path); + if (dup == NULL) + errExit("strdup"); + if (*dup != '/') + errExit("relative path"); // or empty string + + char *p = strrchr(dup, '/'); + if (p == NULL) + errExit("strrchr"); + if (*(p + 1) == '\0') + errExit("trailing slash"); // or root dir + + int parentfd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); + if (parentfd == -1) + errExit("open"); + + // if there is more than one path segment, keep the last one for later + if (p != dup) + *p = '\0'; + + // traverse the path, return -1 if a symlink is encountered + char *tok = strtok(dup, "/"); + if (tok == NULL) + errExit("strtok"); + while (tok) { + fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); + close(parentfd); + if (fd == -1) { + free(dup); + return -1; + } + parentfd = fd; + tok = strtok(NULL, "/"); + } + if (p != dup) { + // open last path segment + fd = openat(parentfd, p + 1, flags|O_NOFOLLOW); + close(parentfd); + } + free(dup); + return fd; // -1 if open failed +} -- cgit v1.2.3-54-g00ecf