diff --git a/contrib/vim/syntax/firejail.vim b/contrib/vim/syntax/firejail.vim index 57c7b371db6..714ed8e6ebf 100644 --- a/contrib/vim/syntax/firejail.vim +++ b/contrib/vim/syntax/firejail.vim @@ -51,7 +51,7 @@ syn match fjVar /\v\$\{(CFG|DESKTOP|DOCUMENTS|DOWNLOADS|HOME|MUSIC|PATH|PICTURES " Generate list with: { rg -o 'strn?cmp\(ptr, "([^"]+) "' -r '$1' src/firejail/profile.c; echo private-lib; } | grep -vEx '(include|ignore|caps\.drop|caps\.keep|protocol|seccomp|seccomp\.drop|seccomp\.keep|env|rmenv|net|ip)' | sort -u | tr $'\n' '|' # private-lib is special-cased in the code and doesn't match the regex; grep-ed patterns are handled later with 'syn match nextgroup=' directives (except for include which is special-cased as a fjCommandNoCond keyword) syn match fjCommand /\v(bind|blacklist|blacklist-nolog|cgroup|cpu|defaultgw|dns|hostname|hosts-file|ip6|iprange|join-or-start|mac|mkdir|mkfile|mtu|name|netfilter|netfilter6|netmask|nice|noblacklist|noexec|nowhitelist|overlay-named|private|private-bin|private-cwd|private-etc|private-home|private-lib|private-opt|private-srv|read-only|read-write|rlimit-as|rlimit-cpu|rlimit-fsize|rlimit-nofile|rlimit-nproc|rlimit-sigpending|timeout|tmpfs|veth-name|whitelist|xephyr-screen) / skipwhite contained " Generate list with: rg -o 'strn?cmp\(ptr, "([^ "]*[^ ])"' -r '$1' src/firejail/profile.c | grep -vEx '(include|rlimit|quiet)' | sed -e 's/\./\\./' | sort -u | tr $'\n' '|' # include/rlimit are false positives, quiet is special-cased below -syn match fjCommand /\v(allow-debuggers|allusers|apparmor|caps|deterministic-exit-code|deterministic-shutdown|disable-mnt|ipc-namespace|keep-config-pulse|keep-dev-shm|keep-var-tmp|machine-id|memory-deny-write-execute|netfilter|no3d|noautopulse|nodbus|nodvd|nogroups|noinput|nonewprivs|noprinters|noroot|nosound|notv|nou2f|novideo|overlay|overlay-tmpfs|private|private-cache|private-cwd|private-dev|private-lib|private-tmp|seccomp|seccomp\.32|seccomp\.block-secondary|tracelog|writable-etc|writable-run-user|writable-var|writable-var-log|x11)$/ contained +syn match fjCommand /\v(allow-debuggers|allusers|apparmor|caps|deterministic-exit-code|deterministic-shutdown|disable-mnt|ipc-namespace|keep-config-pulse|keep-dev-shm|keep-fd|keep-var-tmp|machine-id|memory-deny-write-execute|netfilter|no3d|noautopulse|nodbus|nodvd|nogroups|noinput|nonewprivs|noprinters|noroot|nosound|notv|nou2f|novideo|overlay|overlay-tmpfs|private|private-cache|private-cwd|private-dev|private-lib|private-tmp|seccomp|seccomp\.32|seccomp\.block-secondary|tracelog|writable-etc|writable-run-user|writable-var|writable-var-log|x11)$/ contained syn match fjCommand /ignore / nextgroup=fjCommand,fjCommandNoCond skipwhite contained syn match fjCommand /caps\.drop / nextgroup=fjCapability,fjAll skipwhite contained syn match fjCommand /caps\.keep / nextgroup=fjCapability skipwhite contained diff --git a/src/firejail/dbus.c b/src/firejail/dbus.c index 12256b833d9..66738bd4b17 100644 --- a/src/firejail/dbus.c +++ b/src/firejail/dbus.c @@ -298,10 +298,10 @@ void dbus_proxy_start(void) { errExit("fork"); if (dbus_proxy_pid == 0) { // close open files - int keep_list[2]; - keep_list[0] = status_pipe[1]; - keep_list[1] = args_pipe[0]; - close_all(keep_list, ARRAY_SIZE(keep_list)); + int keep[2]; + keep[0] = status_pipe[1]; + keep[1] = args_pipe[0]; + close_all(keep, ARRAY_SIZE(keep)); if (arg_dbus_log_file != NULL) { int output_fd = creat(arg_dbus_log_file, 0666); diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 7314c535087..b1f30bcda10 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -161,6 +161,7 @@ typedef struct config_t { #define MAX_PROFILE_IGNORE 32 char *profile_ignore[MAX_PROFILE_IGNORE]; + char *keep_fd; // inherit file descriptors to sandbox char *chrootdir; // chroot directory char *home_private; // private home directory char *home_private_keep; // keep list for private home directory @@ -352,6 +353,7 @@ extern int arg_nou2f; // --nou2f extern int arg_noinput; // --noinput extern int arg_deterministic_exit_code; // always exit with first child's exit status extern int arg_deterministic_shutdown; // shut down the sandbox if first child dies +extern int arg_keep_fd_all; // inherit all file descriptors to sandbox typedef enum { DBUS_POLICY_ALLOW, // Allow unrestricted access to the bus diff --git a/src/firejail/join.c b/src/firejail/join.c index 9fc80d85b50..b62a1ca9d91 100644 --- a/src/firejail/join.c +++ b/src/firejail/join.c @@ -569,11 +569,6 @@ void join(pid_t pid, int argc, char **argv, int index) { dbus_set_system_bus_env(); #endif - // set nice and rlimits - if (arg_nice) - set_nice(cfg.nice); - set_rlimits(); - start_application(0, shfd, NULL); __builtin_unreachable(); diff --git a/src/firejail/main.c b/src/firejail/main.c index 09e1a107147..0816afe8321 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -149,6 +149,7 @@ int arg_nou2f = 0; // --nou2f int arg_noinput = 0; // --noinput int arg_deterministic_exit_code = 0; // always exit with first child's exit status int arg_deterministic_shutdown = 0; // shut down the sandbox if first child dies +int arg_keep_fd_all = 0; // inherit all file descriptors to sandbox DbusPolicy arg_dbus_user = DBUS_POLICY_ALLOW; // --dbus-user DbusPolicy arg_dbus_system = DBUS_POLICY_ALLOW; // --dbus-system const char *arg_dbus_log_file = NULL; @@ -1862,6 +1863,14 @@ int main(int argc, char **argv, char **envp) { } profile_add_ignore(argv[i] + 9); } + else if (strncmp(argv[i], "--keep-fd=", 10) == 0) { + if (strcmp(argv[i] + 10, "all") == 0) + arg_keep_fd_all = 1; + else { + const char *add = argv[i] + 10; + profile_list_augment(&cfg.keep_fd, add); + } + } #ifdef HAVE_CHROOT else if (strncmp(argv[i], "--chroot=", 9) == 0) { if (checkcfg(CFG_CHROOT)) { diff --git a/src/firejail/profile.c b/src/firejail/profile.c index 5725100e465..794668dc688 100644 --- a/src/firejail/profile.c +++ b/src/firejail/profile.c @@ -290,6 +290,15 @@ int profile_check_line(char *ptr, int lineno, const char *fname) { return 0; } + if (strncmp(ptr, "keep-fd ", 8) == 0) { + if (strcmp(ptr + 8, "all") == 0) + arg_keep_fd_all = 1; + else { + const char *add = ptr + 8; + profile_list_augment(&cfg.keep_fd, add); + } + return 0; + } if (strncmp(ptr, "xephyr-screen ", 14) == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) { diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c index 61d6578a0bc..0e4e1a36e92 100644 --- a/src/firejail/sandbox.c +++ b/src/firejail/sandbox.c @@ -399,12 +399,28 @@ static int monitor_application(pid_t app_pid) { return arg_deterministic_exit_code ? app_status : status; } + static void print_time(void) { float delta = timetrace_end(); fmessage("Child process initialized in %.02f ms\n", delta); } +int *build_keep_fd_array(size_t *sz) { + if (!cfg.keep_fd) { + *sz = 0; + return NULL; + } + + int *rv = str_to_int_array(cfg.keep_fd, sz); + if (!rv) { + fprintf(stderr, "Error: invalid keep-fd option\n"); + exit(1); + } + return rv; +} + + // check execute permissions for the program // this is done typically by the shell // we are here because of --shell=none @@ -461,10 +477,27 @@ static int ok_to_run(const char *program) { return 0; } + void start_application(int no_sandbox, int fd, char *set_sandbox_status) { - // set environment - if (no_sandbox == 0) + if (no_sandbox == 0) { + // don't leak open file descriptors + if (!arg_keep_fd_all) { + size_t sz; + int *keep = build_keep_fd_array(&sz); + close_all(keep, sz); + if (keep) + free(keep); + } + + // set nice and rlimits + if (arg_nice) + set_nice(cfg.nice); + set_rlimits(); + env_defaults(); + } + + // set environment env_apply_all(); // restore original umask @@ -1252,12 +1285,6 @@ int sandbox(void* sandbox_arg) { #ifdef HAVE_APPARMOR set_apparmor(); #endif - - // set nice and rlimits - if (arg_nice) - set_nice(cfg.nice); - set_rlimits(); - start_application(0, -1, set_sandbox_status); } diff --git a/src/firejail/usage.c b/src/firejail/usage.c index 24c8e319471..c903841c579 100644 --- a/src/firejail/usage.c +++ b/src/firejail/usage.c @@ -119,6 +119,7 @@ static char *usage_str = " --join-or-start=name|pid - join the sandbox or start a new one.\n" " --keep-config-pulse - disable automatic ~/.config/pulse init.\n" " --keep-dev-shm - /dev/shm directory is untouched (even with --private-dev).\n" + " --keep-fd - inherit open file descriptors to sandbox.\n" " --keep-var-tmp - /var/tmp directory is untouched.\n" " --list - list all sandboxes.\n" #ifdef HAVE_FILE_TRANSFER diff --git a/src/include/common.h b/src/include/common.h index 24feab02a76..f72ec9738e7 100644 --- a/src/include/common.h +++ b/src/include/common.h @@ -142,4 +142,5 @@ int pid_proc_cmdline_x11_xpra_xephyr(const pid_t pid); int pid_hidepid(void); void warn_dumpable(void); const char *gnu_basename(const char *path); +int *str_to_int_array(const char *str, size_t *sz); #endif diff --git a/src/lib/common.c b/src/lib/common.c index f46e7db1c31..91d5125b1f2 100644 --- a/src/lib/common.c +++ b/src/lib/common.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "../include/common.h" #define BUFLEN 4096 @@ -320,6 +321,55 @@ const char *gnu_basename(const char *path) { return last_slash+1; } +// takes string with comma separated int values, returns int array +int *str_to_int_array(const char *str, size_t *sz) { + assert(str && sz); + + size_t curr_sz = 0; + size_t arr_sz = 16; + int *rv = malloc(arr_sz * sizeof(int)); + if (!rv) + errExit("malloc"); + + char *dup = strdup(str); + if (!dup) + errExit("strdup"); + char *tok = strtok(dup, ","); + if (!tok) { + free(dup); + free(rv); + goto errout; + } + + while (tok) { + char *end; + long val = strtol(tok, &end, 10); + if (end == tok || *end != '\0' || val < INT_MIN || val > INT_MAX) { + free(dup); + free(rv); + goto errout; + } + + if (curr_sz == arr_sz) { + arr_sz *= 2; + rv = realloc(rv, arr_sz * sizeof(int)); + if (!rv) + errExit("realloc"); + } + rv[curr_sz++] = val; + + tok = strtok(NULL, ","); + } + free(dup); + + *sz = curr_sz; + return rv; + +errout: + *sz = 0; + return NULL; +} + //************************** // time trace based on getticks function //************************** diff --git a/src/man/firejail-profile.txt b/src/man/firejail-profile.txt index 71dab18ba4e..29f0fe4e4af 100644 --- a/src/man/firejail-profile.txt +++ b/src/man/firejail-profile.txt @@ -724,6 +724,11 @@ env CFLAGS="-W -Wall -Werror" .TP \fBipc-namespace Enable IPC namespace. + +.TP +\fBkeep-fd +Inherit open file descriptors to sandbox. + .TP \fBname sandboxname Set sandbox name. Example: diff --git a/src/man/firejail.txt b/src/man/firejail.txt index 80487a49df7..a5704e99526 100644 --- a/src/man/firejail.txt +++ b/src/man/firejail.txt @@ -1103,6 +1103,26 @@ Example: .br $ firejail --keep-dev-shm --private-dev +.TP +\fB\-\-keep-fd=all +Inherit all open file descriptors to the sandbox. By default only file descriptors 0, 1 and 2 are inherited to the sandbox, and all other file descriptors are closed. +.br + +.br +Example: +.br +$ firejail --keep-fd=all + +.TP +\fB\-\-keep-fd=file_descriptor +Don't close specified open file descriptors. By default only file descriptors 0, 1 and 2 are inherited to the sandbox, and all other file descriptors are closed. +.br + +.br +Example: +.br +$ firejail --keep-fd=3,4,5 + .TP \fB\-\-keep-var-tmp /var/tmp directory is untouched. diff --git a/src/zsh_completion/_firejail.in b/src/zsh_completion/_firejail.in index 334812dd69d..f7cd3cdfffa 100644 --- a/src/zsh_completion/_firejail.in +++ b/src/zsh_completion/_firejail.in @@ -104,6 +104,7 @@ _firejail_args=( '--join-or-start=-[join the sandbox or start a new one name|pid]: :_all_firejails' '--keep-config-pulse[disable automatic ~/.config/pulse init]' '--keep-dev-shm[/dev/shm directory is untouched (even with --private-dev)]' + '--keep-fd[inherit open file descriptors to sandbox]' '--keep-var-tmp[/var/tmp directory is untouched]' '--machine-id[spoof /etc/machine-id with a random id]' '--memory-deny-write-execute[seccomp filter to block attempts to create memory mappings that are both writable and executable]'