From 9dfecbe49fbbd85714d64b18933600b0d7f88ff3 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Sun, 29 Sep 2019 17:26:32 +0200 Subject: move chroot from path based to file descriptor based mounts --- src/firejail/fs.c | 181 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 105 insertions(+), 76 deletions(-) diff --git a/src/firejail/fs.c b/src/firejail/fs.c index ce2ca5e2a..1c91d96d4 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #ifndef O_PATH @@ -1228,122 +1229,150 @@ void fs_check_chroot_dir(const char *rootdir) { close(parentfd); } +// copy /etc/resolv.conf in chroot directory +static void copy_resolvconf(int parentfd) { + int in = open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC); + if (in == -1) { + fwarning("/etc/resolv.conf not initialized\n"); + return; + } + struct stat instat; + if (fstat(in, &instat) == -1) + errExit("fstat"); + // try to detect if resolv.conf has been bind mounted into the chroot + // do nothing in this case in order to not truncate the real file + struct stat outstat; + if (fstatat(parentfd, "etc/resolv.conf", &outstat, 0) == 0) { + if (instat.st_dev == outstat.st_dev && instat.st_ino == outstat.st_ino) { + close(in); + return; + } + } + if (arg_debug) + printf("Updating /etc/resolv.conf in chroot\n"); + int out = openat(parentfd, "etc/resolv.conf", O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); + if (out == -1) + errExit("open"); + if (sendfile(out, in, NULL, instat.st_size) == -1) + errExit("sendfile"); + close(in); + close(out); +} + // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf void fs_chroot(const char *rootdir) { assert(rootdir); + int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); + if (parentfd == -1) + errExit("safe_fd"); + // mount-bind a /dev in rootdir - char *newdev; - if (asprintf(&newdev, "%s/dev", rootdir) == -1) - errExit("asprintf"); if (arg_debug) - printf("Mounting /dev on %s\n", newdev); - if (mount("/dev", newdev, NULL, MS_BIND|MS_REC, NULL) < 0) + printf("Mounting /dev on chroot /dev\n"); + int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -1) + errExit("open"); + char *proc; + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) + errExit("asprintf"); + if (mount("/dev", proc, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev"); - free(newdev); + free(proc); + close(fd); - // mount a new proc filesystem - char *newproc; - if (asprintf(&newproc, "%s/proc", rootdir) == -1) - errExit("asprintf"); + // mount a brand new proc filesystem if (arg_debug) - printf("Mounting /proc filesystem on %s\n", newproc); - if (mount("proc", newproc, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) + printf("Mounting /proc filesystem on chroot /proc\n"); + fd = openat(parentfd, "proc", O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -1) + errExit("open"); + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) + errExit("asprintf"); + if (mount("proc", proc, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) errExit("mounting /proc"); - free(newproc); + free(proc); + close(fd); // x11 if (getenv("FIREJAIL_X11")) { - char *newx11; - if (asprintf(&newx11, "%s/tmp/.X11-unix", rootdir) == -1) - errExit("asprintf"); if (arg_debug) - printf("Mounting /tmp/.X11-unix on %s\n", newx11); - if (mount("/tmp/.X11-unix", newx11, NULL, MS_BIND|MS_REC, NULL) < 0) + printf("Mounting /tmp/.X11-unix on chroot /tmp/.X11-unix\n"); + fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -1) + errExit("open"); + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) + errExit("asprintf"); + if (mount("/tmp/.X11-unix", proc, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /tmp/.X11-unix"); - free(newx11); + free(proc); + close(fd); } - // some older distros don't have a /run directory - // create one by default - char *rundir; - if (asprintf(&rundir, "%s/run", rootdir) == -1) - errExit("asprintf"); + // update chroot resolv.conf + copy_resolvconf(parentfd); + + // some older distros don't have a /run directory, create one by default struct stat s; - if (lstat(rundir, &s) == 0) { + if (fstatat(parentfd, "run", &s, AT_SYMLINK_NOFOLLOW) == 0) { if (S_ISLNK(s.st_mode)) { fprintf(stderr, "Error: chroot /run is a symbolic link\n"); exit(1); } - if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { - fprintf(stderr, "Error: chroot /run should be a directory owned by root\n"); - exit(1); - } - if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { - fprintf(stderr, "Error: only root user should be given write permission on chroot /run\n"); - exit(1); - } } - else { - // several sandboxes could race to create /run - if (mkdir(rundir, 0755) == -1 && errno != EEXIST) - errExit("mkdir"); - ASSERT_PERMS(rundir, 0, 0, 0755); - } - free(rundir); + else if (mkdirat(parentfd, "run", 0755) == -1 && errno != EEXIST) + errExit("mkdir"); + fs_check_chroot_subdir("run", parentfd, 1); // create /run/firejail directory in chroot - if (asprintf(&rundir, "%s/run/firejail", rootdir) == -1) - errExit("asprintf"); - if (mkdir(rundir, 0755) == -1 && errno != EEXIST) + if (mkdirat(parentfd, RUN_FIREJAIL_DIR+1, 0755) == -1 && errno != EEXIST) errExit("mkdir"); - ASSERT_PERMS(rundir, 0, 0, 0755); - free(rundir); - // create /run/firejail/lib directory in chroot and mount it - if (asprintf(&rundir, "%s%s", rootdir, RUN_FIREJAIL_LIB_DIR) == -1) - errExit("asprintf"); - if (mkdir(rundir, 0755) == -1 && errno != EEXIST) + // create /run/firejail/lib directory in chroot + if (mkdirat(parentfd, RUN_FIREJAIL_LIB_DIR+1, 0755) == -1 && errno != EEXIST) errExit("mkdir"); - ASSERT_PERMS(rundir, 0, 0, 0755); - if (mount(RUN_FIREJAIL_LIB_DIR, rundir, NULL, MS_BIND|MS_REC, NULL) < 0) - errExit("mount bind"); - free(rundir); - - // create /run/firejail/mnt directory in chroot and mount the current one - if (asprintf(&rundir, "%s%s", rootdir, RUN_MNT_DIR) == -1) + // mount lib directory into the chroot + fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -1) + errExit("open"); + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) errExit("asprintf"); - if (mkdir(rundir, 0755) == -1 && errno != EEXIST) - errExit("mkdir"); - ASSERT_PERMS(rundir, 0, 0, 0755); - if (mount(RUN_MNT_DIR, rundir, NULL, MS_BIND|MS_REC, NULL) < 0) + if (mount(RUN_FIREJAIL_LIB_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); - free(rundir); + free(proc); + close(fd); - // copy /etc/resolv.conf in chroot directory - char *fname; - if (asprintf(&fname, "%s/etc/resolv.conf", rootdir) == -1) + // create /run/firejail/mnt directory in chroot + if (mkdirat(parentfd, RUN_MNT_DIR+1, 0755) == -1 && errno != EEXIST) + errExit("mkdir"); + // mount the current mnt directory into the chroot + fd = openat(parentfd, RUN_MNT_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -1) + errExit("open"); + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) errExit("asprintf"); - if (arg_debug) - printf("Updating /etc/resolv.conf in %s\n", fname); - unlink(fname); - if (copy_file("/etc/resolv.conf", fname, 0, 0, 0644) == -1) // root needed - fwarning("/etc/resolv.conf not initialized\n"); - free(fname); + if (mount(RUN_MNT_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) + errExit("mount bind"); + free(proc); + close(fd); - // chroot into the new directory #ifdef HAVE_GCOV __gcov_flush(); #endif - // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay - // and chroot into this new directory - if (arg_debug) - printf("Chrooting into %s\n", rootdir); + // create /run/firejail/mnt/oroot char *oroot = RUN_OVERLAY_ROOT; if (mkdir(oroot, 0755) == -1) errExit("mkdir"); - if (mount(rootdir, oroot, NULL, MS_BIND|MS_REC, NULL) < 0) + // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay + if (asprintf(&proc, "/proc/self/fd/%d", parentfd) == -1) + errExit("asprintf"); + if (mount(proc, oroot, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting rootdir oroot"); + free(proc); + close(parentfd); + // chroot into the new directory + if (arg_debug) + printf("Chrooting into %s\n", rootdir); if (chroot(oroot) < 0) errExit("chroot"); -- cgit v1.2.3-70-g09d2 From 94aba62229f083173cb28ffe370eaa9f3ee540b9 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Sun, 29 Sep 2019 17:58:27 +0200 Subject: chroot module --- src/firejail/chroot.c | 309 ++++++++++++++++++++++++++++++++++++++++++++++++ src/firejail/firejail.h | 10 +- src/firejail/fs.c | 284 +------------------------------------------- 3 files changed, 319 insertions(+), 284 deletions(-) create mode 100644 src/firejail/chroot.c diff --git a/src/firejail/chroot.c b/src/firejail/chroot.c new file mode 100644 index 000000000..c99af1d52 --- /dev/null +++ b/src/firejail/chroot.c @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2014-2019 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_CHROOT +#include "firejail.h" +#include +#include +#include +#include + +#include +#ifndef O_PATH +# define O_PATH 010000000 +#endif + + +// exit if error +static void fs_check_chroot_subdir(const char *subdir, int parentfd, int check_writable) { + assert(subdir); + int fd = openat(parentfd, subdir, O_PATH|O_CLOEXEC); + if (fd == -1) { + if (errno == ENOENT) + fprintf(stderr, "Error: cannot find /%s in chroot directory\n", subdir); + else { + perror("open"); + fprintf(stderr, "Error: cannot open /%s in chroot directory\n", subdir); + } + exit(1); + } + struct stat s; + if (fstat(fd, &s) == -1) + errExit("fstat"); + close(fd); + if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { + fprintf(stderr, "Error: chroot /%s should be a directory owned by root\n", subdir); + exit(1); + } + if (check_writable && ((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { + fprintf(stderr, "Error: only root user should be given write permission on chroot /%s\n", subdir); + exit(1); + } +} + +// exit if error +void fs_check_chroot_dir(const char *rootdir) { + EUID_ASSERT(); + assert(rootdir); + + char *overlay; + if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) + errExit("asprintf"); + if (strncmp(rootdir, overlay, strlen(overlay)) == 0) { + fprintf(stderr, "Error: invalid chroot directory: no directories in %s are allowed\n", overlay); + exit(1); + } + free(overlay); + + // fails if there is any symlink or if rootdir is not a directory + int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); + if (parentfd == -1) { + fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir); + exit(1); + } + // rootdir has to be owned by root and is not allowed to be generally writable, + // this also excludes /tmp, /var/tmp and such + struct stat s; + if (fstat(parentfd, &s) == -1) + errExit("fstat"); + if (s.st_uid != 0) { + fprintf(stderr, "Error: chroot directory should be owned by root\n"); + exit(1); + } + if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { + fprintf(stderr, "Error: only root user should be given write permission on chroot directory\n"); + exit(1); + } + + // check subdirectories in rootdir + fs_check_chroot_subdir("dev", parentfd, 0); + fs_check_chroot_subdir("etc", parentfd, 1); + fs_check_chroot_subdir("proc", parentfd, 0); + fs_check_chroot_subdir("tmp", parentfd, 0); + fs_check_chroot_subdir("var/tmp", parentfd, 0); + + // there should be no checking on /etc/resolv.conf + // the file is replaced with the real /etc/resolv.conf anyway +#if 0 + if (asprintf(&name, "%s/etc/resolv.conf", rootdir) == -1) + errExit("asprintf"); + if (stat(name, &s) == 0) { + if (s.st_uid != 0) { + fprintf(stderr, "Error: chroot /etc/resolv.conf should be owned by root\n"); + exit(1); + } + } + else { + fprintf(stderr, "Error: chroot /etc/resolv.conf not found\n"); + exit(1); + } + // on Arch /etc/resolv.conf could be a symlink to /run/systemd/resolve/resolv.conf + // on Ubuntu 17.04 /etc/resolv.conf could be a symlink to /run/resolveconf/resolv.conf + if (is_link(name)) { + // check the link points in chroot + char *rname = realpath(name, NULL); + if (!rname || strncmp(rname, rootdir, strlen(rootdir)) != 0) { + fprintf(stderr, "Error: chroot /etc/resolv.conf is pointing outside chroot\n"); + exit(1); + } + } + free(name); +#endif + + // check x11 socket directory + if (getenv("FIREJAIL_X11")) + fs_check_chroot_subdir("tmp/.X11-unix", parentfd, 0); + + close(parentfd); +} + +// copy /etc/resolv.conf in chroot directory +static void copy_resolvconf(int parentfd) { + int in = open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC); + if (in == -1) { + fwarning("/etc/resolv.conf not initialized\n"); + return; + } + struct stat instat; + if (fstat(in, &instat) == -1) + errExit("fstat"); + // try to detect if resolv.conf has been bind mounted into the chroot + // do nothing in this case in order to not truncate the real file + struct stat outstat; + if (fstatat(parentfd, "etc/resolv.conf", &outstat, 0) == 0) { + if (instat.st_dev == outstat.st_dev && instat.st_ino == outstat.st_ino) { + close(in); + return; + } + } + if (arg_debug) + printf("Updating /etc/resolv.conf in chroot\n"); + int out = openat(parentfd, "etc/resolv.conf", O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); + if (out == -1) + errExit("open"); + if (sendfile(out, in, NULL, instat.st_size) == -1) + errExit("sendfile"); + close(in); + close(out); +} + +// chroot into an existing directory; mount existing /dev and update /etc/resolv.conf +void fs_chroot(const char *rootdir) { + assert(rootdir); + + int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); + if (parentfd == -1) + errExit("safe_fd"); + + // mount-bind a /dev in rootdir + if (arg_debug) + printf("Mounting /dev on chroot /dev\n"); + int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -1) + errExit("open"); + char *proc; + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) + errExit("asprintf"); + if (mount("/dev", proc, NULL, MS_BIND|MS_REC, NULL) < 0) + errExit("mounting /dev"); + free(proc); + close(fd); + + // mount a brand new proc filesystem + if (arg_debug) + printf("Mounting /proc filesystem on chroot /proc\n"); + fd = openat(parentfd, "proc", O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -1) + errExit("open"); + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) + errExit("asprintf"); + if (mount("proc", proc, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) + errExit("mounting /proc"); + free(proc); + close(fd); + + // x11 + if (getenv("FIREJAIL_X11")) { + if (arg_debug) + printf("Mounting /tmp/.X11-unix on chroot /tmp/.X11-unix\n"); + fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -1) + errExit("open"); + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) + errExit("asprintf"); + if (mount("/tmp/.X11-unix", proc, NULL, MS_BIND|MS_REC, NULL) < 0) + errExit("mounting /tmp/.X11-unix"); + free(proc); + close(fd); + } + + // update chroot resolv.conf + copy_resolvconf(parentfd); + + // some older distros don't have a /run directory, create one by default + struct stat s; + if (fstatat(parentfd, "run", &s, AT_SYMLINK_NOFOLLOW) == 0) { + if (S_ISLNK(s.st_mode)) { + fprintf(stderr, "Error: chroot /run is a symbolic link\n"); + exit(1); + } + } + else if (mkdirat(parentfd, "run", 0755) == -1 && errno != EEXIST) + errExit("mkdir"); + fs_check_chroot_subdir("run", parentfd, 1); + + // create /run/firejail directory in chroot + if (mkdirat(parentfd, RUN_FIREJAIL_DIR+1, 0755) == -1 && errno != EEXIST) + errExit("mkdir"); + + // create /run/firejail/lib directory in chroot + if (mkdirat(parentfd, RUN_FIREJAIL_LIB_DIR+1, 0755) == -1 && errno != EEXIST) + errExit("mkdir"); + // mount lib directory into the chroot + fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -1) + errExit("open"); + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) + errExit("asprintf"); + if (mount(RUN_FIREJAIL_LIB_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) + errExit("mount bind"); + free(proc); + close(fd); + + // create /run/firejail/mnt directory in chroot + if (mkdirat(parentfd, RUN_MNT_DIR+1, 0755) == -1 && errno != EEXIST) + errExit("mkdir"); + // mount the current mnt directory into the chroot + fd = openat(parentfd, RUN_MNT_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (fd == -1) + errExit("open"); + if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) + errExit("asprintf"); + if (mount(RUN_MNT_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) + errExit("mount bind"); + free(proc); + close(fd); + +#ifdef HAVE_GCOV + __gcov_flush(); +#endif + // create /run/firejail/mnt/oroot + char *oroot = RUN_OVERLAY_ROOT; + if (mkdir(oroot, 0755) == -1) + errExit("mkdir"); + // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay + if (asprintf(&proc, "/proc/self/fd/%d", parentfd) == -1) + errExit("asprintf"); + if (mount(proc, oroot, NULL, MS_BIND|MS_REC, NULL) < 0) + errExit("mounting rootdir oroot"); + free(proc); + close(parentfd); + // chroot into the new directory + if (arg_debug) + printf("Chrooting into %s\n", rootdir); + if (chroot(oroot) < 0) + errExit("chroot"); + + // create all other /run/firejail files and directories + preproc_build_firejail_dir(); + + // 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(); +} + +#endif // HAVE_CHROOT diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index a6377261f..80cf71caf 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -386,18 +386,22 @@ void fs_remount(const char *dir, OPERATION op, unsigned check_mnt); void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt); // mount /proc and /sys directories void fs_proc_sys_dev_boot(void); +// blacklist firejail configuration and runtime directories +void disable_config(void); // build a basic read-only filesystem void fs_basic_fs(void); // mount overlayfs on top of / directory char *fs_check_overlay_dir(const char *subdirname, int allow_reuse); void fs_overlayfs(void); -// chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf -void fs_chroot(const char *rootdir); -void fs_check_chroot_dir(const char *rootdir); void fs_private_tmp(void); void fs_private_cache(void); void fs_mnt(const int enforce); +// chroot.c +// chroot into an existing directory; mount existing /dev and update /etc/resolv.conf +void fs_check_chroot_dir(const char *rootdir); +void fs_chroot(const char *rootdir); + // profile.c // find and read the profile specified by name from dir directory int profile_find_firejail(const char *name, int add_ext); diff --git a/src/firejail/fs.c b/src/firejail/fs.c index 1c91d96d4..f2639f318 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c @@ -27,7 +27,7 @@ #include #include #include -#include + #include #ifndef O_PATH @@ -697,8 +697,8 @@ void fs_proc_sys_dev_boot(void) { } } -// disable firejail configuration in /etc/firejail and in ~/.config/firejail -static void disable_config(void) { +// disable firejail configuration in ~/.config/firejail +void disable_config(void) { struct stat s; char *fname; @@ -1124,284 +1124,6 @@ void fs_overlayfs(void) { } #endif - -#ifdef HAVE_CHROOT -// exit if error -static void fs_check_chroot_subdir(const char *subdir, int parentfd, int check_writable) { - assert(subdir); - int fd = openat(parentfd, subdir, O_PATH|O_CLOEXEC); - if (fd == -1) { - if (errno == ENOENT) - fprintf(stderr, "Error: cannot find /%s in chroot directory\n", subdir); - else { - perror("open"); - fprintf(stderr, "Error: cannot open /%s in chroot directory\n", subdir); - } - exit(1); - } - struct stat s; - if (fstat(fd, &s) == -1) - errExit("fstat"); - close(fd); - if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { - fprintf(stderr, "Error: chroot /%s should be a directory owned by root\n", subdir); - exit(1); - } - if (check_writable && ((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { - fprintf(stderr, "Error: only root user should be given write permission on chroot /%s\n", subdir); - exit(1); - } -} - -// exit if error -void fs_check_chroot_dir(const char *rootdir) { - EUID_ASSERT(); - assert(rootdir); - - char *overlay; - if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) - errExit("asprintf"); - if (strncmp(rootdir, overlay, strlen(overlay)) == 0) { - fprintf(stderr, "Error: invalid chroot directory: no directories in %s are allowed\n", overlay); - exit(1); - } - free(overlay); - - // fails if there is any symlink or if rootdir is not a directory - int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); - if (parentfd == -1) { - fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir); - exit(1); - } - // rootdir has to be owned by root and is not allowed to be generally writable, - // this also excludes /tmp, /var/tmp and such - struct stat s; - if (fstat(parentfd, &s) == -1) - errExit("fstat"); - if (s.st_uid != 0) { - fprintf(stderr, "Error: chroot directory should be owned by root\n"); - exit(1); - } - if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { - fprintf(stderr, "Error: only root user should be given write permission on chroot directory\n"); - exit(1); - } - - // check subdirectories in rootdir - fs_check_chroot_subdir("dev", parentfd, 0); - fs_check_chroot_subdir("etc", parentfd, 1); - fs_check_chroot_subdir("proc", parentfd, 0); - fs_check_chroot_subdir("tmp", parentfd, 0); - fs_check_chroot_subdir("var/tmp", parentfd, 0); - - // there should be no checking on /etc/resolv.conf - // the file is replaced with the real /etc/resolv.conf anyway -#if 0 - if (asprintf(&name, "%s/etc/resolv.conf", rootdir) == -1) - errExit("asprintf"); - if (stat(name, &s) == 0) { - if (s.st_uid != 0) { - fprintf(stderr, "Error: chroot /etc/resolv.conf should be owned by root\n"); - exit(1); - } - } - else { - fprintf(stderr, "Error: chroot /etc/resolv.conf not found\n"); - exit(1); - } - // on Arch /etc/resolv.conf could be a symlink to /run/systemd/resolve/resolv.conf - // on Ubuntu 17.04 /etc/resolv.conf could be a symlink to /run/resolveconf/resolv.conf - if (is_link(name)) { - // check the link points in chroot - char *rname = realpath(name, NULL); - if (!rname || strncmp(rname, rootdir, strlen(rootdir)) != 0) { - fprintf(stderr, "Error: chroot /etc/resolv.conf is pointing outside chroot\n"); - exit(1); - } - } - free(name); -#endif - - // check x11 socket directory - if (getenv("FIREJAIL_X11")) - fs_check_chroot_subdir("tmp/.X11-unix", parentfd, 0); - - close(parentfd); -} - -// copy /etc/resolv.conf in chroot directory -static void copy_resolvconf(int parentfd) { - int in = open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC); - if (in == -1) { - fwarning("/etc/resolv.conf not initialized\n"); - return; - } - struct stat instat; - if (fstat(in, &instat) == -1) - errExit("fstat"); - // try to detect if resolv.conf has been bind mounted into the chroot - // do nothing in this case in order to not truncate the real file - struct stat outstat; - if (fstatat(parentfd, "etc/resolv.conf", &outstat, 0) == 0) { - if (instat.st_dev == outstat.st_dev && instat.st_ino == outstat.st_ino) { - close(in); - return; - } - } - if (arg_debug) - printf("Updating /etc/resolv.conf in chroot\n"); - int out = openat(parentfd, "etc/resolv.conf", O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); - if (out == -1) - errExit("open"); - if (sendfile(out, in, NULL, instat.st_size) == -1) - errExit("sendfile"); - close(in); - close(out); -} - -// chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf -void fs_chroot(const char *rootdir) { - assert(rootdir); - - int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); - if (parentfd == -1) - errExit("safe_fd"); - - // mount-bind a /dev in rootdir - if (arg_debug) - printf("Mounting /dev on chroot /dev\n"); - int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_CLOEXEC); - if (fd == -1) - errExit("open"); - char *proc; - if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) - errExit("asprintf"); - if (mount("/dev", proc, NULL, MS_BIND|MS_REC, NULL) < 0) - errExit("mounting /dev"); - free(proc); - close(fd); - - // mount a brand new proc filesystem - if (arg_debug) - printf("Mounting /proc filesystem on chroot /proc\n"); - fd = openat(parentfd, "proc", O_PATH|O_DIRECTORY|O_CLOEXEC); - if (fd == -1) - errExit("open"); - if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) - errExit("asprintf"); - if (mount("proc", proc, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) - errExit("mounting /proc"); - free(proc); - close(fd); - - // x11 - if (getenv("FIREJAIL_X11")) { - if (arg_debug) - printf("Mounting /tmp/.X11-unix on chroot /tmp/.X11-unix\n"); - fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_CLOEXEC); - if (fd == -1) - errExit("open"); - if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) - errExit("asprintf"); - if (mount("/tmp/.X11-unix", proc, NULL, MS_BIND|MS_REC, NULL) < 0) - errExit("mounting /tmp/.X11-unix"); - free(proc); - close(fd); - } - - // update chroot resolv.conf - copy_resolvconf(parentfd); - - // some older distros don't have a /run directory, create one by default - struct stat s; - if (fstatat(parentfd, "run", &s, AT_SYMLINK_NOFOLLOW) == 0) { - if (S_ISLNK(s.st_mode)) { - fprintf(stderr, "Error: chroot /run is a symbolic link\n"); - exit(1); - } - } - else if (mkdirat(parentfd, "run", 0755) == -1 && errno != EEXIST) - errExit("mkdir"); - fs_check_chroot_subdir("run", parentfd, 1); - - // create /run/firejail directory in chroot - if (mkdirat(parentfd, RUN_FIREJAIL_DIR+1, 0755) == -1 && errno != EEXIST) - errExit("mkdir"); - - // create /run/firejail/lib directory in chroot - if (mkdirat(parentfd, RUN_FIREJAIL_LIB_DIR+1, 0755) == -1 && errno != EEXIST) - errExit("mkdir"); - // mount lib directory into the chroot - fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); - if (fd == -1) - errExit("open"); - if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) - errExit("asprintf"); - if (mount(RUN_FIREJAIL_LIB_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) - errExit("mount bind"); - free(proc); - close(fd); - - // create /run/firejail/mnt directory in chroot - if (mkdirat(parentfd, RUN_MNT_DIR+1, 0755) == -1 && errno != EEXIST) - errExit("mkdir"); - // mount the current mnt directory into the chroot - fd = openat(parentfd, RUN_MNT_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); - if (fd == -1) - errExit("open"); - if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) - errExit("asprintf"); - if (mount(RUN_MNT_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) - errExit("mount bind"); - free(proc); - close(fd); - -#ifdef HAVE_GCOV - __gcov_flush(); -#endif - // create /run/firejail/mnt/oroot - char *oroot = RUN_OVERLAY_ROOT; - if (mkdir(oroot, 0755) == -1) - errExit("mkdir"); - // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay - if (asprintf(&proc, "/proc/self/fd/%d", parentfd) == -1) - errExit("asprintf"); - if (mount(proc, oroot, NULL, MS_BIND|MS_REC, NULL) < 0) - errExit("mounting rootdir oroot"); - free(proc); - close(parentfd); - // chroot into the new directory - if (arg_debug) - printf("Chrooting into %s\n", rootdir); - if (chroot(oroot) < 0) - errExit("chroot"); - - // create all other /run/firejail files and directories - preproc_build_firejail_dir(); - - // 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(); -} -#endif - // this function is called from sandbox.c before blacklist/whitelist functions void fs_private_tmp(void) { // check XAUTHORITY file, KDE keeps it under /tmp -- cgit v1.2.3-70-g09d2 From 13305e4937dd1d48b8e11c03288f1f170d4c8d7d Mon Sep 17 00:00:00 2001 From: smitsohu Date: Sun, 29 Sep 2019 19:26:29 +0200 Subject: improve variable names --- src/firejail/chroot.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/firejail/chroot.c b/src/firejail/chroot.c index c99af1d52..4eff84494 100644 --- a/src/firejail/chroot.c +++ b/src/firejail/chroot.c @@ -141,14 +141,14 @@ static void copy_resolvconf(int parentfd) { fwarning("/etc/resolv.conf not initialized\n"); return; } - struct stat instat; - if (fstat(in, &instat) == -1) + struct stat src; + if (fstat(in, &src) == -1) errExit("fstat"); // try to detect if resolv.conf has been bind mounted into the chroot // do nothing in this case in order to not truncate the real file - struct stat outstat; - if (fstatat(parentfd, "etc/resolv.conf", &outstat, 0) == 0) { - if (instat.st_dev == outstat.st_dev && instat.st_ino == outstat.st_ino) { + struct stat dst; + if (fstatat(parentfd, "etc/resolv.conf", &dst, 0) == 0) { + if (src.st_dev == dst.st_dev && src.st_ino == dst.st_ino) { close(in); return; } @@ -158,7 +158,7 @@ static void copy_resolvconf(int parentfd) { int out = openat(parentfd, "etc/resolv.conf", O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); if (out == -1) errExit("open"); - if (sendfile(out, in, NULL, instat.st_size) == -1) + if (sendfile(out, in, NULL, src.st_size) == -1) errExit("sendfile"); close(in); close(out); -- cgit v1.2.3-70-g09d2 From 70c8924ebaf51387f74eba3c443ef7e870d2afc9 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Tue, 1 Oct 2019 15:46:12 +0200 Subject: base checks and mounts on same file descriptor --- src/firejail/chroot.c | 190 ++++++++++++++++++++---------------------------- src/firejail/firejail.h | 3 +- src/firejail/main.c | 32 +++----- 3 files changed, 91 insertions(+), 134 deletions(-) diff --git a/src/firejail/chroot.c b/src/firejail/chroot.c index 4eff84494..8a57dee35 100644 --- a/src/firejail/chroot.c +++ b/src/firejail/chroot.c @@ -21,7 +21,6 @@ #ifdef HAVE_CHROOT #include "firejail.h" #include -#include #include #include @@ -31,107 +30,33 @@ #endif -// exit if error -static void fs_check_chroot_subdir(const char *subdir, int parentfd, int check_writable) { - assert(subdir); - int fd = openat(parentfd, subdir, O_PATH|O_CLOEXEC); - if (fd == -1) { - if (errno == ENOENT) - fprintf(stderr, "Error: cannot find /%s in chroot directory\n", subdir); - else { - perror("open"); - fprintf(stderr, "Error: cannot open /%s in chroot directory\n", subdir); - } - exit(1); - } - struct stat s; - if (fstat(fd, &s) == -1) - errExit("fstat"); - close(fd); - if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { - fprintf(stderr, "Error: chroot /%s should be a directory owned by root\n", subdir); - exit(1); - } - if (check_writable && ((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { - fprintf(stderr, "Error: only root user should be given write permission on chroot /%s\n", subdir); - exit(1); - } -} - -// exit if error -void fs_check_chroot_dir(const char *rootdir) { +// exit if error, return resolved chroot path +char *fs_check_chroot_dir(const char *rootdir) { EUID_ASSERT(); assert(rootdir); + if (strstr(rootdir, "..") || + is_link(rootdir) || + !is_dir(rootdir)) + goto errout; + + // check chroot dirname exists, chrooting into the root directory is not allowed + char *rpath = realpath(rootdir, NULL); + if (rpath == NULL || strcmp(rpath, "/") == 0) + goto errout; char *overlay; if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) errExit("asprintf"); - if (strncmp(rootdir, overlay, strlen(overlay)) == 0) { + if (strncmp(rpath, overlay, strlen(overlay)) == 0) { fprintf(stderr, "Error: invalid chroot directory: no directories in %s are allowed\n", overlay); exit(1); } free(overlay); + return rpath; - // fails if there is any symlink or if rootdir is not a directory - int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); - if (parentfd == -1) { - fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir); - exit(1); - } - // rootdir has to be owned by root and is not allowed to be generally writable, - // this also excludes /tmp, /var/tmp and such - struct stat s; - if (fstat(parentfd, &s) == -1) - errExit("fstat"); - if (s.st_uid != 0) { - fprintf(stderr, "Error: chroot directory should be owned by root\n"); - exit(1); - } - if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { - fprintf(stderr, "Error: only root user should be given write permission on chroot directory\n"); - exit(1); - } - - // check subdirectories in rootdir - fs_check_chroot_subdir("dev", parentfd, 0); - fs_check_chroot_subdir("etc", parentfd, 1); - fs_check_chroot_subdir("proc", parentfd, 0); - fs_check_chroot_subdir("tmp", parentfd, 0); - fs_check_chroot_subdir("var/tmp", parentfd, 0); - - // there should be no checking on /etc/resolv.conf - // the file is replaced with the real /etc/resolv.conf anyway -#if 0 - if (asprintf(&name, "%s/etc/resolv.conf", rootdir) == -1) - errExit("asprintf"); - if (stat(name, &s) == 0) { - if (s.st_uid != 0) { - fprintf(stderr, "Error: chroot /etc/resolv.conf should be owned by root\n"); - exit(1); - } - } - else { - fprintf(stderr, "Error: chroot /etc/resolv.conf not found\n"); - exit(1); - } - // on Arch /etc/resolv.conf could be a symlink to /run/systemd/resolve/resolv.conf - // on Ubuntu 17.04 /etc/resolv.conf could be a symlink to /run/resolveconf/resolv.conf - if (is_link(name)) { - // check the link points in chroot - char *rname = realpath(name, NULL); - if (!rname || strncmp(rname, rootdir, strlen(rootdir)) != 0) { - fprintf(stderr, "Error: chroot /etc/resolv.conf is pointing outside chroot\n"); - exit(1); - } - } - free(name); -#endif - - // check x11 socket directory - if (getenv("FIREJAIL_X11")) - fs_check_chroot_subdir("tmp/.X11-unix", parentfd, 0); - - close(parentfd); +errout: + fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir); + exit(1); } // copy /etc/resolv.conf in chroot directory @@ -145,7 +70,7 @@ static void copy_resolvconf(int parentfd) { if (fstat(in, &src) == -1) errExit("fstat"); // try to detect if resolv.conf has been bind mounted into the chroot - // do nothing in this case in order to not truncate the real file + // do nothing in this case in order to not unlink the real file struct stat dst; if (fstatat(parentfd, "etc/resolv.conf", &dst, 0) == 0) { if (src.st_dev == dst.st_dev && src.st_ino == dst.st_ino) { @@ -155,7 +80,8 @@ static void copy_resolvconf(int parentfd) { } if (arg_debug) printf("Updating /etc/resolv.conf in chroot\n"); - int out = openat(parentfd, "etc/resolv.conf", O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); + unlinkat(parentfd, "etc/resolv.conf", 0); + int out = openat(parentfd, "etc/resolv.conf", O_CREAT|O_WRONLY|O_CLOEXEC, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); if (out == -1) errExit("open"); if (sendfile(out, in, NULL, src.st_size) == -1) @@ -164,18 +90,65 @@ static void copy_resolvconf(int parentfd) { close(out); } +// exit if error +static void check_subdir(int parentfd, const char *subdir, int check_writable) { + assert(subdir); + struct stat s; + if (fstatat(parentfd, subdir, &s, AT_SYMLINK_NOFOLLOW) != 0) { + fprintf(stderr, "Error: cannot find /%s in chroot directory\n", subdir); + exit(1); + } + if (!S_ISDIR(s.st_mode)) { + if (S_ISLNK(s.st_mode)) + fprintf(stderr, "Error: chroot /%s is a symbolic link\n", subdir); + else + fprintf(stderr, "Error: chroot /%s is not a directory\n", subdir); + exit(1); + } + if (s.st_uid != 0) { + fprintf(stderr, "Error: chroot /%s should owned by root\n", subdir); + exit(1); + } + if (check_writable && ((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { + fprintf(stderr, "Error: only root user should be given write permission on chroot /%s\n", subdir); + exit(1); + } +} + // chroot into an existing directory; mount existing /dev and update /etc/resolv.conf void fs_chroot(const char *rootdir) { assert(rootdir); + // fails if there is any symlink or if rootdir is not a directory int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); if (parentfd == -1) errExit("safe_fd"); + // rootdir has to be owned by root and is not allowed to be generally writable, + // this also excludes /tmp and friends + struct stat s; + if (fstat(parentfd, &s) == -1) + errExit("fstat"); + if (s.st_uid != 0) { + fprintf(stderr, "Error: chroot directory should be owned by root\n"); + exit(1); + } + if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { + fprintf(stderr, "Error: only root user should be given write permission on chroot directory\n"); + exit(1); + } + // check chroot subdirectories; /tmp/.X11-unix and /run are treated separately + check_subdir(parentfd, "dev", 0); + check_subdir(parentfd, "etc", 1); + check_subdir(parentfd, "proc", 0); + check_subdir(parentfd, "tmp", 0); + check_subdir(parentfd, "var/tmp", 0); // mount-bind a /dev in rootdir if (arg_debug) printf("Mounting /dev on chroot /dev\n"); - int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_CLOEXEC); + // open chroot /dev to get a file descriptor, + // then use this descriptor as a mount target + int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); if (fd == -1) errExit("open"); char *proc; @@ -189,7 +162,7 @@ void fs_chroot(const char *rootdir) { // mount a brand new proc filesystem if (arg_debug) printf("Mounting /proc filesystem on chroot /proc\n"); - fd = openat(parentfd, "proc", O_PATH|O_DIRECTORY|O_CLOEXEC); + fd = openat(parentfd, "proc", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); if (fd == -1) errExit("open"); if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) @@ -203,7 +176,8 @@ void fs_chroot(const char *rootdir) { if (getenv("FIREJAIL_X11")) { if (arg_debug) printf("Mounting /tmp/.X11-unix on chroot /tmp/.X11-unix\n"); - fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_CLOEXEC); + check_subdir(parentfd, "tmp/.X11-unix", 0); + fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); if (fd == -1) errExit("open"); if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) @@ -214,30 +188,22 @@ void fs_chroot(const char *rootdir) { close(fd); } - // update chroot resolv.conf - copy_resolvconf(parentfd); - // some older distros don't have a /run directory, create one by default - struct stat s; - if (fstatat(parentfd, "run", &s, AT_SYMLINK_NOFOLLOW) == 0) { - if (S_ISLNK(s.st_mode)) { - fprintf(stderr, "Error: chroot /run is a symbolic link\n"); - exit(1); - } - } - else if (mkdirat(parentfd, "run", 0755) == -1 && errno != EEXIST) + if (mkdirat(parentfd, "run", 0755) == -1 && errno != EEXIST) errExit("mkdir"); - fs_check_chroot_subdir("run", parentfd, 1); + check_subdir(parentfd, "run", 1); // create /run/firejail directory in chroot if (mkdirat(parentfd, RUN_FIREJAIL_DIR+1, 0755) == -1 && errno != EEXIST) errExit("mkdir"); + check_subdir(parentfd, RUN_FIREJAIL_DIR+1, 1); // create /run/firejail/lib directory in chroot if (mkdirat(parentfd, RUN_FIREJAIL_LIB_DIR+1, 0755) == -1 && errno != EEXIST) errExit("mkdir"); + check_subdir(parentfd, RUN_FIREJAIL_LIB_DIR+1, 1); // mount lib directory into the chroot - fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); + fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); if (fd == -1) errExit("open"); if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) @@ -250,8 +216,9 @@ void fs_chroot(const char *rootdir) { // create /run/firejail/mnt directory in chroot if (mkdirat(parentfd, RUN_MNT_DIR+1, 0755) == -1 && errno != EEXIST) errExit("mkdir"); + check_subdir(parentfd, RUN_MNT_DIR+1, 1); // mount the current mnt directory into the chroot - fd = openat(parentfd, RUN_MNT_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); + fd = openat(parentfd, RUN_MNT_DIR+1, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); if (fd == -1) errExit("open"); if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) @@ -261,6 +228,9 @@ void fs_chroot(const char *rootdir) { free(proc); close(fd); + // update chroot resolv.conf + copy_resolvconf(parentfd); + #ifdef HAVE_GCOV __gcov_flush(); #endif diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 80cf71caf..487803770 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -398,8 +398,9 @@ void fs_private_cache(void); void fs_mnt(const int enforce); // chroot.c +// returns resolved chroot directory path +char *fs_check_chroot_dir(const char *rootdir); // chroot into an existing directory; mount existing /dev and update /etc/resolv.conf -void fs_check_chroot_dir(const char *rootdir); void fs_chroot(const char *rootdir); // profile.c diff --git a/src/firejail/main.c b/src/firejail/main.c index e8664e914..5c83239ef 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -1659,35 +1659,21 @@ int main(int argc, char **argv) { fprintf(stderr, "Error: --chroot option is not available on Grsecurity systems\n"); exit(1); } - - + if (*(argv[i] + 9) == '\0') { + fprintf(stderr, "Error: invalid chroot option\n"); + exit(1); + } invalid_filename(argv[i] + 9, 0); // no globbing // extract chroot dirname - cfg.chrootdir = argv[i] + 9; + char *tmp = argv[i] + 9; // if the directory starts with ~, expand the home directory - if (*cfg.chrootdir == '~') { - char *tmp; - if (asprintf(&tmp, "%s%s", cfg.homedir, cfg.chrootdir + 1) == -1) + if (*(argv[i] + 9) == '~') { + if (asprintf(&tmp, "%s%s", cfg.homedir, argv[i] + 10) == -1) errExit("asprintf"); - cfg.chrootdir = tmp; } - - if (strstr(cfg.chrootdir, "..") || is_link(cfg.chrootdir)) { - fprintf(stderr, "Error: invalid chroot directory %s\n", cfg.chrootdir); - return 1; - } - - // check chroot dirname exists, don't allow "--chroot=/" - char *rpath = realpath(cfg.chrootdir, NULL); - if (rpath == NULL || strcmp(rpath, "/") == 0) { - fprintf(stderr, "Error: invalid chroot directory\n"); - exit(1); - } - cfg.chrootdir = rpath; - - // check chroot directory structure - fs_check_chroot_dir(cfg.chrootdir); + // check chroot directory + cfg.chrootdir = fs_check_chroot_dir(tmp); } else exit_err_feature("chroot"); -- cgit v1.2.3-70-g09d2 From b2a91ca47eb37581a680004411f8fa98e03ddde1 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Tue, 1 Oct 2019 18:25:00 +0200 Subject: simplify chroot option parsing this is a partial revert, back to the original code --- src/firejail/chroot.c | 19 ++++++++++--------- src/firejail/firejail.h | 3 +-- src/firejail/main.c | 16 +++++++++------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/firejail/chroot.c b/src/firejail/chroot.c index 8a57dee35..f5bb11a76 100644 --- a/src/firejail/chroot.c +++ b/src/firejail/chroot.c @@ -30,17 +30,17 @@ #endif -// exit if error, return resolved chroot path -char *fs_check_chroot_dir(const char *rootdir) { +// exit if error +void fs_check_chroot_dir(void) { EUID_ASSERT(); - assert(rootdir); - if (strstr(rootdir, "..") || - is_link(rootdir) || - !is_dir(rootdir)) + assert(cfg.chrootdir); + if (strstr(cfg.chrootdir, "..") || + is_link(cfg.chrootdir) || + !is_dir(cfg.chrootdir)) goto errout; // check chroot dirname exists, chrooting into the root directory is not allowed - char *rpath = realpath(rootdir, NULL); + char *rpath = realpath(cfg.chrootdir, NULL); if (rpath == NULL || strcmp(rpath, "/") == 0) goto errout; @@ -52,10 +52,11 @@ char *fs_check_chroot_dir(const char *rootdir) { exit(1); } free(overlay); - return rpath; + cfg.chrootdir = rpath; + return; errout: - fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir); + fprintf(stderr, "Error: invalid chroot directory %s\n", cfg.chrootdir); exit(1); } diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 487803770..fdbeb4691 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -398,9 +398,8 @@ void fs_private_cache(void); void fs_mnt(const int enforce); // chroot.c -// returns resolved chroot directory path -char *fs_check_chroot_dir(const char *rootdir); // chroot into an existing directory; mount existing /dev and update /etc/resolv.conf +void fs_check_chroot_dir(void); void fs_chroot(const char *rootdir); // profile.c diff --git a/src/firejail/main.c b/src/firejail/main.c index 5c83239ef..cbe3292ba 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -1659,21 +1659,23 @@ int main(int argc, char **argv) { fprintf(stderr, "Error: --chroot option is not available on Grsecurity systems\n"); exit(1); } - if (*(argv[i] + 9) == '\0') { + // extract chroot dirname + cfg.chrootdir = argv[i] + 9; + if (*cfg.chrootdir == '\0') { fprintf(stderr, "Error: invalid chroot option\n"); exit(1); } - invalid_filename(argv[i] + 9, 0); // no globbing + invalid_filename(cfg.chrootdir, 0); // no globbing - // extract chroot dirname - char *tmp = argv[i] + 9; // if the directory starts with ~, expand the home directory - if (*(argv[i] + 9) == '~') { - if (asprintf(&tmp, "%s%s", cfg.homedir, argv[i] + 10) == -1) + if (*cfg.chrootdir == '~') { + char *tmp; + if (asprintf(&tmp, "%s%s", cfg.homedir, cfg.chrootdir + 1) == -1) errExit("asprintf"); + cfg.chrootdir = tmp; } // check chroot directory - cfg.chrootdir = fs_check_chroot_dir(tmp); + fs_check_chroot_dir(); } else exit_err_feature("chroot"); -- cgit v1.2.3-70-g09d2 From cba04e05bed19bb319eedc90b9cbd654f43a05a7 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Tue, 1 Oct 2019 17:11:31 +0000 Subject: improve enforce_filters warning added an additional newline in order to keep it visually separate from other unrelated error messages --- src/firejail/sandbox.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c index 288726d22..80b595a9f 100644 --- a/src/firejail/sandbox.c +++ b/src/firejail/sandbox.c @@ -578,7 +578,7 @@ static void enforce_filters(void) { force_nonewprivs = 1; // disable all capabilities - fmessage("\n** Warning: dropping all Linux capabilities **\n"); + fmessage("\n** Warning: dropping all Linux capabilities **\n\n"); arg_caps_drop_all = 1; // drop all supplementary groups; /etc/group file inside chroot -- cgit v1.2.3-70-g09d2