/* * Copyright (C) 2014-2022 Firejail Authors * * This file is part of firejail project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_OVERLAYFS #include "firejail.h" #include "../include/gcov_wrapper.h" #include #include #include #include #include #ifndef O_PATH #define O_PATH 010000000 #endif char *fs_check_overlay_dir(const char *subdirname, int allow_reuse) { assert(subdirname); EUID_ASSERT(); struct stat s; char *dirname; if (asprintf(&dirname, "%s/.firejail", cfg.homedir) == -1) errExit("asprintf"); // check if ~/.firejail already exists if (lstat(dirname, &s) == 0) { if (!S_ISDIR(s.st_mode)) { if (S_ISLNK(s.st_mode)) fprintf(stderr, "Error: %s is a symbolic link\n", dirname); else fprintf(stderr, "Error: %s is not a directory\n", dirname); exit(1); } if (s.st_uid != getuid()) { fprintf(stderr, "Error: %s is not owned by the current user\n", dirname); exit(1); } } else { // create ~/.firejail directory create_empty_dir_as_user(dirname, 0700); if (stat(dirname, &s) == -1) { fprintf(stderr, "Error: cannot create directory %s\n", dirname); exit(1); } } free(dirname); // check overlay directory if (asprintf(&dirname, "%s/.firejail/%s", cfg.homedir, subdirname) == -1) errExit("asprintf"); if (lstat(dirname, &s) == 0) { if (!S_ISDIR(s.st_mode)) { if (S_ISLNK(s.st_mode)) fprintf(stderr, "Error: %s is a symbolic link\n", dirname); else fprintf(stderr, "Error: %s is not a directory\n", dirname); exit(1); } if (s.st_uid != 0) { fprintf(stderr, "Error: overlay directory %s is not owned by the root user\n", dirname); exit(1); } if (allow_reuse == 0) { fprintf(stderr, "Error: overlay directory exists, but reuse is not allowed\n"); exit(1); } } return dirname; } // mount overlayfs on top of / directory // mounting an overlay and chrooting into it: // // Old Ubuntu kernel // # cd ~ // # mkdir -p overlay/root // # mkdir -p overlay/diff // # mount -t overlayfs -o lowerdir=/,upperdir=/root/overlay/diff overlayfs /root/overlay/root // # chroot /root/overlay/root // to shutdown, first exit the chroot and then unmount the overlay // # exit // # umount /root/overlay/root // // Kernels 3.18+ // # cd ~ // # mkdir -p overlay/root // # mkdir -p overlay/diff // # mkdir -p overlay/work // # mount -t overlay -o lowerdir=/,upperdir=/root/overlay/diff,workdir=/root/overlay/work overlay /root/overlay/root // # cat /etc/mtab | grep overlay // /root/overlay /root/overlay/root overlay rw,relatime,lowerdir=/,upperdir=/root/overlay/diff,workdir=/root/overlay/work 0 0 // # chroot /root/overlay/root // to shutdown, first exit the chroot and then unmount the overlay // # exit // # umount /root/overlay/root // to do: fix the code below #include void fs_overlayfs(void) { struct stat s; // check kernel version struct utsname u; int rv = uname(&u); if (rv != 0) errExit("uname"); int major; int minor; if (2 != sscanf(u.release, "%d.%d", &major, &minor)) { fprintf(stderr, "Error: cannot extract Linux kernel version: %s\n", u.version); exit(1); } if (arg_debug) printf("Linux kernel version %d.%d\n", major, minor); int oldkernel = 0; if (major < 3) { fprintf(stderr, "Error: minimum kernel version required 3.x\n"); exit(1); } if (major == 3 && minor < 18) oldkernel = 1; // mounting an overlayfs on top of / seems to be broken for kernels > 4.19 // we disable overlayfs for now, pending fixing if (major >= 4 &&minor >= 19) { fprintf(stderr, "Error: OverlayFS disabled for Linux kernels 4.19 and newer, pending fixing.\n"); exit(1); } char *oroot = RUN_OVERLAY_ROOT; mkdir_attr(oroot, 0755, 0, 0); // set base for working and diff directories char *basedir = RUN_MNT_DIR; int basefd = -1; if (arg_overlay_keep) { basedir = cfg.overlay_dir; assert(basedir); // get a file descriptor for ~/.firejail, fails if there is any symlink char *firejail; if (asprintf(&firejail, "%s/.firejail", cfg.homedir) == -1) errExit("asprintf"); int fd = safer_openat(-1, firejail, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); if (fd == -1) errExit("safer_openat"); free(firejail); // create basedir if it doesn't exist // the new directory will be owned by root const char *dirname = gnu_basename(basedir); if (mkdirat(fd, dirname, 0755) == -1 && errno != EEXIST) { perror("mkdir"); fprintf(stderr, "Error: cannot create overlay directory %s\n", basedir); exit(1); } // open basedir basefd = openat(fd, dirname, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); close(fd); } else { basefd = open(basedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); } if (basefd == -1) { perror("open"); fprintf(stderr, "Error: cannot open overlay directory %s\n", basedir); exit(1); } // confirm once more base is owned by root if (fstat(basefd, &s) == -1) errExit("fstat"); if (s.st_uid != 0) { fprintf(stderr, "Error: overlay directory %s is not owned by the root user\n", basedir); exit(1); } // confirm permissions of base are 0755 if (((S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) & s.st_mode) != (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) { fprintf(stderr, "Error: invalid permissions on overlay directory %s\n", basedir); exit(1); } // create diff and work directories inside base // no need to check arg_overlay_reuse char *odiff; if (asprintf(&odiff, "%s/odiff", basedir) == -1) errExit("asprintf"); // the new directory will be owned by root if (mkdirat(basefd, "odiff", 0755) == -1 && errno != EEXIST) { perror("mkdir"); fprintf(stderr, "Error: cannot create overlay directory %s\n", odiff); exit(1); } ASSERT_PERMS(odiff, 0, 0, 0755); char *owork; if (asprintf(&owork, "%s/owork", basedir) == -1) errExit("asprintf"); // the new directory will be owned by root if (mkdirat(basefd, "owork", 0755) == -1 && errno != EEXIST) { perror("mkdir"); fprintf(stderr, "Error: cannot create overlay directory %s\n", owork); exit(1); } ASSERT_PERMS(owork, 0, 0, 0755); // mount overlayfs if (arg_debug) printf("Mounting OverlayFS\n"); char *option; if (oldkernel) { // old Ubuntu/OpenSUSE kernels if (arg_overlay_keep) { fprintf(stderr, "Error: option --overlay= not available for kernels older than 3.18\n"); exit(1); } if (asprintf(&option, "lowerdir=/,upperdir=%s", odiff) == -1) errExit("asprintf"); if (mount("overlayfs", oroot, "overlayfs", MS_MGC_VAL, option) < 0) errExit("mounting overlayfs"); } else { // kernel 3.18 or newer if (asprintf(&option, "lowerdir=/,upperdir=%s,workdir=%s", odiff, owork) == -1) errExit("asprintf"); if (mount("overlay", oroot, "overlay", MS_MGC_VAL, option) < 0) { fprintf(stderr, "Debug: running on kernel version %d.%d\n", major, minor); errExit("mounting overlayfs"); } //*************************** // issue #263 start code // My setup has a separate mount point for /home. When the overlay is mounted, // the overlay does not contain the original /home contents. // I added code to create a second overlay for /home if the overlay home dir is empty and this seems to work // @dshmgh, Jan 2016 { char *overlayhome; struct stat s; char *hroot; char *hdiff; char *hwork; // dons add debug if (arg_debug) printf ("DEBUG: chroot dirs are oroot %s odiff %s owork %s\n",oroot,odiff,owork); // BEFORE NEXT, WE NEED TO TEST IF /home has any contents or do we need to mount it? // must create var for oroot/cfg.homedir if (asprintf(&overlayhome, "%s%s", oroot, cfg.homedir) == -1) errExit("asprintf"); if (arg_debug) printf ("DEBUG: overlayhome var holds ##%s##\n", overlayhome); // if no homedir in overlay -- create another overlay for /home if (stat(cfg.homedir, &s) == 0 && stat(overlayhome, &s) == -1) { // no need to check arg_overlay_reuse if (asprintf(&hdiff, "%s/hdiff", basedir) == -1) errExit("asprintf"); // the new directory will be owned by root if (mkdirat(basefd, "hdiff", 0755) == -1 && errno != EEXIST) { perror("mkdir"); fprintf(stderr, "Error: cannot create overlay directory %s\n", hdiff); exit(1); } ASSERT_PERMS(hdiff, 0, 0, 0755); // no need to check arg_overlay_reuse if (asprintf(&hwork, "%s/hwork", basedir) == -1) errExit("asprintf"); // the new directory will be owned by root if (mkdirat(basefd, "hwork", 0755) == -1 && errno != EEXIST) { perror("mkdir"); fprintf(stderr, "Error: cannot create overlay directory %s\n", hwork); exit(1); } ASSERT_PERMS(hwork, 0, 0, 0755); // no homedir in overlay so now mount another overlay for /home if (asprintf(&hroot, "%s/home", oroot) == -1) errExit("asprintf"); if (asprintf(&option, "lowerdir=/home,upperdir=%s,workdir=%s", hdiff, hwork) == -1) errExit("asprintf"); if (mount("overlay", hroot, "overlay", MS_MGC_VAL, option) < 0) errExit("mounting overlayfs for mounted home directory"); printf("OverlayFS for /home configured in %s directory\n", basedir); free(hroot); free(hdiff); free(hwork); } // stat(overlayhome) free(overlayhome); } // issue #263 end code //*************************** } fmessage("OverlayFS configured in %s directory\n", basedir); close(basefd); // /dev, /run and /tmp are not covered by the overlay // mount-bind dev directory if (arg_debug) printf("Mounting /dev\n"); char *dev; if (asprintf(&dev, "%s/dev", oroot) == -1) errExit("asprintf"); if (mount("/dev", dev, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev"); fs_logger("whitelist /dev"); // mount-bind run directory if (arg_debug) printf("Mounting /run\n"); char *run; if (asprintf(&run, "%s/run", oroot) == -1) errExit("asprintf"); if (mount("/run", run, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /run"); fs_logger("whitelist /run"); // mount-bind tmp directory if (arg_debug) printf("Mounting /tmp\n"); char *tmp; if (asprintf(&tmp, "%s/tmp", oroot) == -1) errExit("asprintf"); if (mount("/tmp", tmp, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /tmp"); fs_logger("whitelist /tmp"); // chroot in the new filesystem __gcov_flush(); if (chroot(oroot) == -1) errExit("chroot"); // mount a new proc filesystem if (arg_debug) printf("Mounting /proc filesystem representing the PID namespace\n"); if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) errExit("mounting /proc"); // update /var directory in order to support multiple sandboxes running on the same root directory // if (!arg_private_dev) // fs_dev_shm(); fs_var_lock(); if (!arg_keep_var_tmp) fs_var_tmp(); if (!arg_writable_var_log) fs_var_log(); fs_var_lib(); fs_var_cache(); fs_var_utmp(); fs_machineid(); // don't leak user information restrict_users(); // when starting as root, firejail config is not disabled; if (getuid() != 0) disable_config(); // cleanup and exit free(option); free(odiff); free(owork); free(dev); free(run); free(tmp); } static int remove_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { (void) sb; (void) typeflag; (void) ftwbuf; assert(fpath); if (strcmp(fpath, ".") == 0) // rmdir would fail with EINVAL return 0; if (remove(fpath)) { // removes the link not the actual file fprintf(stderr, "Error: cannot remove file: %s\n", strerror(errno)); exit(1); } return 0; } int remove_overlay_directory(void) { EUID_ASSERT(); sleep(1); char *path; if (asprintf(&path, "%s/.firejail", cfg.homedir) == -1) errExit("asprintf"); if (access(path, F_OK) == 0) { pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { // open ~/.firejail int fd = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); if (fd == -1) { fprintf(stderr, "Error: cannot open %s\n", path); exit(1); } struct stat s; if (fstat(fd, &s) == -1) errExit("fstat"); if (!S_ISDIR(s.st_mode)) { if (S_ISLNK(s.st_mode)) fprintf(stderr, "Error: %s is a symbolic link\n", path); else fprintf(stderr, "Error: %s is not a directory\n", path); exit(1); } if (s.st_uid != getuid()) { fprintf(stderr, "Error: %s is not owned by the current user\n", path); exit(1); } // chdir to ~/.firejail if (fchdir(fd) == -1) errExit("fchdir"); close(fd); EUID_ROOT(); // FTW_PHYS - do not follow symbolic links if (nftw(".", remove_callback, 64, FTW_DEPTH | FTW_PHYS) == -1) errExit("nftw"); EUID_USER(); // remove ~/.firejail if (rmdir(path) == -1) errExit("rmdir"); __gcov_flush(); _exit(0); } // wait for the child to finish waitpid(child, NULL, 0); // check if ~/.firejail was deleted if (access(path, F_OK) == 0) return 1; } return 0; } #endif // HAVE_OVERLAYFS