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.
This commit is contained in:
Kristóf Marussy 2020-02-27 19:53:21 +01:00
parent 6fc8a559de
commit 2345cc4c7d
2 changed files with 229 additions and 205 deletions

View file

@ -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);

View file

@ -23,7 +23,7 @@
#include <unistd.h>
#include <net/if.h>
#include <stdarg.h>
#include <sys/wait.h>
#include <sys/wait.h>
#include "../include/seccomp.h"
#include <fcntl.h>
@ -31,6 +31,210 @@
#define O_PATH 010000000
#endif
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";
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
if ((filtermask & SBOX_KEEP_FDS) == 0) {
int i;
for (i = 3; i < FIREJAIL_MAX_FD; 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) {
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),
#endif
// syscall list
#ifdef SYS_mount
BLACKLIST(SYS_mount), // mount/unmount filesystems
#endif
#ifdef SYS_umount2
BLACKLIST(SYS_umount2),
#endif
#ifdef SYS_ptrace
BLACKLIST(SYS_ptrace), // trace processes
#endif
#ifdef SYS_process_vm_readv
BLACKLIST(SYS_process_vm_readv),
#endif
#ifdef SYS_process_vm_writev
BLACKLIST(SYS_process_vm_writev),
#endif
#ifdef SYS_kexec_file_load
BLACKLIST(SYS_kexec_file_load), // loading a different kernel
#endif
#ifdef SYS_kexec_load
BLACKLIST(SYS_kexec_load),
#endif
#ifdef 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
#endif
#ifdef SYS_init_module
BLACKLIST(SYS_init_module), // kernel module handling
#endif
#ifdef SYS_finit_module // introduced in 2013
BLACKLIST(SYS_finit_module),
#endif
#ifdef SYS_create_module
BLACKLIST(SYS_create_module),
#endif
#ifdef SYS_delete_module
BLACKLIST(SYS_delete_module),
#endif
#ifdef SYS_iopl
BLACKLIST(SYS_iopl), // io permissions
#endif
#ifdef SYS_ioperm
BLACKLIST(SYS_ioperm),
#endif
#ifdef SYS_ioprio_set
BLACKLIST(SYS_ioprio_set),
#endif
#ifdef SYS_ni_syscall // new io permissions call on arm devices
BLACKLIST(SYS_ni_syscall),
#endif
#ifdef SYS_swapon
BLACKLIST(SYS_swapon), // swap on/off
#endif
#ifdef SYS_swapoff
BLACKLIST(SYS_swapoff),
#endif
#ifdef SYS_syslog
BLACKLIST(SYS_syslog), // kernel printk control
#endif
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);
@ -39,7 +243,7 @@ int sbox_run(unsigned filtermask, int num, ...) {
char **arg = malloc((num + 1) * sizeof(char *));
int i;
for (i = 0; i < num; i++)
arg[i] = va_arg(valist, char*);
arg[i] = va_arg(valist, char *);
arg[i] = NULL;
va_end(valist);
@ -51,87 +255,6 @@ int sbox_run(unsigned filtermask, int num, ...) {
}
int sbox_run_v(unsigned filtermask, char * const arg[]) {
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),
#endif
// syscall list
#ifdef SYS_mount
BLACKLIST(SYS_mount), // mount/unmount filesystems
#endif
#ifdef SYS_umount2
BLACKLIST(SYS_umount2),
#endif
#ifdef SYS_ptrace
BLACKLIST(SYS_ptrace), // trace processes
#endif
#ifdef SYS_process_vm_readv
BLACKLIST(SYS_process_vm_readv),
#endif
#ifdef SYS_process_vm_writev
BLACKLIST(SYS_process_vm_writev),
#endif
#ifdef SYS_kexec_file_load
BLACKLIST(SYS_kexec_file_load), // loading a different kernel
#endif
#ifdef SYS_kexec_load
BLACKLIST(SYS_kexec_load),
#endif
#ifdef 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
#endif
#ifdef SYS_init_module
BLACKLIST(SYS_init_module), // kernel module handling
#endif
#ifdef SYS_finit_module // introduced in 2013
BLACKLIST(SYS_finit_module),
#endif
#ifdef SYS_create_module
BLACKLIST(SYS_create_module),
#endif
#ifdef SYS_delete_module
BLACKLIST(SYS_delete_module),
#endif
#ifdef SYS_iopl
BLACKLIST(SYS_iopl), // io permissions
#endif
#ifdef SYS_ioperm
BLACKLIST(SYS_ioperm),
#endif
#ifdef SYS_ioprio_set
BLACKLIST(SYS_ioprio_set),
#endif
#ifdef SYS_ni_syscall // new io permissions call on arm devices
BLACKLIST(SYS_ni_syscall),
#endif
#ifdef SYS_swapon
BLACKLIST(SYS_swapon), // swap on/off
#endif
#ifdef SYS_swapoff
BLACKLIST(SYS_swapoff),
#endif
#ifdef SYS_syslog
BLACKLIST(SYS_syslog), // kernel printk control
#endif
RETURN_ALLOW
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};
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);
}