From 515f3440439fa8c70e5e517b529cdc994845f6ec Mon Sep 17 00:00:00 2001 From: smitsohu Date: Mon, 17 Aug 2020 16:38:47 +0200 Subject: hardening: run plugins with dumpable flag cleared the kernel clears the dumpable flag if a user has no read permission on an executable and it is owned by another user; I omitted faudit, fbuilder and ftee for now as they are not used to configure the sandbox itself, and as this commit is going to complicate debugging efforts to some extent --- Makefile.in | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Makefile.in b/Makefile.in index 8cbba12e9..f3d1b3ad0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -18,15 +18,16 @@ HAVE_SUID=@HAVE_SUID@ all: all_items man filters APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats -SBOX_APPS = src/faudit/faudit src/fbuilder/fbuilder src/fcopy/fcopy src/fldd/fldd src/fnet/fnet src/fnetfilter/fnetfilter src/ftee/ftee +SBOX_APPS = src/faudit/faudit src/fbuilder/fbuilder src/ftee/ftee +SBOX_APPS_NON_DUMPABLE = src/fcopy/fcopy src/fldd/fldd src/fnet/fnet src/fnetfilter/fnetfilter MYDIRS = src/lib MYLIBS = src/libpostexecseccomp/libpostexecseccomp.so src/libtrace/libtrace.so src/libtracelog/libtracelog.so MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 firejail-users.5 ifeq ($(HAVE_SECCOMP),-DHAVE_SECCOMP) -SBOX_APPS += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp +SBOX_APPS_NON_DUMPABLE += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp SECCOMP_FILTERS = seccomp seccomp.debug seccomp.32 seccomp.block_secondary seccomp.mdwx seccomp.mdwx.32 endif -ALL_ITEMS = $(APPS) $(SBOX_APPS) $(MYLIBS) +ALL_ITEMS = $(APPS) $(SBOX_APPS) $(SBOX_APPS_NON_DUMPABLE) $(MYLIBS) .PHONY: all_items $(ALL_ITEMS) all_items: $(ALL_ITEMS) @@ -43,7 +44,7 @@ $(MANPAGES): $(wildcard src/man/*.txt) man: $(MANPAGES) -filters: $(SECCOMP_FILTERS) $(SBOX_APPS) +filters: $(SECCOMP_FILTERS) $(SBOX_APPS_NON_DUMPABLE) ifeq ($(HAVE_SECCOMP),-DHAVE_SECCOMP) seccomp: src/fseccomp/fseccomp src/fsec-optimize/fsec-optimize src/fseccomp/fseccomp default seccomp @@ -106,7 +107,10 @@ endif install -m 0755 -d $(DESTDIR)$(libdir)/firejail install -m 0644 -t $(DESTDIR)$(libdir)/firejail $(MYLIBS) $(SECCOMP_FILTERS) src/firecfg/firecfg.config install -m 0755 -t $(DESTDIR)$(libdir)/firejail $(SBOX_APPS) + # non-dumpable plugins + install -m 0711 -t $(DESTDIR)$(libdir)/firejail $(SBOX_APPS_NON_DUMPABLE) ifeq ($(HAVE_CONTRIB_INSTALL),yes) + # contrib scripts install -m 0755 -t $(DESTDIR)$(libdir)/firejail contrib/*.py contrib/*.sh # vim syntax install -m 0755 -d $(DESTDIR)$(datarootdir)/vim/vimfiles/ftdetect -- cgit v1.2.3-54-g00ecf From 7d0800682ab3a74e3d463836cd2ca5cd697d542c Mon Sep 17 00:00:00 2001 From: smitsohu Date: Mon, 17 Aug 2020 16:40:52 +0200 Subject: various x11 xorg enhancements 1) copy xauth binary into the sandbox and set mode to 0711, so it runs with cleared dumpable flag for unprivileged users 2) run xauth in an sbox sandbox 3) generate Xauthority file in runtime directory instead of /tmp; this way xauth is able to connect to the X11 socket even if the abstract socket doesn't exist, for example because a new network namespace was instantiated --- src/firejail/x11.c | 173 ++++++++++++++++++++------------------------------ src/include/rundefs.h | 5 +- 2 files changed, 72 insertions(+), 106 deletions(-) diff --git a/src/firejail/x11.c b/src/firejail/x11.c index 98ac184d9..1ecb688af 100644 --- a/src/firejail/x11.c +++ b/src/firejail/x11.c @@ -34,7 +34,7 @@ #include #ifndef O_PATH -# define O_PATH 010000000 +#define O_PATH 010000000 #endif @@ -1129,28 +1129,17 @@ void x11_start(int argc, char **argv) { } #endif -// Porting notes: -// -// 1. merge #1100 from zackw: -// Attempting to run xauth -f directly on a file in /run/firejail/mnt/ directory fails on Debian 8 -// with this message: -// xauth: timeout in locking authority file /run/firejail/mnt/sec.Xauthority-Qt5Mu4 -// Failed to create untrusted X cookie: xauth: exit 1 -// For this reason we run xauth on a file in a tmpfs filesystem mounted on /tmp. This was -// a partial merge. -// -// 2. Since we cannot deal with the TOCTOU condition when mounting .Xauthority in user home -// directory, we need to make sure /usr/bin/xauth executable is the real thing, and not -// something picked up on $PATH. -// -// 3. If for any reason xauth command fails, we exit the sandbox. On Debian 8 this happens -// when using a network namespace. Somehow, xauth tries to connect to the abstract socket, -// and it fails because of the network namespace - it should try to connect to the regular -// Unix socket! If we ignore the fail condition, the program will be started on X server without -// the security extension loaded. + void x11_xorg(void) { #ifdef HAVE_X11 + // get DISPLAY env + char *display = getenv("DISPLAY"); + if (!display) { + fputs("Error: --x11=xorg requires an 'outer' X11 server to use.\n", stderr); + exit(1); + } + // check xauth utility is present in the system struct stat s; if (stat("/usr/bin/xauth", &s) == -1) { @@ -1160,26 +1149,27 @@ void x11_xorg(void) { fprintf(stderr, " Fedora: sudo dnf install xorg-x11-xauth\n"); exit(1); } - if (s.st_uid != 0 && s.st_gid != 0) { + if ((s.st_uid != 0 && s.st_gid != 0) || (s.st_mode & S_IWOTH)) { fprintf(stderr, "Error: invalid /usr/bin/xauth executable\n"); exit(1); } - - // get DISPLAY env - char *display = getenv("DISPLAY"); - if (!display) { - fputs("Error: --x11=xorg requires an 'outer' X11 server to use.\n", stderr); + if (s.st_size > 1024 * 1024) { + fprintf(stderr, "Error: /usr/bin/xauth executable is too large\n"); exit(1); } - - // temporarily mount a tempfs on top of /tmp directory - if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=1777,gid=0") < 0) - errExit("mounting /tmp"); - - // create the temporary .Xauthority file + // copy /usr/bin/xauth in the sandbox and set mode to 0711 + // users are not able to trace the running xauth this way if (arg_debug) - printf("Generating a new .Xauthority file\n"); - char tmpfname[] = "/tmp/.tmpXauth-XXXXXX"; + printf("Copying /usr/bin/xauth to %s\n", RUN_XAUTH_FILE); + if (copy_file("/usr/bin/xauth", RUN_XAUTH_FILE, 0, 0, 0711)) { + fprintf(stderr, "Error: cannot copy /usr/bin/xauth executable\n"); + exit(1); + } + + fmessage("Generating a new .Xauthority file\n"); + mkdir_attr(RUN_XAUTHORITY_SEC_DIR, 0700, getuid(), getgid()); + // create new Xauthority file in RUN_XAUTHORITY_SEC_DIR + char tmpfname[] = RUN_XAUTHORITY_SEC_DIR "/.Xauth-XXXXXX"; int fd = mkstemp(tmpfname); if (fd == -1) { fprintf(stderr, "Error: cannot create .Xauthority file\n"); @@ -1189,64 +1179,17 @@ void x11_xorg(void) { errExit("chown"); close(fd); - pid_t child = fork(); - if (child < 0) - errExit("fork"); - if (child == 0) { - drop_privs(1); - clearenv(); -#ifdef HAVE_GCOV - __gcov_flush(); -#endif - if (arg_debug) { - execlp("/usr/bin/xauth", "/usr/bin/xauth", "-v", "-f", tmpfname, - "generate", display, "MIT-MAGIC-COOKIE-1", "untrusted", NULL); - } - else { - execlp("/usr/bin/xauth", "/usr/bin/xauth", "-f", tmpfname, - "generate", display, "MIT-MAGIC-COOKIE-1", "untrusted", NULL); - } - - _exit(127); - } - - // wait for the xauth process to finish - int status; - if (waitpid(child, &status, 0) != child) - errExit("waitpid"); - if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { - /* success */ - } - else if (WIFEXITED(status)) { - fprintf(stderr, "Failed to create untrusted X cookie: xauth: exit %d\n", - WEXITSTATUS(status)); - exit(1); - } - else if (WIFSIGNALED(status)) { - fprintf(stderr, "Failed to create untrusted X cookie: xauth: %s\n", - strsignal(WTERMSIG(status))); - exit(1); - } - else { - fprintf(stderr, "Failed to create untrusted X cookie: " - "xauth: un-decodable exit status %04x\n", status); - exit(1); - } - - // move the temporary file in RUN_XAUTHORITY_SEC_FILE in order to have it deleted - // automatically when the sandbox is closed (rename doesn't work) + // run xauth if (arg_debug) - printf("Copying the new .Xauthority file\n"); - copy_file_from_user_to_root(tmpfname, RUN_XAUTHORITY_SEC_FILE, getuid(), getgid(), 0600); - - /* coverity[toctou] */ - unlink(tmpfname); - umount("/tmp"); - - // mount RUN_XAUTHORITY_SEC_FILE noexec, nodev, nosuid - fs_remount(RUN_XAUTHORITY_SEC_FILE, MOUNT_NOEXEC, 0); + sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 8, RUN_XAUTH_FILE, "-v", "-f", tmpfname, + "generate", display, "MIT-MAGIC-COOKIE-1", "untrusted"); + else + sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 7, RUN_XAUTH_FILE, "-f", tmpfname, + "generate", display, "MIT-MAGIC-COOKIE-1", "untrusted"); + // remove xauth copy + unlink(RUN_XAUTH_FILE); - // Ensure there is already a file in the usual location, so that bind-mount below will work. + // ensure there is already a file ~/.Xauthority, so that bind-mount below will work. char *dest; if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1) errExit("asprintf"); @@ -1257,13 +1200,12 @@ void x11_xorg(void) { exit(1); } } - - // get a file descriptor for .Xauthority - fd = safe_fd(dest, O_PATH|O_NOFOLLOW|O_CLOEXEC); - if (fd == -1) + // get a file descriptor for ~/.Xauthority + int dst = safe_fd(dest, O_PATH|O_NOFOLLOW|O_CLOEXEC); + if (dst == -1) errExit("safe_fd"); // check if the actual mount destination is a user owned regular file - if (fstat(fd, &s) == -1) + if (fstat(dst, &s) == -1) errExit("fstat"); if (!S_ISREG(s.st_mode) || s.st_uid != getuid()) { if (S_ISLNK(s.st_mode)) @@ -1274,31 +1216,49 @@ void x11_xorg(void) { } // preserve a read-only mount struct statvfs vfs; - if (fstatvfs(fd, &vfs) == -1) + if (fstatvfs(dst, &vfs) == -1) errExit("fstatvfs"); if ((vfs.f_flag & MS_RDONLY) == MS_RDONLY) - fs_remount(RUN_XAUTHORITY_SEC_FILE, MOUNT_READONLY, 0); + fs_remount(RUN_XAUTHORITY_SEC_DIR, MOUNT_READONLY, 0); + + // always mounting the new Xauthority file noexec,nodev,nosuid + fs_remount(RUN_XAUTHORITY_SEC_DIR, MOUNT_NOEXEC, 0); + + // get a file descriptor for the new Xauthority file + int src = safe_fd(tmpfname, O_PATH|O_NOFOLLOW|O_CLOEXEC); + if (src == -1) + errExit("safe_fd"); + if (fstat(src, &s) == -1) + errExit("fstat"); + if (!S_ISREG(s.st_mode)) { + errno = EPERM; + errExit("mounting Xauthority file"); + } // mount via the link in /proc/self/fd if (arg_debug) - printf("Mounting %s on %s\n", RUN_XAUTHORITY_SEC_FILE, dest); - char *proc; - if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) + printf("Mounting %s on %s\n", tmpfname, dest); + char *proc_src, *proc_dst; + if (asprintf(&proc_src, "/proc/self/fd/%d", src) == -1) + errExit("asprintf"); + if (asprintf(&proc_dst, "/proc/self/fd/%d", dst) == -1) errExit("asprintf"); - if (mount(RUN_XAUTHORITY_SEC_FILE, proc, "none", MS_BIND, "mode=0600") == -1) { + if (mount(proc_src, proc_dst, NULL, MS_BIND, NULL) == -1) { fprintf(stderr, "Error: cannot mount the new .Xauthority file\n"); exit(1); } - free(proc); - close(fd); // check /proc/self/mountinfo to confirm the mount is ok MountData *mptr = get_last_mount(); if (strcmp(mptr->dir, dest) != 0 || strcmp(mptr->fstype, "tmpfs") != 0) errLogExit("invalid .Xauthority mount"); + free(proc_src); + free(proc_dst); + close(src); + close(dst); ASSERT_PERMS(dest, getuid(), getgid(), 0600); - // blacklist .Xauthority file if it is not masked already + // blacklist user .Xauthority file if it is not masked already char *envar = getenv("XAUTHORITY"); if (envar) { char *rp = realpath(envar, NULL); @@ -1312,6 +1272,11 @@ void x11_xorg(void) { if (setenv("XAUTHORITY", dest, 1) < 0) errExit("setenv"); free(dest); + + // mask RUN_XAUTHORITY_SEC_DIR + if (mount("tmpfs", RUN_XAUTHORITY_SEC_DIR, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME, "mode=755,gid=0") < 0) + errExit("mounting tmpfs"); + fs_logger2("tmpfs", RUN_XAUTHORITY_SEC_DIR); #endif } diff --git a/src/include/rundefs.h b/src/include/rundefs.h index f8bcdec52..d56623907 100644 --- a/src/include/rundefs.h +++ b/src/include/rundefs.h @@ -99,8 +99,9 @@ #define RUN_WHITELIST_SHARE_DIR RUN_MNT_DIR "/orig-share" #define RUN_WHITELIST_MODULE_DIR RUN_MNT_DIR "/orig-module" -#define RUN_XAUTHORITY_FILE RUN_MNT_DIR "/.Xauthority" -#define RUN_XAUTHORITY_SEC_FILE RUN_MNT_DIR "/sec.Xauthority" +#define RUN_XAUTHORITY_FILE RUN_MNT_DIR "/.Xauthority" // private options +#define RUN_XAUTH_FILE RUN_MNT_DIR "/xauth" // x11=xorg +#define RUN_XAUTHORITY_SEC_DIR RUN_MNT_DIR "/.sec.Xauthority" // x11=xorg #define RUN_ASOUNDRC_FILE RUN_MNT_DIR "/.asoundrc" #define RUN_HOSTNAME_FILE RUN_MNT_DIR "/hostname" #define RUN_HOSTS_FILE RUN_MNT_DIR "/hosts" -- cgit v1.2.3-54-g00ecf From 9e3b7b90cf9aad35fc8db2eabdeb9e1ed038acea Mon Sep 17 00:00:00 2001 From: smitsohu Date: Mon, 17 Aug 2020 17:08:43 +0200 Subject: add dumpable warnings --- src/fcopy/main.c | 6 ++++++ src/firejail/main.c | 4 ++++ src/firejail/sbox.c | 1 + src/fldd/main.c | 6 ++++++ src/fnet/main.c | 16 ++++++++++------ src/fnetfilter/main.c | 6 +++++- src/fsec-optimize/fsec_optimize.h | 1 + src/fsec-optimize/main.c | 5 +++++ src/fsec-print/fsec_print.h | 1 + src/fsec-print/main.c | 5 +++++ src/fseccomp/fseccomp.h | 1 + src/fseccomp/main.c | 15 ++++++++++----- src/include/common.h | 3 +++ 13 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/fcopy/main.c b/src/fcopy/main.c index 83d9c17e6..bda7e2f1b 100644 --- a/src/fcopy/main.c +++ b/src/fcopy/main.c @@ -23,6 +23,7 @@ #include #include #include +#include #if HAVE_SELINUX #include @@ -411,6 +412,11 @@ int main(int argc, char **argv) { exit(1); } +#ifdef WARN_DUMPABLE + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + fprintf(stderr, "Error fcopy: I am dumpable\n"); +#endif + // trim trailing chars if (src[strlen(src) - 1] == '/') src[strlen(src) - 1] = '\0'; diff --git a/src/firejail/main.c b/src/firejail/main.c index 79e39b669..4aa5311a2 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -1275,6 +1275,10 @@ int main(int argc, char **argv, char **envp) { } EUID_ASSERT(); +#ifdef WARN_DUMPABLE + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid()) + fprintf(stderr, "Error: Firejail is dumpable\n"); +#endif // check for force-nonewprivs in /etc/firejail/firejail.config file if (checkcfg(CFG_FORCE_NONEWPRIVS)) diff --git a/src/firejail/sbox.c b/src/firejail/sbox.c index 99f11a246..cf3d3aeed 100644 --- a/src/firejail/sbox.c +++ b/src/firejail/sbox.c @@ -48,6 +48,7 @@ static int sbox_do_exec_v(unsigned filtermask, char * const arg[]) { if (cfg.seccomp_error_action) if (asprintf(&new_environment[env_index++], "FIREJAIL_SECCOMP_ERROR_ACTION=%s", cfg.seccomp_error_action) == -1) errExit("asprintf"); + new_environment[env_index++] = "FIREJAIL_PLUGIN="; // always set if (filtermask & SBOX_STDIN_FROM_FILE) { int fd; diff --git a/src/fldd/main.c b/src/fldd/main.c index dd22e601e..567f6c566 100644 --- a/src/fldd/main.c +++ b/src/fldd/main.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -302,6 +303,11 @@ printf("\n"); return 0; } +#ifdef WARN_DUMPABLE + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + fprintf(stderr, "Error fldd: I am dumpable\n"); +#endif + // check program access if (access(argv[1], R_OK)) { fprintf(stderr, "Error fldd: cannot access %s\n", argv[1]); diff --git a/src/fnet/main.c b/src/fnet/main.c index 95e12164e..22879b8ce 100644 --- a/src/fnet/main.c +++ b/src/fnet/main.c @@ -21,6 +21,7 @@ #include #include #include +#include int arg_quiet = 0; @@ -64,16 +65,19 @@ printf("\n"); usage(); return 1; } - - char *quiet = getenv("FIREJAIL_QUIET"); - if (quiet && strcmp(quiet, "yes") == 0) - arg_quiet = 1; - if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") ==0) { usage(); return 0; } - else if (argc == 3 && strcmp(argv[1], "ifup") == 0) { +#ifdef WARN_DUMPABLE + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + fprintf(stderr, "Error fnet: I am dumpable\n"); +#endif + char *quiet = getenv("FIREJAIL_QUIET"); + if (quiet && strcmp(quiet, "yes") == 0) + arg_quiet = 1; + + if (argc == 3 && strcmp(argv[1], "ifup") == 0) { net_if_up(argv[2]); } else if (argc == 2 && strcmp(argv[1], "printif") == 0) { diff --git a/src/fnetfilter/main.c b/src/fnetfilter/main.c index 8124beb1a..bac60cbec 100644 --- a/src/fnetfilter/main.c +++ b/src/fnetfilter/main.c @@ -18,6 +18,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../include/common.h" +#include #define MAXBUF 4098 #define MAXARGS 16 @@ -180,7 +181,10 @@ printf("\n"); usage(); return 1; } - +#ifdef WARN_DUMPABLE + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + fprintf(stderr, "Error fnetfilter: I am dumpable\n"); +#endif char *destfile = (argc == 3)? argv[2]: argv[1]; char *command = (argc == 3)? argv[1]: NULL; //printf("command %s\n", command); diff --git a/src/fsec-optimize/fsec_optimize.h b/src/fsec-optimize/fsec_optimize.h index 211111641..034fde2ac 100644 --- a/src/fsec-optimize/fsec_optimize.h +++ b/src/fsec-optimize/fsec_optimize.h @@ -22,6 +22,7 @@ #include "../include/common.h" #include "../include/seccomp.h" #include +#include // optimize.c struct sock_filter *duplicate(struct sock_filter *filter, int entries); diff --git a/src/fsec-optimize/main.c b/src/fsec-optimize/main.c index 416d85b88..4da110583 100644 --- a/src/fsec-optimize/main.c +++ b/src/fsec-optimize/main.c @@ -44,6 +44,11 @@ printf("\n"); return 0; } +#ifdef WARN_DUMPABLE + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + fprintf(stderr, "Error fsec-optimize: I am dumpable\n"); +#endif + char *fname = argv[1]; // open input file diff --git a/src/fsec-print/fsec_print.h b/src/fsec-print/fsec_print.h index 337199288..9d17e3f18 100644 --- a/src/fsec-print/fsec_print.h +++ b/src/fsec-print/fsec_print.h @@ -23,6 +23,7 @@ #include "../include/seccomp.h" #include "../include/syscall.h" #include +#include // print.c void print(struct sock_filter *filter, int entries); diff --git a/src/fsec-print/main.c b/src/fsec-print/main.c index ade45c881..858289767 100644 --- a/src/fsec-print/main.c +++ b/src/fsec-print/main.c @@ -61,6 +61,11 @@ printf("\n"); return 0; } +#ifdef WARN_DUMPABLE + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + fprintf(stderr, "Error fsec-print: I am dumpable\n"); +#endif + char *fname = argv[1]; // open input file diff --git a/src/fseccomp/fseccomp.h b/src/fseccomp/fseccomp.h index e8dd083b6..e40999938 100644 --- a/src/fseccomp/fseccomp.h +++ b/src/fseccomp/fseccomp.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "../include/common.h" #include "../include/syscall.h" diff --git a/src/fseccomp/main.c b/src/fseccomp/main.c index 892a88e25..6b7800f35 100644 --- a/src/fseccomp/main.c +++ b/src/fseccomp/main.c @@ -64,6 +64,15 @@ printf("\n"); usage(); return 1; } + if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") ==0) { + usage(); + return 0; + } + +#ifdef WARN_DUMPABLE + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + fprintf(stderr, "Error fseccomp: I am dumpable\n"); +#endif char *quiet = getenv("FIREJAIL_QUIET"); if (quiet && strcmp(quiet, "yes") == 0) @@ -81,11 +90,7 @@ printf("\n"); } } - if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") ==0) { - usage(); - return 0; - } - else if (argc == 2 && strcmp(argv[1], "debug-syscalls") == 0) + if (argc == 2 && strcmp(argv[1], "debug-syscalls") == 0) syscall_print(); else if (argc == 2 && strcmp(argv[1], "debug-syscalls32") == 0) syscall_print_32(); diff --git a/src/include/common.h b/src/include/common.h index c65ba0d55..025f3c247 100644 --- a/src/include/common.h +++ b/src/include/common.h @@ -34,6 +34,9 @@ #define errExit(msg) do { char msgout[500]; snprintf(msgout, 500, "Error %s: %s:%d %s", msg, __FILE__, __LINE__, __FUNCTION__); perror(msgout); exit(1);} while (0) +// check if processes run with dumpable flag set +#define WARN_DUMPABLE + // macro to print ip addresses in a printf statement #define PRINT_IP(A) \ ((int) (((A) >> 24) & 0xFF)), ((int) (((A) >> 16) & 0xFF)), ((int) (((A) >> 8) & 0xFF)), ((int) ( (A) & 0xFF)) -- cgit v1.2.3-54-g00ecf From 21918f6d92f9261cc45f208ac407819980d4a59c Mon Sep 17 00:00:00 2001 From: smitsohu Date: Mon, 17 Aug 2020 22:10:07 +0200 Subject: cleanup --- src/fcopy/main.c | 2 +- src/fldd/main.c | 2 +- src/fnet/main.c | 2 +- src/fnetfilter/main.c | 2 +- src/fsec-optimize/main.c | 1 + src/fsec-print/main.c | 2 +- src/fseccomp/main.c | 1 + 7 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/fcopy/main.c b/src/fcopy/main.c index bda7e2f1b..67237b4ea 100644 --- a/src/fcopy/main.c +++ b/src/fcopy/main.c @@ -413,7 +413,7 @@ int main(int argc, char **argv) { } #ifdef WARN_DUMPABLE - if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid()) fprintf(stderr, "Error fcopy: I am dumpable\n"); #endif diff --git a/src/fldd/main.c b/src/fldd/main.c index 567f6c566..d68504f6b 100644 --- a/src/fldd/main.c +++ b/src/fldd/main.c @@ -304,7 +304,7 @@ printf("\n"); } #ifdef WARN_DUMPABLE - if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid()) fprintf(stderr, "Error fldd: I am dumpable\n"); #endif diff --git a/src/fnet/main.c b/src/fnet/main.c index 22879b8ce..f6316a7fe 100644 --- a/src/fnet/main.c +++ b/src/fnet/main.c @@ -70,7 +70,7 @@ printf("\n"); return 0; } #ifdef WARN_DUMPABLE - if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid()) fprintf(stderr, "Error fnet: I am dumpable\n"); #endif char *quiet = getenv("FIREJAIL_QUIET"); diff --git a/src/fnetfilter/main.c b/src/fnetfilter/main.c index bac60cbec..1ca35ab56 100644 --- a/src/fnetfilter/main.c +++ b/src/fnetfilter/main.c @@ -182,7 +182,7 @@ printf("\n"); return 1; } #ifdef WARN_DUMPABLE - if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid()) fprintf(stderr, "Error fnetfilter: I am dumpable\n"); #endif char *destfile = (argc == 3)? argv[2]: argv[1]; diff --git a/src/fsec-optimize/main.c b/src/fsec-optimize/main.c index 4da110583..fb13eeca8 100644 --- a/src/fsec-optimize/main.c +++ b/src/fsec-optimize/main.c @@ -45,6 +45,7 @@ printf("\n"); } #ifdef WARN_DUMPABLE + // check FIREJAIL_PLUGIN in order to not print a warning during make if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) fprintf(stderr, "Error fsec-optimize: I am dumpable\n"); #endif diff --git a/src/fsec-print/main.c b/src/fsec-print/main.c index 858289767..d1f056e47 100644 --- a/src/fsec-print/main.c +++ b/src/fsec-print/main.c @@ -62,7 +62,7 @@ printf("\n"); } #ifdef WARN_DUMPABLE - if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid()) fprintf(stderr, "Error fsec-print: I am dumpable\n"); #endif diff --git a/src/fseccomp/main.c b/src/fseccomp/main.c index 6b7800f35..13eb3dfe7 100644 --- a/src/fseccomp/main.c +++ b/src/fseccomp/main.c @@ -70,6 +70,7 @@ printf("\n"); } #ifdef WARN_DUMPABLE + // check FIREJAIL_PLUGIN in order to not print a warning during make if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 1 && getuid() && getenv("FIREJAIL_PLUGIN")) fprintf(stderr, "Error fseccomp: I am dumpable\n"); #endif -- cgit v1.2.3-54-g00ecf From 66c398e34cdf39c5ad9aa06bd022ce8a518bcbc4 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Wed, 19 Aug 2020 00:39:20 +0200 Subject: refactor ls.c and prepare for new --cat option --- src/firejail/firejail.h | 3 + src/firejail/ls.c | 163 +++++++++++++++++++++++++++++------------------- 2 files changed, 103 insertions(+), 63 deletions(-) diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 54a1023ab..56bdd7ec3 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -729,10 +729,13 @@ void x11_xorg(void); // ls.c enum { SANDBOX_FS_LS = 0, + SANDBOX_FS_CAT, SANDBOX_FS_GET, SANDBOX_FS_PUT, SANDBOX_FS_MAX // this should always be the last entry }; +void ls(const char *path); +void cat(const char *path); void sandboxfs(int op, pid_t pid, const char *path1, const char *path2); // checkcfg.c diff --git a/src/firejail/ls.c b/src/firejail/ls.c index aa33d838b..7c7f8b75f 100644 --- a/src/firejail/ls.c +++ b/src/firejail/ls.c @@ -34,18 +34,12 @@ static uid_t c_uid = 0; static char *c_uid_name = NULL; -static void print_file_or_dir(const char *path, const char *fname, int separator) { +static void print_file_or_dir(const char *path, const char *fname) { assert(fname); char *name; - if (separator) { - if (asprintf(&name, "%s/%s", path, fname) == -1) - errExit("asprintf"); - } - else { - if (asprintf(&name, "%s%s", path, fname) == -1) - errExit("asprintf"); - } + if (asprintf(&name, "%s/%s", path, fname) == -1) + errExit("asprintf"); struct stat s; if (stat(name, &s) == -1) { @@ -178,13 +172,75 @@ static void print_directory(const char *path) { errExit("scandir"); else { for (i = 0; i < n; i++) { - print_file_or_dir(path, namelist[i]->d_name, 0); + print_file_or_dir(path, namelist[i]->d_name); free(namelist[i]); } } free(namelist); } +void ls(const char *path) { + EUID_ASSERT(); + assert(path); + + char *rp = realpath(path, NULL); + if (!rp || access(rp, R_OK) == -1) { + fprintf(stderr, "Error: cannot access %s\n", path); + exit(1); + } + if (arg_debug) + printf("ls %s\n", rp); + + // list directory contents + struct stat s; + if (stat(rp, &s) == -1) { + fprintf(stderr, "Error: cannot access %s\n", rp); + exit(1); + } + if (S_ISDIR(s.st_mode)) + print_directory(rp); + else { + char *split = strrchr(rp, '/'); + if (split) { + *split = '\0'; + char *rp2 = split + 1; + if (arg_debug) + printf("path %s, file %s\n", rp, rp2); + print_file_or_dir(rp, rp2); + } + } + free(rp); +} + +void cat(const char *path) { + EUID_ASSERT(); + assert(path); + + if (arg_debug) + printf("cat %s\n", path); + FILE *fp = fopen(path, "r"); + if (!fp) { + fprintf(stderr, "Error: cannot read %s\n", path); + exit(1); + } + int fd = fileno(fp); + if (fd == -1) + errExit("fileno"); + struct stat s; + if (fstat(fd, &s) == -1) + errExit("fstat"); + if (!S_ISREG(s.st_mode)) { + fprintf(stderr, "Error: %s is not a regular file\n", path); + exit(1); + } + + int c; + while ((c = fgetc(fp)) != EOF) + fputc(c, stdout); + fflush(stdout); + fclose(fp); +} + char *expand_path(const char *path) { char *fname = NULL; if (*path == '/') { @@ -219,14 +275,14 @@ void sandboxfs(int op, pid_t pid, const char *path1, const char *path2) { check_join_permission(pid); // expand paths - char *fname1 = expand_path(path1);; + char *fname1 = expand_path(path1); char *fname2 = NULL; if (path2 != NULL) { fname2 = expand_path(path2); } if (arg_debug) { printf("file1 %s\n", fname1); - printf("file2 %s\n", fname2); + printf("file2 %s\n", fname2 ? fname2 : "(null)"); } // sandbox root directory @@ -234,57 +290,36 @@ void sandboxfs(int op, pid_t pid, const char *path1, const char *path2) { if (asprintf(&rootdir, "/proc/%d/root", pid) == -1) errExit("asprintf"); - if (op == SANDBOX_FS_LS) { - EUID_ROOT(); - // chroot - if (chroot(rootdir) < 0) - errExit("chroot"); - if (chdir("/") < 0) - errExit("chdir"); - - // drop privileges - drop_privs(0); - - // check access - if (access(fname1, R_OK) == -1) { - fprintf(stderr, "Error: Cannot access %s\n", fname1); - exit(1); - } - /* coverity[toctou] */ - char *rp = realpath(fname1, NULL); - if (!rp) { - fprintf(stderr, "Error: Cannot access %s\n", fname1); - exit(1); - } - if (arg_debug) - printf("realpath %s\n", rp); - + if (op == SANDBOX_FS_LS || op == SANDBOX_FS_CAT) { + pid_t child = fork(); + if (child < 0) + errExit("fork"); + if (child == 0) { + EUID_ROOT(); + // chroot + if (chroot(rootdir) < 0) + errExit("chroot"); + if (chdir("/") < 0) + errExit("chdir"); - // list directory contents - struct stat s; - if (stat(rp, &s) == -1) { - fprintf(stderr, "Error: Cannot access %s\n", rp); - exit(1); - } - if (S_ISDIR(s.st_mode)) { - char *dir; - if (asprintf(&dir, "%s/", rp) == -1) - errExit("asprintf"); + // drop privileges + drop_privs(0); - print_directory(dir); - free(dir); - } - else { - char *split = strrchr(rp, '/'); - if (split) { - *split = '\0'; - char *rp2 = split + 1; - if (arg_debug) - printf("path %s, file %s\n", rp, rp2); - print_file_or_dir(rp, rp2, 1); - } + if (op == SANDBOX_FS_LS) + ls(fname1); + else + cat(fname1); +#ifdef HAVE_GCOV + __gcov_flush(); +#endif + _exit(0); } - free(rp); + // wait for the child to finish + int status = 0; + waitpid(child, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0); + else + exit(1); } // get file from sandbox and store it in the current directory @@ -303,10 +338,12 @@ void sandboxfs(int op, pid_t pid, const char *path1, const char *path2) { // create a user-owned temporary file in /run/firejail directory char tmp_fname[] = "/run/firejail/tmpget-XXXXXX"; int fd = mkstemp(tmp_fname); - if (fd != -1) { - SET_PERMS_FD(fd, getuid(), getgid(), 0600); - close(fd); + if (fd == -1) { + fprintf(stderr, "Error: cannot create temporary file %s\n", tmp_fname); + exit(1); } + SET_PERMS_FD(fd, getuid(), getgid(), 0600); + close(fd); // copy the source file into the temporary file - we need to chroot pid_t child = fork(); -- cgit v1.2.3-54-g00ecf From 921b0c95a91d621786346dc6f6e88d8907852bc7 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Wed, 19 Aug 2020 00:41:25 +0200 Subject: drop system(3) calls from sandbox.c --- src/firejail/sandbox.c | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c index 2314d5744..df33319f6 100644 --- a/src/firejail/sandbox.c +++ b/src/firejail/sandbox.c @@ -140,6 +140,20 @@ void set_apparmor(void) { } #endif +#ifdef HAVE_SECCOMP +void seccomp_debug(void) { + if (arg_debug == 0) + return; + + EUID_USER(); + printf("Seccomp directory:\n"); + ls(RUN_SECCOMP_DIR); + printf("Active seccomp files:\n"); + cat(RUN_SECCOMP_LIST); + EUID_ROOT(); +} +#endif + static void save_nogroups(void) { if (arg_nogroups == 0) return; @@ -197,32 +211,6 @@ static FILE *create_ready_for_join_file(void) { } } -#ifdef HAVE_SECCOMP -static void seccomp_debug(void) { - if (arg_debug == 0) - return; - - pid_t child = fork(); - if (child < 0) - errExit("fork"); - if (child == 0) { - // dropping privs before calling system(3) - drop_privs(1); - printf("Seccomp directory:\n"); - int rv = system("ls -l " RUN_SECCOMP_DIR); - (void) rv; - printf("Active seccomp files:\n"); - rv = system("cat " RUN_SECCOMP_LIST); - (void) rv; -#ifdef HAVE_GCOV - __gcov_flush(); -#endif - _exit(0); - } - waitpid(child, NULL, 0); -} -#endif - static void sandbox_if_up(Bridge *br) { assert(br); if (!br->configured) -- cgit v1.2.3-54-g00ecf From f473c959d2549ec0799bc6a7b7609c10b7f7e758 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Wed, 19 Aug 2020 00:55:47 +0200 Subject: cat option --- src/firejail/main.c | 31 +++++++++++++++++++++++++++++++ src/firejail/usage.c | 3 +++ src/man/firejail.txt | 18 +++++++++++++++--- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/firejail/main.c b/src/firejail/main.c index 79e39b669..afd9af91d 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -811,6 +811,8 @@ static void run_cmd_and_exit(int i, int argc, char **argv) { } // list directory contents + if (!arg_debug) + arg_quiet = 1; pid_t pid = require_pid(argv[i] + 5); sandboxfs(SANDBOX_FS_LS, pid, path, NULL); exit(0); @@ -818,6 +820,35 @@ static void run_cmd_and_exit(int i, int argc, char **argv) { else exit_err_feature("file transfer"); } + else if (strncmp(argv[i], "--cat=", 6) == 0) { + if (checkcfg(CFG_FILE_TRANSFER)) { + logargs(argc, argv); + if (arg_private_cwd) { + fprintf(stderr, "Error: --cat and --private-cwd options are mutually exclusive\n"); + exit(1); + } + + if ((i + 2) != argc) { + fprintf(stderr, "Error: invalid --cat option, path expected\n"); + exit(1); + } + char *path = argv[i + 1]; + invalid_filename(path, 0); // no globbing + if (strstr(path, "..")) { + fprintf(stderr, "Error: invalid file name %s\n", path); + exit(1); + } + + // write file contents to stdout + if (!arg_debug) + arg_quiet = 1; + pid_t pid = require_pid(argv[i] + 6); + sandboxfs(SANDBOX_FS_CAT, pid, path, NULL); + exit(0); + } + else + exit_err_feature("file transfer"); + } #endif else if (strncmp(argv[i], "--join=", 7) == 0) { if (checkcfg(CFG_JOIN) || getuid() == 0) { diff --git a/src/firejail/usage.c b/src/firejail/usage.c index 4ab464289..be6715df4 100644 --- a/src/firejail/usage.c +++ b/src/firejail/usage.c @@ -47,6 +47,9 @@ static char *usage_str = " --caps.drop=capability,capability - blacklist capabilities filter.\n" " --caps.keep=capability,capability - whitelist capabilities filter.\n" " --caps.print=name|pid - print the caps filter.\n" +#ifdef HAVE_FILE_TRANSFER + " --cat=name|pid filename - print content of file from sandbox container.\n" +#endif " --cgroup=tasks-file - place the sandbox in the specified control group.\n" #ifdef HAVE_CHROOT " --chroot=dirname - chroot into directory.\n" diff --git a/src/man/firejail.txt b/src/man/firejail.txt index 69cd4a7bc..f5f092bd9 100644 --- a/src/man/firejail.txt +++ b/src/man/firejail.txt @@ -272,6 +272,10 @@ $ firejail \-\-list .br $ firejail \-\-caps.print=3272 +.TP +\fB\-\-cat=name|pid filename +Print content of file from sandbox container, see FILE TRANSFER section for more details. + .TP \fB\-\-cgroup=tasks-file Place the sandbox in the specified control group. tasks-file is the full path of cgroup tasks file. @@ -344,7 +348,7 @@ $ firejail --dbus-system=filter --dbus-system.log --dbus-log=dbus.txt .TP \fB\-\-dbus-system=filter|none -Set system DBus sandboxing policy. +Set system DBus sandboxing policy. .br .br @@ -3028,6 +3032,10 @@ $ firejail --read-only=~/dir[1-4] These features allow the user to inspect the filesystem container of an existing sandbox and transfer files between the container and the host filesystem. +.TP +\fB\-\-cat=name|pid filename +Write content of a container file to standard out. + .TP \fB\-\-get=name|pid filename Retrieve the container file and store it on the host in the current working directory. @@ -3072,6 +3080,10 @@ $ firejail \-\-get=mybrowser ~/Downloads/xpra-clipboard.png $ firejail \-\-put=mybrowser xpra-clipboard.png ~/Downloads/xpra-clipboard.png .br +.br +$ firejail \-\-cat=mybrowser ~/.bashrc +.br + .SH MONITORING Option \-\-list prints a list of all sandboxes. The format for each process entry is as follows: @@ -3259,7 +3271,7 @@ Homepage: https://firejail.wordpress.com \&\flfirejail-profile\fR\|(5), \&\flfirejail-login\fR\|(5), \&\flfirejail-users\fR\|(5), -.UR https://github.com/netblue30/firejail/wiki +.UR https://github.com/netblue30/firejail/wiki .UE , -.UR https://github.com/netblue30/firejail +.UR https://github.com/netblue30/firejail .UE -- cgit v1.2.3-54-g00ecf From 450ae941a52e44a1acf68628fa8c3c9e293a8ea3 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Thu, 20 Aug 2020 23:51:22 +0200 Subject: harden cat option --- src/firejail/ls.c | 8 +++++++- src/man/firejail.txt | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/firejail/ls.c b/src/firejail/ls.c index 7c7f8b75f..4d0a001b6 100644 --- a/src/firejail/ls.c +++ b/src/firejail/ls.c @@ -233,10 +233,16 @@ void cat(const char *path) { fprintf(stderr, "Error: %s is not a regular file\n", path); exit(1); } + bool tty = isatty(STDOUT_FILENO); int c; - while ((c = fgetc(fp)) != EOF) + while ((c = fgetc(fp)) != EOF) { + // file is untrusted + // replace control characters when printing to a terminal + if (tty && c != '\t' && c != '\n' && iscntrl((unsigned char) c)) + c = '?'; fputc(c, stdout); + } fflush(stdout); fclose(fp); } diff --git a/src/man/firejail.txt b/src/man/firejail.txt index abb73b5e2..3b7ba4e3d 100644 --- a/src/man/firejail.txt +++ b/src/man/firejail.txt @@ -3036,7 +3036,9 @@ and transfer files between the container and the host filesystem. .TP \fB\-\-cat=name|pid filename -Write content of a container file to standard out. +Write content of a container file to standard out. The container is specified by name or PID. +If standard out is a terminal, all ASCII control characters except new line and horizontal tab +are replaced. .TP \fB\-\-get=name|pid filename -- cgit v1.2.3-54-g00ecf