mirror of
https://github.com/netblue30/firejail.git
synced 2026-05-15 14:16:14 -06:00
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:
parent
6fc8a559de
commit
2345cc4c7d
2 changed files with 229 additions and 205 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue