From 2345cc4c7d1ec1322c50d11e992be49ba3588db3 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Thu, 27 Feb 2020 19:53:21 +0100 Subject: Add sbox_exec_v and SBOX_KEEP_FDS To contain processes forked for long time, such as the xdg-dbus-proxy, sbox_exec_v can be used, which is the non-forking version of sbox_run_v. Additionally, the SBOX_KEEPS_FDS flag avoid closing any open fds, so fds needed by the subordinate process can be left open before calling sbox_exec_v. This flag does not makes sense for sbox_run_v, and causes an assertion failure. --- src/firejail/firejail.h | 3 + src/firejail/sbox.c | 369 +++++++++++++++++++++++++----------------------- 2 files changed, 198 insertions(+), 174 deletions(-) (limited to 'src') diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index ea4012335..d35e0d155 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -830,10 +830,13 @@ void build_appimage_cmdline(char **command_line, char **window_title, int argc, #define SBOX_STDIN_FROM_FILE (1 << 6) // open file and redirect it to stdin #define SBOX_CAPS_HIDEPID (1 << 7) // hidepid caps filter for running firemon #define SBOX_CAPS_NET_SERVICE (1 << 8) // caps filter for programs running network services +#define SBOX_KEEP_FDS (1 << 9) // keep file descriptors open +#define FIREJAIL_MAX_FD 20 // getdtablesize() is overkill for a firejail process // run sbox int sbox_run(unsigned filter, int num, ...); int sbox_run_v(unsigned filter, char * const arg[]); +void sbox_exec_v(unsigned filter, char * const arg[]); // run_files.c void delete_run_files(pid_t pid); diff --git a/src/firejail/sbox.c b/src/firejail/sbox.c index e96b9cf79..e7fa267d8 100644 --- a/src/firejail/sbox.c +++ b/src/firejail/sbox.c @@ -23,7 +23,7 @@ #include #include #include - #include +#include #include "../include/seccomp.h" #include @@ -31,107 +31,230 @@ #define O_PATH 010000000 #endif -int sbox_run(unsigned filtermask, int num, ...) { - va_list valist; - va_start(valist, num); +static int sbox_do_exec_v(unsigned filtermask, char * const arg[]) { + int env_index = 0; + char *new_environment[256] = { NULL }; + // preserve firejail-specific env vars + char *cl = getenv("FIREJAIL_FILE_COPY_LIMIT"); + if (cl) { + if (asprintf(&new_environment[env_index++], "FIREJAIL_FILE_COPY_LIMIT=%s", cl) == -1) + errExit("asprintf"); + } + clearenv(); + if (arg_quiet) // --quiet is passed as an environment variable + new_environment[env_index++] = "FIREJAIL_QUIET=yes"; + if (arg_debug) // --debug is passed as an environment variable + new_environment[env_index++] = "FIREJAIL_DEBUG=yes"; - // build argument list - char **arg = malloc((num + 1) * sizeof(char *)); - int i; - for (i = 0; i < num; i++) - arg[i] = va_arg(valist, char*); - arg[i] = NULL; - va_end(valist); + if (filtermask & SBOX_STDIN_FROM_FILE) { + int fd; + if((fd = open(SBOX_STDIN_FILE, O_RDONLY)) == -1) { + fprintf(stderr,"Error: cannot open %s\n", SBOX_STDIN_FILE); + exit(1); + } + if (dup2(fd, STDIN_FILENO) == -1) + errExit("dup2"); + close(fd); + } + else if ((filtermask & SBOX_ALLOW_STDIN) == 0) { + int fd = open("/dev/null",O_RDWR, 0); + if (fd != -1) { + if (dup2(fd, STDIN_FILENO) == -1) + errExit("dup2"); + close(fd); + } + else // the user could run the sandbox without /dev/null + close(STDIN_FILENO); + } - int status = sbox_run_v(filtermask, arg); + // close all other file descriptors + if ((filtermask & SBOX_KEEP_FDS) == 0) { + int i; + for (i = 3; i < FIREJAIL_MAX_FD; i++) + close(i); // close open files + } - free(arg); + umask(027); - return status; -} + // apply filters + if (filtermask & SBOX_CAPS_NONE) { + caps_drop_all(); + } else { + uint64_t set = 0; + if (filtermask & SBOX_CAPS_NETWORK) { +#ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files + set |= ((uint64_t) 1) << CAP_NET_ADMIN; + set |= ((uint64_t) 1) << CAP_NET_RAW; +#endif + } + if (filtermask & SBOX_CAPS_HIDEPID) { +#ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files + set |= ((uint64_t) 1) << CAP_SYS_PTRACE; + set |= ((uint64_t) 1) << CAP_SYS_PACCT; +#endif + } + if (filtermask & SBOX_CAPS_NET_SERVICE) { +#ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files + set |= ((uint64_t) 1) << CAP_NET_BIND_SERVICE; + set |= ((uint64_t) 1) << CAP_NET_BROADCAST; +#endif + } + if (set != 0) { // some SBOX_CAPS_ flag was specified, drop all other capabilities +#ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files + caps_set(set); +#endif + } + } -int sbox_run_v(unsigned filtermask, char * const arg[]) { - struct sock_filter filter[] = { - VALIDATE_ARCHITECTURE, - EXAMINE_SYSCALL, + if (filtermask & SBOX_SECCOMP) { + struct sock_filter filter[] = { + VALIDATE_ARCHITECTURE, + EXAMINE_SYSCALL, #if defined(__x86_64__) #define X32_SYSCALL_BIT 0x40000000 - // handle X32 ABI - BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, X32_SYSCALL_BIT, 1, 0), - BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, 0, 1, 0), - RETURN_ERRNO(EPERM), + // handle X32 ABI + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, X32_SYSCALL_BIT, 1, 0), + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, 0, 1, 0), + RETURN_ERRNO(EPERM), #endif - // syscall list + // syscall list #ifdef SYS_mount - BLACKLIST(SYS_mount), // mount/unmount filesystems + BLACKLIST(SYS_mount), // mount/unmount filesystems #endif #ifdef SYS_umount2 - BLACKLIST(SYS_umount2), + BLACKLIST(SYS_umount2), #endif #ifdef SYS_ptrace - BLACKLIST(SYS_ptrace), // trace processes + BLACKLIST(SYS_ptrace), // trace processes #endif #ifdef SYS_process_vm_readv - BLACKLIST(SYS_process_vm_readv), + BLACKLIST(SYS_process_vm_readv), #endif #ifdef SYS_process_vm_writev - BLACKLIST(SYS_process_vm_writev), + BLACKLIST(SYS_process_vm_writev), #endif #ifdef SYS_kexec_file_load - BLACKLIST(SYS_kexec_file_load), // loading a different kernel + BLACKLIST(SYS_kexec_file_load), // loading a different kernel #endif #ifdef SYS_kexec_load - BLACKLIST(SYS_kexec_load), + BLACKLIST(SYS_kexec_load), #endif #ifdef SYS_name_to_handle_at - BLACKLIST(SYS_name_to_handle_at), + BLACKLIST(SYS_name_to_handle_at), #endif #ifdef SYS_open_by_handle_at - BLACKLIST(SYS_open_by_handle_at), // open by handle + BLACKLIST(SYS_open_by_handle_at), // open by handle #endif #ifdef SYS_init_module - BLACKLIST(SYS_init_module), // kernel module handling + BLACKLIST(SYS_init_module), // kernel module handling #endif #ifdef SYS_finit_module // introduced in 2013 - BLACKLIST(SYS_finit_module), + BLACKLIST(SYS_finit_module), #endif #ifdef SYS_create_module - BLACKLIST(SYS_create_module), + BLACKLIST(SYS_create_module), #endif #ifdef SYS_delete_module - BLACKLIST(SYS_delete_module), + BLACKLIST(SYS_delete_module), #endif #ifdef SYS_iopl - BLACKLIST(SYS_iopl), // io permissions + BLACKLIST(SYS_iopl), // io permissions #endif -#ifdef SYS_ioperm - BLACKLIST(SYS_ioperm), +#ifdef SYS_ioperm + BLACKLIST(SYS_ioperm), #endif -#ifdef SYS_ioprio_set - BLACKLIST(SYS_ioprio_set), +#ifdef SYS_ioprio_set + BLACKLIST(SYS_ioprio_set), #endif #ifdef SYS_ni_syscall // new io permissions call on arm devices - BLACKLIST(SYS_ni_syscall), + BLACKLIST(SYS_ni_syscall), #endif #ifdef SYS_swapon - BLACKLIST(SYS_swapon), // swap on/off + BLACKLIST(SYS_swapon), // swap on/off #endif #ifdef SYS_swapoff - BLACKLIST(SYS_swapoff), + BLACKLIST(SYS_swapoff), #endif #ifdef SYS_syslog - BLACKLIST(SYS_syslog), // kernel printk control + BLACKLIST(SYS_syslog), // kernel printk control #endif - RETURN_ALLOW - }; + RETURN_ALLOW + }; + + struct sock_fprog prog = { + .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])), + .filter = filter, + }; + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + perror("prctl(NO_NEW_PRIVS)"); + } + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { + perror("prctl(PR_SET_SECCOMP)"); + } + } + + if (filtermask & SBOX_ROOT) { + // elevate privileges in order to get grsecurity working + if (setreuid(0, 0)) + errExit("setreuid"); + if (setregid(0, 0)) + errExit("setregid"); + } + else if (filtermask & SBOX_USER) + drop_privs(1); + + if (arg[0]) { // get rid of scan-build warning + int fd = open(arg[0], O_PATH | O_CLOEXEC); + if (fd == -1) { + if (errno == ENOENT) { + fprintf(stderr, "Error: %s does not exist\n", arg[0]); + exit(1); + } else { + errExit("open"); + } + } + struct stat s; + if (fstat(fd, &s) == -1) + errExit("fstat"); + if (s.st_uid != 0 && s.st_gid != 0) { + fprintf(stderr, "Error: %s is not owned by root, refusing to execute\n", arg[0]); + exit(1); + } + if (s.st_mode & 00002) { + fprintf(stderr, "Error: %s is world writable, refusing to execute\n", arg[0]); + exit(1); + } + fexecve(fd, arg, new_environment); + } else { + assert(0); + } + perror("fexecve"); + _exit(1); +} + +int sbox_run(unsigned filtermask, int num, ...) { + va_list valist; + va_start(valist, num); + + // build argument list + char **arg = malloc((num + 1) * sizeof(char *)); + int i; + for (i = 0; i < num; i++) + arg[i] = va_arg(valist, char *); + arg[i] = NULL; + va_end(valist); + + int status = sbox_run_v(filtermask, arg); + + free(arg); - struct sock_fprog prog = { - .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])), - .filter = filter, - }; + return status; +} +int sbox_run_v(unsigned filtermask, char * const arg[]) { EUID_ROOT(); if (arg_debug) { @@ -144,132 +267,14 @@ int sbox_run_v(unsigned filtermask, char * const arg[]) { printf("\n"); } + // KEEP_FDS only makes sense with sbox_exec_v + assert((filtermask & SBOX_KEEP_FDS) == 0); + pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { - int env_index = 0; - char *new_environment[256] = { NULL }; - // preserve firejail-specific env vars - char *cl = getenv("FIREJAIL_FILE_COPY_LIMIT"); - if (cl) { - if (asprintf(&new_environment[env_index++], "FIREJAIL_FILE_COPY_LIMIT=%s", cl) == -1) - errExit("asprintf"); - } - clearenv(); - if (arg_quiet) // --quiet is passed as an environment variable - new_environment[env_index++] = "FIREJAIL_QUIET=yes"; - if (arg_debug) // --debug is passed as an environment variable - new_environment[env_index++] = "FIREJAIL_DEBUG=yes"; - if (cfg.seccomp_error_action) - if (asprintf(&new_environment[env_index++], "FIREJAIL_SECCOMP_ERROR_ACTION=%s", cfg.seccomp_error_action) == -1) - errExit("asprintf"); - - if (filtermask & SBOX_STDIN_FROM_FILE) { - int fd; - if((fd = open(SBOX_STDIN_FILE, O_RDONLY)) == -1) { - fprintf(stderr,"Error: cannot open %s\n", SBOX_STDIN_FILE); - exit(1); - } - if (dup2(fd, STDIN_FILENO) == -1) - errExit("dup2"); - close(fd); - } - else if ((filtermask & SBOX_ALLOW_STDIN) == 0) { - int fd = open("/dev/null",O_RDWR, 0); - if (fd != -1) { - if (dup2(fd, STDIN_FILENO) == -1) - errExit("dup2"); - close(fd); - } - else // the user could run the sandbox without /dev/null - close(STDIN_FILENO); - } - - // close all other file descriptors - int max = 20; // getdtablesize() is overkill for a firejail process - int i = 3; - for (i = 3; i < max; i++) - close(i); // close open files - - umask(027); - - // apply filters - if (filtermask & SBOX_CAPS_NONE) { - caps_drop_all(); - } else { - uint64_t set = 0; - if (filtermask & SBOX_CAPS_NETWORK) { -#ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files - set |= ((uint64_t) 1) << CAP_NET_ADMIN; - set |= ((uint64_t) 1) << CAP_NET_RAW; -#endif - } - if (filtermask & SBOX_CAPS_HIDEPID) { -#ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files - set |= ((uint64_t) 1) << CAP_SYS_PTRACE; - set |= ((uint64_t) 1) << CAP_SYS_PACCT; -#endif - } - if (filtermask & SBOX_CAPS_NET_SERVICE) { -#ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files - set |= ((uint64_t) 1) << CAP_NET_BIND_SERVICE; - set |= ((uint64_t) 1) << CAP_NET_BROADCAST; -#endif - } - if (set != 0) { // some SBOX_CAPS_ flag was specified, drop all other capabilities -#ifndef HAVE_GCOV // the following filter will prevent GCOV from saving info in .gcda files - caps_set(set); -#endif - } - } - - if (filtermask & SBOX_SECCOMP) { - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { - perror("prctl(NO_NEW_PRIVS)"); - } - if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { - perror("prctl(PR_SET_SECCOMP)"); - } - } - - if (filtermask & SBOX_ROOT) { - // elevate privileges in order to get grsecurity working - if (setreuid(0, 0)) - errExit("setreuid"); - if (setregid(0, 0)) - errExit("setregid"); - } - else if (filtermask & SBOX_USER) - drop_privs(1); - - if (arg[0]) { // get rid of scan-build warning - int fd = open(arg[0], O_PATH | O_CLOEXEC); - if (fd == -1) { - if (errno == ENOENT) { - fprintf(stderr, "Error: %s does not exist\n", arg[0]); - exit(1); - } else { - errExit("open"); - } - } - struct stat s; - if (fstat(fd, &s) == -1) - errExit("fstat"); - if (s.st_uid != 0 && s.st_gid != 0) { - fprintf(stderr, "Error: %s is not owned by root, refusing to execute\n", arg[0]); - exit(1); - } - if (s.st_mode & 00002) { - fprintf(stderr, "Error: %s is world writable, refusing to execute\n", arg[0]); - exit(1); - } - fexecve(fd, arg, new_environment); - } else { - assert(0); - } - perror("fexecve"); - _exit(1); + sbox_do_exec_v(filtermask, arg); } int status; @@ -283,3 +288,19 @@ int sbox_run_v(unsigned filtermask, char * const arg[]) { return status; } + +void sbox_exec_v(unsigned filtermask, char * const arg[]) { + EUID_ROOT(); + + if (arg_debug) { + printf("sbox exec: "); + int i = 0; + while (arg[i]) { + printf("%s ", arg[i]); + i++; + } + printf("\n"); + } + + sbox_do_exec_v(filtermask, arg); +} -- cgit v1.2.3-70-g09d2