mirror of
https://github.com/netblue30/firejail.git
synced 2026-05-21 06:45:29 -06:00
Merge pull request #3265 from kris7t/dbus-proxy
Fine-grained DBus sandboxing
This commit is contained in:
commit
07fac581f6
12 changed files with 982 additions and 249 deletions
|
|
@ -18,41 +18,436 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
#include "firejail.h"
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef O_PATH
|
||||
#define O_PATH 010000000
|
||||
#endif
|
||||
|
||||
#define DBUS_SOCKET_PATH_PREFIX "unix:path="
|
||||
#define DBUS_USER_SOCKET_FORMAT "/run/user/%d/bus"
|
||||
#define DBUS_USER_SOCKET_FORMAT2 "/run/user/%d/dbus/user_bus_socket"
|
||||
#define DBUS_SYSTEM_SOCKET "/run/dbus/system_bus_socket"
|
||||
#define DBUS_SESSION_BUS_ADDRESS_ENV "DBUS_SESSION_BUS_ADDRESS"
|
||||
#define DBUS_SYSTEM_BUS_ADDRESS_ENV "DBUS_SYSTEM_BUS_ADDRESS"
|
||||
#define DBUS_USER_DIR_FORMAT RUN_FIREJAIL_DBUS_DIR "/%d"
|
||||
#define DBUS_USER_PROXY_SOCKET_FORMAT DBUS_USER_DIR_FORMAT "/%d-user"
|
||||
#define DBUS_SYSTEM_PROXY_SOCKET_FORMAT DBUS_USER_DIR_FORMAT "/%d-system"
|
||||
#define DBUS_MAX_NAME_LENGTH 255
|
||||
#define XDG_DBUS_PROXY_PATH "/usr/bin/xdg-dbus-proxy"
|
||||
|
||||
static pid_t dbus_proxy_pid = 0;
|
||||
static int dbus_proxy_status_fd = -1;
|
||||
static char *dbus_user_proxy_socket = NULL;
|
||||
static char *dbus_system_proxy_socket = NULL;
|
||||
|
||||
int dbus_check_name(const char *name) {
|
||||
unsigned long length = strlen(name);
|
||||
if (length == 0 || length > DBUS_MAX_NAME_LENGTH)
|
||||
return 0;
|
||||
const char *p = name;
|
||||
int segments = 1;
|
||||
int in_segment = 0;
|
||||
while (*p) {
|
||||
int alpha = (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z');
|
||||
int digit = *p >= '0' && *p <= '9';
|
||||
if (in_segment) {
|
||||
if (*p == '.') {
|
||||
++segments;
|
||||
in_segment = 0;
|
||||
} else if (!alpha && !digit && *p != '_' && *p != '-') {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (*p == '*') {
|
||||
return *(p + 1) == '\0';
|
||||
} else if (!alpha && *p != '_' && *p != '-') {
|
||||
return 0;
|
||||
}
|
||||
in_segment = 1;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
return in_segment && segments >= 2;
|
||||
}
|
||||
|
||||
static void dbus_check_bus_profile(char const *prefix, DbusPolicy *policy) {
|
||||
if (*policy == DBUS_POLICY_FILTER) {
|
||||
struct stat s;
|
||||
if (stat(XDG_DBUS_PROXY_PATH, &s) == -1) {
|
||||
if (errno == ENOENT) {
|
||||
fprintf(stderr,
|
||||
"Warning: " XDG_DBUS_PROXY_PATH
|
||||
" was not found, downgrading %s policy to allow.\n"
|
||||
"To enable DBus filtering, install the xdg-dbus-proxy "
|
||||
"program.\n", prefix);
|
||||
*policy = DBUS_POLICY_ALLOW;
|
||||
} else {
|
||||
errExit("stat");
|
||||
}
|
||||
} else {
|
||||
// No need to warn on profile entries.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
size_t prefix_length = strlen(prefix);
|
||||
ProfileEntry *it = cfg.profile;
|
||||
int num_matches = 0;
|
||||
const char *first_match = NULL;
|
||||
while (it) {
|
||||
char *data = it->data;
|
||||
it = it->next;
|
||||
if (strncmp(prefix, data, prefix_length) == 0) {
|
||||
++num_matches;
|
||||
if (first_match == NULL)
|
||||
first_match = data;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_matches > 0) {
|
||||
assert(first_match != NULL);
|
||||
if (num_matches == 1) {
|
||||
fprintf(stderr, "Ignoring \"%s\".\n", first_match);
|
||||
} else if (num_matches == 2) {
|
||||
fprintf(stderr, "Ignoring \"%s\" and 1 other %s filter rule.\n",
|
||||
first_match, prefix);
|
||||
} else {
|
||||
fprintf(stderr, "Ignoring \"%s\" and %d other %s filter rules.\n",
|
||||
first_match, num_matches - 1, prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dbus_check_profile(void) {
|
||||
dbus_check_bus_profile("dbus-user", &arg_dbus_user);
|
||||
dbus_check_bus_profile("dbus-system", &arg_dbus_system);
|
||||
}
|
||||
|
||||
static void write_arg(int fd, char const *format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
char *arg;
|
||||
int length = vasprintf(&arg, format, ap);
|
||||
va_end(ap);
|
||||
if (length == -1)
|
||||
errExit("vasprintf");
|
||||
length++;
|
||||
if (arg_debug)
|
||||
printf("xdg-dbus-proxy arg: %s\n", arg);
|
||||
if (write(fd, arg, (size_t) length) != (ssize_t) length)
|
||||
errExit("write");
|
||||
free(arg);
|
||||
}
|
||||
|
||||
static void write_profile(int fd, char const *prefix) {
|
||||
size_t prefix_length = strlen(prefix);
|
||||
ProfileEntry *it = cfg.profile;
|
||||
while (it) {
|
||||
char *data = it->data;
|
||||
it = it->next;
|
||||
if (strncmp(prefix, data, prefix_length) != 0)
|
||||
continue;
|
||||
data += prefix_length;
|
||||
int arg_length = 0;
|
||||
while (data[arg_length] != '\0' && data[arg_length] != ' ')
|
||||
arg_length++;
|
||||
if (data[arg_length] != ' ')
|
||||
continue;
|
||||
write_arg(fd, "--%.*s=%s", arg_length, data, &data[arg_length + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
static void dbus_create_user_dir(void) {
|
||||
char *path;
|
||||
if (asprintf(&path, DBUS_USER_DIR_FORMAT, (int) getuid()) == -1)
|
||||
errExit("asprintf");
|
||||
struct stat s;
|
||||
mode_t mode = 0700;
|
||||
uid_t uid = getuid();
|
||||
gid_t gid = getgid();
|
||||
if (stat(path, &s)) {
|
||||
if (arg_debug)
|
||||
printf("Creating %s directory for DBus proxy sockets\n", path);
|
||||
if (mkdir(path, mode) == -1 && errno != EEXIST)
|
||||
errExit("mkdir");
|
||||
if (set_perms(path, uid, gid, mode))
|
||||
errExit("set_perms");
|
||||
ASSERT_PERMS(path, uid, gid, mode);
|
||||
}
|
||||
free(path);
|
||||
}
|
||||
|
||||
static char *find_user_socket_by_format(char *format) {
|
||||
char *dbus_user_socket;
|
||||
if (asprintf(&dbus_user_socket, format, (int) getuid()) == -1)
|
||||
errExit("asprintf");
|
||||
struct stat s;
|
||||
if (stat(dbus_user_socket, &s) == -1) {
|
||||
if (errno == ENOENT)
|
||||
goto fail;
|
||||
return NULL;
|
||||
errExit("stat");
|
||||
}
|
||||
if (!S_ISSOCK(s.st_mode))
|
||||
goto fail;
|
||||
return dbus_user_socket;
|
||||
fail:
|
||||
free(dbus_user_socket);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *find_user_socket(void) {
|
||||
char *socket1 = find_user_socket_by_format(DBUS_USER_SOCKET_FORMAT);
|
||||
if (socket1 != NULL)
|
||||
return socket1;
|
||||
char *socket2 = find_user_socket_by_format(DBUS_USER_SOCKET_FORMAT2);
|
||||
if (socket2 != NULL)
|
||||
return socket2;
|
||||
fprintf(stderr, "DBus user socket was not found.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void dbus_proxy_start(void) {
|
||||
dbus_create_user_dir();
|
||||
|
||||
int status_pipe[2];
|
||||
if (pipe(status_pipe) == -1)
|
||||
errExit("pipe");
|
||||
dbus_proxy_status_fd = status_pipe[0];
|
||||
|
||||
int args_pipe[2];
|
||||
if (pipe(args_pipe) == -1)
|
||||
errExit("pipe");
|
||||
|
||||
dbus_proxy_pid = fork();
|
||||
if (dbus_proxy_pid == -1)
|
||||
errExit("fork");
|
||||
if (dbus_proxy_pid == 0) {
|
||||
int i;
|
||||
for (i = 3; i < FIREJAIL_MAX_FD; i++) {
|
||||
if (i != status_pipe[1] && i != args_pipe[0])
|
||||
close(i); // close open files
|
||||
}
|
||||
char *args[4] = {XDG_DBUS_PROXY_PATH, NULL, NULL, NULL};
|
||||
if (asprintf(&args[1], "--fd=%d", status_pipe[1]) == -1
|
||||
|| asprintf(&args[2], "--args=%d", args_pipe[0]) == -1)
|
||||
errExit("asprintf");
|
||||
if (arg_debug)
|
||||
printf("starting xdg-dbus-proxy\n");
|
||||
sbox_exec_v(SBOX_USER | SBOX_SECCOMP | SBOX_CAPS_NONE | SBOX_KEEP_FDS, args);
|
||||
} else {
|
||||
if (close(status_pipe[1]) == -1 || close(args_pipe[0]) == -1)
|
||||
errExit("close");
|
||||
|
||||
if (arg_dbus_user == DBUS_POLICY_FILTER) {
|
||||
char *user_env = getenv(DBUS_SESSION_BUS_ADDRESS_ENV);
|
||||
if (user_env == NULL) {
|
||||
char *dbus_user_socket = find_user_socket();
|
||||
write_arg(args_pipe[1], DBUS_SOCKET_PATH_PREFIX "%s",
|
||||
dbus_user_socket);
|
||||
free(dbus_user_socket);
|
||||
} else {
|
||||
write_arg(args_pipe[1], "%s", user_env);
|
||||
}
|
||||
if (asprintf(&dbus_user_proxy_socket, DBUS_USER_PROXY_SOCKET_FORMAT,
|
||||
(int) getuid(), (int) getpid()) == -1)
|
||||
errExit("asprintf");
|
||||
write_arg(args_pipe[1], "%s", dbus_user_proxy_socket);
|
||||
write_arg(args_pipe[1], "--filter");
|
||||
write_profile(args_pipe[1], "dbus-user.");
|
||||
}
|
||||
|
||||
if (arg_dbus_system == DBUS_POLICY_FILTER) {
|
||||
char *system_env = getenv(DBUS_SYSTEM_BUS_ADDRESS_ENV);
|
||||
if (system_env == NULL) {
|
||||
write_arg(args_pipe[1],
|
||||
DBUS_SOCKET_PATH_PREFIX DBUS_SYSTEM_SOCKET);
|
||||
} else {
|
||||
write_arg(args_pipe[1], "%s", system_env);
|
||||
}
|
||||
if (asprintf(&dbus_system_proxy_socket, DBUS_SYSTEM_PROXY_SOCKET_FORMAT,
|
||||
(int) getuid(), (int) getpid()) == -1)
|
||||
errExit("asprintf");
|
||||
write_arg(args_pipe[1], "%s", dbus_system_proxy_socket);
|
||||
write_arg(args_pipe[1], "--filter");
|
||||
write_profile(args_pipe[1], "dbus-system.");
|
||||
}
|
||||
|
||||
if (close(args_pipe[1]) == -1)
|
||||
errExit("close");
|
||||
char buf[1];
|
||||
ssize_t read_bytes = read(status_pipe[0], buf, 1);
|
||||
switch (read_bytes) {
|
||||
case -1:
|
||||
errExit("read");
|
||||
break;
|
||||
case 0:
|
||||
fprintf(stderr, "xdg-dbus-proxy closed pipe unexpectedly\n");
|
||||
// Wait for the subordinate process to write any errors to stderr and exit.
|
||||
waitpid(dbus_proxy_pid, NULL, 0);
|
||||
exit(-1);
|
||||
break;
|
||||
case 1:
|
||||
if (arg_debug)
|
||||
printf("xdg-dbus-proxy initialized\n");
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dbus_proxy_stop(void) {
|
||||
if (dbus_proxy_pid == 0)
|
||||
return;
|
||||
assert(dbus_proxy_status_fd >= 0);
|
||||
if (close(dbus_proxy_status_fd) == -1)
|
||||
errExit("close");
|
||||
int status;
|
||||
if (waitpid(dbus_proxy_pid, &status, 0) == -1)
|
||||
errExit("waitpid");
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
|
||||
fwarning("xdg-dbus-proxy returned %s\n", WEXITSTATUS(status));
|
||||
dbus_proxy_pid = 0;
|
||||
dbus_proxy_status_fd = -1;
|
||||
if (dbus_user_proxy_socket != NULL) {
|
||||
free(dbus_user_proxy_socket);
|
||||
dbus_user_proxy_socket = NULL;
|
||||
}
|
||||
if (dbus_system_proxy_socket != NULL) {
|
||||
free(dbus_system_proxy_socket);
|
||||
dbus_system_proxy_socket = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void socket_overlay(char *socket_path, char *proxy_path) {
|
||||
int fd = safe_fd(proxy_path, O_PATH | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
errExit("opening DBus proxy socket");
|
||||
struct stat s;
|
||||
if (fstat(fd, &s) == -1)
|
||||
errExit("fstat");
|
||||
if (!S_ISSOCK(s.st_mode)) {
|
||||
errno = ENOTSOCK;
|
||||
errExit("mounting DBus proxy socket");
|
||||
}
|
||||
char *proxy_fd_path;
|
||||
if (asprintf(&proxy_fd_path, "/proc/self/fd/%d", fd) == -1)
|
||||
errExit("asprintf");
|
||||
if (mount(proxy_path, socket_path, NULL, MS_BIND | MS_REC, NULL) == -1)
|
||||
errExit("mount bind");
|
||||
free(proxy_fd_path);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static char *get_socket_env(const char *name) {
|
||||
char *value = getenv(name);
|
||||
if (value == NULL)
|
||||
return NULL;
|
||||
if (strncmp(value, DBUS_SOCKET_PATH_PREFIX,
|
||||
strlen(DBUS_SOCKET_PATH_PREFIX)) == 0)
|
||||
return value + strlen(DBUS_SOCKET_PATH_PREFIX);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void disable_socket_dir(void) {
|
||||
struct stat s;
|
||||
if (stat(RUN_FIREJAIL_DBUS_DIR, &s) == 0)
|
||||
disable_file_or_dir(RUN_FIREJAIL_DBUS_DIR);
|
||||
}
|
||||
|
||||
void dbus_apply_policy(void) {
|
||||
EUID_ROOT();
|
||||
|
||||
if (arg_dbus_user == DBUS_POLICY_ALLOW && arg_dbus_system == DBUS_POLICY_ALLOW) {
|
||||
disable_socket_dir();
|
||||
return;
|
||||
}
|
||||
|
||||
void dbus_disable(void) {
|
||||
if (!checkcfg(CFG_DBUS)) {
|
||||
disable_socket_dir();
|
||||
fwarning("D-Bus handling is disabled in Firejail configuration file\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char *path;
|
||||
if (asprintf(&path, "/run/user/%d/bus", getuid()) == -1)
|
||||
errExit("asprintf");
|
||||
char *env_var;
|
||||
if (asprintf(&env_var, "unix:path=%s", path) == -1)
|
||||
errExit("asprintf");
|
||||
create_empty_dir_as_root(RUN_DBUS_DIR, 0755);
|
||||
create_empty_file_as_root(RUN_DBUS_USER_SOCKET, 0700);
|
||||
create_empty_file_as_root(RUN_DBUS_SYSTEM_SOCKET, 0700);
|
||||
|
||||
// set a new environment variable: DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/<UID>/bus
|
||||
if (setenv("DBUS_SESSION_BUS_ADDRESS", env_var, 1) == -1) {
|
||||
fprintf(stderr, "Error: cannot modify DBUS_SESSION_BUS_ADDRESS required by --nodbus\n");
|
||||
exit(1);
|
||||
if (arg_dbus_user != DBUS_POLICY_ALLOW) {
|
||||
if (arg_dbus_user == DBUS_POLICY_FILTER) {
|
||||
assert(dbus_user_proxy_socket != NULL);
|
||||
socket_overlay(RUN_DBUS_USER_SOCKET, dbus_user_proxy_socket);
|
||||
free(dbus_user_proxy_socket);
|
||||
}
|
||||
|
||||
char *dbus_user_socket;
|
||||
if (asprintf(&dbus_user_socket, DBUS_USER_SOCKET_FORMAT,
|
||||
(int) getuid()) == -1)
|
||||
errExit("asprintf");
|
||||
disable_file_or_dir(dbus_user_socket);
|
||||
|
||||
char *dbus_user_socket2;
|
||||
if (asprintf(&dbus_user_socket2, DBUS_USER_SOCKET_FORMAT2,
|
||||
(int) getuid()) == -1)
|
||||
errExit("asprintf");
|
||||
disable_file_or_dir(dbus_user_socket2);
|
||||
|
||||
char *user_env = get_socket_env(DBUS_SESSION_BUS_ADDRESS_ENV);
|
||||
if (user_env != NULL && strcmp(user_env, dbus_user_socket) != 0 &&
|
||||
strcmp(user_env, dbus_user_socket2) != 0)
|
||||
disable_file_or_dir(user_env);
|
||||
|
||||
free(dbus_user_socket);
|
||||
free(dbus_user_socket2);
|
||||
|
||||
if (setenv(DBUS_SESSION_BUS_ADDRESS_ENV,
|
||||
DBUS_SOCKET_PATH_PREFIX RUN_DBUS_USER_SOCKET, 1) == -1) {
|
||||
fprintf(stderr, "Error: cannot modify " DBUS_SESSION_BUS_ADDRESS_ENV
|
||||
" required by --dbus-user\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// blacklist the dbus-launch user directory
|
||||
char *path;
|
||||
if (asprintf(&path, "%s/.dbus", cfg.homedir) == -1)
|
||||
errExit("asprintf");
|
||||
disable_file_or_dir(path);
|
||||
free(path);
|
||||
}
|
||||
|
||||
// blacklist the path
|
||||
disable_file_or_dir(path);
|
||||
free(path);
|
||||
free(env_var);
|
||||
if (arg_dbus_system != DBUS_POLICY_ALLOW) {
|
||||
if (arg_dbus_system == DBUS_POLICY_FILTER) {
|
||||
assert(dbus_system_proxy_socket != NULL);
|
||||
socket_overlay(RUN_DBUS_SYSTEM_SOCKET, dbus_system_proxy_socket);
|
||||
free(dbus_system_proxy_socket);
|
||||
}
|
||||
|
||||
disable_file_or_dir(DBUS_SYSTEM_SOCKET);
|
||||
|
||||
// blacklist the dbus-launch user directory
|
||||
if (asprintf(&path, "%s/.dbus", cfg.homedir) == -1)
|
||||
errExit("asprintf");
|
||||
disable_file_or_dir(path);
|
||||
free(path);
|
||||
char *system_env = get_socket_env(DBUS_SYSTEM_BUS_ADDRESS_ENV);
|
||||
if (system_env != NULL && strcmp(system_env, DBUS_SYSTEM_SOCKET) != 0)
|
||||
disable_file_or_dir(system_env);
|
||||
|
||||
// blacklist also system D-Bus socket
|
||||
disable_file_or_dir("/run/dbus/system_bus_socket");
|
||||
if (setenv(DBUS_SYSTEM_BUS_ADDRESS_ENV,
|
||||
DBUS_SOCKET_PATH_PREFIX RUN_DBUS_SYSTEM_SOCKET, 1) == -1) {
|
||||
fprintf(stderr, "Error: cannot modify " DBUS_SYSTEM_BUS_ADDRESS_ENV
|
||||
" required by --dbus-system\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Only disable access to /run/firejail/dbus here, when the sockets have been bind-mounted.
|
||||
disable_socket_dir();
|
||||
|
||||
// look for a possible abstract unix socket
|
||||
|
||||
|
|
|
|||
|
|
@ -340,9 +340,16 @@ extern int arg_memory_deny_write_execute; // block writable and executable memor
|
|||
extern int arg_notv; // --notv
|
||||
extern int arg_nodvd; // --nodvd
|
||||
extern int arg_nou2f; // --nou2f
|
||||
extern int arg_nodbus; // -nodbus
|
||||
extern int arg_deterministic_exit_code; // always exit with first child's exit status
|
||||
|
||||
typedef enum {
|
||||
DBUS_POLICY_ALLOW, // Allow unrestricted access to the bus
|
||||
DBUS_POLICY_FILTER, // Filter with xdg-dbus-proxy
|
||||
DBUS_POLICY_BLOCK // Block access
|
||||
} DbusPolicy;
|
||||
extern DbusPolicy arg_dbus_user; // --dbus-user
|
||||
extern DbusPolicy arg_dbus_system; // --dbus-system
|
||||
|
||||
extern int login_shell;
|
||||
extern int parent_to_child_fds[2];
|
||||
extern int child_to_parent_fds[2];
|
||||
|
|
@ -823,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);
|
||||
|
|
@ -836,7 +846,11 @@ void set_x11_run_file(pid_t pid, int display);
|
|||
void set_profile_run_file(pid_t pid, const char *fname);
|
||||
|
||||
// dbus.c
|
||||
void dbus_disable(void);
|
||||
int dbus_check_name(const char *name);
|
||||
void dbus_check_profile(void);
|
||||
void dbus_proxy_start(void);
|
||||
void dbus_proxy_stop(void);
|
||||
void dbus_apply_policy(void);
|
||||
|
||||
// dhcp.c
|
||||
extern pid_t dhclient4_pid;
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ void fs_blacklist(void) {
|
|||
// whitelist commands handled by fs_whitelist()
|
||||
if (strncmp(entry->data, "whitelist ", 10) == 0 ||
|
||||
strncmp(entry->data, "nowhitelist ", 12) == 0 ||
|
||||
strncmp(entry->data, "dbus-", 5) == 0 ||
|
||||
*entry->data == '\0') {
|
||||
entry = entry->next;
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -144,9 +144,10 @@ int arg_noprofile = 0; // use default.profile if none other found/specified
|
|||
int arg_memory_deny_write_execute = 0; // block writable and executable memory
|
||||
int arg_notv = 0; // --notv
|
||||
int arg_nodvd = 0; // --nodvd
|
||||
int arg_nodbus = 0; // -nodbus
|
||||
int arg_nou2f = 0; // --nou2f
|
||||
int arg_deterministic_exit_code = 0; // always exit with first child's exit status
|
||||
DbusPolicy arg_dbus_user = DBUS_POLICY_ALLOW; // --dbus-user
|
||||
DbusPolicy arg_dbus_system = DBUS_POLICY_ALLOW; // --dbus-system
|
||||
int login_shell = 0;
|
||||
|
||||
//**********************************************************************************
|
||||
|
|
@ -180,6 +181,7 @@ static void myexit(int rv) {
|
|||
|
||||
|
||||
// delete sandbox files in shared memory
|
||||
dbus_proxy_stop();
|
||||
EUID_ROOT();
|
||||
delete_run_files(sandbox_pid);
|
||||
appimage_clear();
|
||||
|
|
@ -2053,8 +2055,70 @@ int main(int argc, char **argv, char **envp) {
|
|||
arg_nodvd = 1;
|
||||
else if (strcmp(argv[i], "--nou2f") == 0)
|
||||
arg_nou2f = 1;
|
||||
else if (strcmp(argv[i], "--nodbus") == 0)
|
||||
arg_nodbus = 1;
|
||||
else if (strcmp(argv[i], "--nodbus") == 0) {
|
||||
arg_dbus_user = DBUS_POLICY_BLOCK;
|
||||
arg_dbus_system = DBUS_POLICY_BLOCK;
|
||||
}
|
||||
else if (strncmp("--dbus-user=", argv[i], 12) == 0) {
|
||||
if (strcmp("filter", argv[i] + 12) == 0) {
|
||||
if (arg_dbus_user == DBUS_POLICY_BLOCK) {
|
||||
fprintf(stderr, "Error: Cannot relax --dbus-user policy, it is already set to block\n");
|
||||
exit(1);
|
||||
}
|
||||
arg_dbus_user = DBUS_POLICY_FILTER;
|
||||
} else if (strcmp("none", argv[i] + 12) == 0) {
|
||||
arg_dbus_user = DBUS_POLICY_BLOCK;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown dbus-user policy: %s\n", argv[i] + 12);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else if (strncmp(argv[i], "--dbus-user.talk=", 17) == 0) {
|
||||
char *line;
|
||||
if (asprintf(&line, "dbus-user.talk %s", argv[i] + 17) == -1)
|
||||
errExit("asprintf");
|
||||
|
||||
profile_check_line(line, 0, NULL); // will exit if something wrong
|
||||
profile_add(line);
|
||||
}
|
||||
else if (strncmp(argv[i], "--dbus-user.own=", 16) == 0) {
|
||||
char *line;
|
||||
if (asprintf(&line, "dbus-user.own %s", argv[i] + 16) == -1)
|
||||
errExit("asprintf");
|
||||
|
||||
profile_check_line(line, 0, NULL); // will exit if something wrong
|
||||
profile_add(line);
|
||||
}
|
||||
else if (strncmp("--dbus-system=", argv[i], 14) == 0) {
|
||||
if (strcmp("filter", argv[i] + 14) == 0) {
|
||||
if (arg_dbus_system == DBUS_POLICY_BLOCK) {
|
||||
fprintf(stderr, "Error: Cannot relax --dbus-system policy, it is already set to block\n");
|
||||
exit(1);
|
||||
}
|
||||
arg_dbus_system = DBUS_POLICY_FILTER;
|
||||
} else if (strcmp("none", argv[i] + 14) == 0) {
|
||||
arg_dbus_system = DBUS_POLICY_BLOCK;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown dbus-system policy: %s\n", argv[i] + 14);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else if (strncmp(argv[i], "--dbus-system.talk=", 19) == 0) {
|
||||
char *line;
|
||||
if (asprintf(&line, "dbus-system.talk %s", argv[i] + 19) == -1)
|
||||
errExit("asprintf");
|
||||
|
||||
profile_check_line(line, 0, NULL); // will exit if something wrong
|
||||
profile_add(line);
|
||||
}
|
||||
else if (strncmp(argv[i], "--dbus-system.own=", 18) == 0) {
|
||||
char *line;
|
||||
if (asprintf(&line, "dbus-system.own %s", argv[i] + 18) == -1)
|
||||
errExit("asprintf");
|
||||
|
||||
profile_check_line(line, 0, NULL); // will exit if something wrong
|
||||
profile_add(line);
|
||||
}
|
||||
|
||||
//*************************************
|
||||
// network
|
||||
|
|
@ -2740,6 +2804,16 @@ int main(int argc, char **argv, char **envp) {
|
|||
}
|
||||
EUID_USER();
|
||||
|
||||
if (checkcfg(CFG_DBUS)) {
|
||||
dbus_check_profile();
|
||||
if (arg_dbus_user == DBUS_POLICY_FILTER ||
|
||||
arg_dbus_system == DBUS_POLICY_FILTER) {
|
||||
EUID_ROOT();
|
||||
dbus_proxy_start();
|
||||
EUID_USER();
|
||||
}
|
||||
}
|
||||
|
||||
// clone environment
|
||||
int flags = CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | SIGCHLD;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,20 @@ void preproc_build_firejail_dir(void) {
|
|||
create_empty_dir_as_root(RUN_FIREJAIL_X11_DIR, 0755);
|
||||
}
|
||||
|
||||
if (stat(RUN_FIREJAIL_DBUS_DIR, &s)) {
|
||||
create_empty_dir_as_root(RUN_FIREJAIL_DBUS_DIR, 0755);
|
||||
if (arg_debug)
|
||||
printf("Remounting the " RUN_FIREJAIL_DBUS_DIR
|
||||
" directory as noexec\n");
|
||||
if (mount(RUN_FIREJAIL_DBUS_DIR, RUN_FIREJAIL_DBUS_DIR, NULL,
|
||||
MS_BIND, NULL) == -1)
|
||||
errExit("mounting " RUN_FIREJAIL_DBUS_DIR);
|
||||
if (mount(NULL, RUN_FIREJAIL_DBUS_DIR, NULL,
|
||||
MS_REMOUNT | MS_BIND | MS_NOSUID | MS_NOEXEC | MS_NODEV,
|
||||
"mode=755,gid=0") == -1)
|
||||
errExit("remounting " RUN_FIREJAIL_DBUS_DIR);
|
||||
}
|
||||
|
||||
if (stat(RUN_FIREJAIL_APPIMAGE_DIR, &s)) {
|
||||
create_empty_dir_as_root(RUN_FIREJAIL_APPIMAGE_DIR, 0755);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ static int check_netoptions(void) {
|
|||
}
|
||||
|
||||
static int check_nodbus(void) {
|
||||
return arg_nodbus != 0;
|
||||
return arg_dbus_user != DBUS_POLICY_ALLOW || arg_dbus_system != DBUS_POLICY_ALLOW;
|
||||
}
|
||||
|
||||
static int check_nosound(void) {
|
||||
|
|
@ -432,11 +432,72 @@ int profile_check_line(char *ptr, int lineno, const char *fname) {
|
|||
return 0;
|
||||
}
|
||||
else if (strcmp(ptr, "nodbus") == 0) {
|
||||
arg_nodbus = 1;
|
||||
arg_dbus_user = DBUS_POLICY_BLOCK;
|
||||
arg_dbus_system = DBUS_POLICY_BLOCK;
|
||||
return 0;
|
||||
}
|
||||
else if (strncmp("dbus-user ", ptr, 10) == 0) {
|
||||
ptr += 10;
|
||||
if (strcmp("filter", ptr) == 0) {
|
||||
if (arg_dbus_user == DBUS_POLICY_BLOCK) {
|
||||
fprintf(stderr, "Error: Cannot relax dbus-user policy, it is already set to block\n");
|
||||
exit(1);
|
||||
}
|
||||
arg_dbus_user = DBUS_POLICY_FILTER;
|
||||
} else if (strcmp("none", ptr) == 0) {
|
||||
arg_dbus_user = DBUS_POLICY_BLOCK;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown dbus-user policy: %s\n", ptr);
|
||||
exit(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else if (strncmp(ptr, "dbus-user.talk ", 15) == 0) {
|
||||
if (!dbus_check_name(ptr + 15)) {
|
||||
printf("Invalid dbus-user.talk name: %s\n", ptr + 15);
|
||||
exit(1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else if (strncmp(ptr, "dbus-user.own ", 14) == 0) {
|
||||
if (!dbus_check_name(ptr + 14)) {
|
||||
fprintf(stderr, "Invalid dbus-user.own name: %s\n", ptr + 14);
|
||||
exit(1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else if (strncmp("dbus-system ", ptr, 12) == 0) {
|
||||
ptr += 12;
|
||||
if (strcmp("filter", ptr) == 0) {
|
||||
if (arg_dbus_system == DBUS_POLICY_BLOCK) {
|
||||
fprintf(stderr, "Error: Cannot relax dbus-system policy, it is already set to block\n");
|
||||
exit(1);
|
||||
}
|
||||
arg_dbus_system = DBUS_POLICY_FILTER;
|
||||
} else if (strcmp("none", ptr) == 0) {
|
||||
arg_dbus_system = DBUS_POLICY_BLOCK;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown dbus-system policy: %s\n", ptr);
|
||||
exit(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else if (strncmp(ptr, "dbus-system.talk ", 17) == 0) {
|
||||
if (!dbus_check_name(ptr + 17)) {
|
||||
fprintf(stderr, "Invalid dbus-system.talk name: %s\n", ptr + 17);
|
||||
exit(1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else if (strncmp(ptr, "dbus-system.own ", 16) == 0) {
|
||||
if (!dbus_check_name(ptr + 16)) {
|
||||
fprintf(stderr, "Invalid dbus-system.own name: %s\n", ptr + 16);
|
||||
exit(1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else if (strcmp(ptr, "nou2f") == 0) {
|
||||
arg_nou2f = 1;
|
||||
arg_nou2f = 1;
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(ptr, "netfilter") == 0) {
|
||||
|
|
|
|||
|
|
@ -932,8 +932,7 @@ int sandbox(void* sandbox_arg) {
|
|||
//****************************
|
||||
// Session D-BUS
|
||||
//****************************
|
||||
if (arg_nodbus)
|
||||
dbus_disable();
|
||||
dbus_apply_policy();
|
||||
|
||||
|
||||
//****************************
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,12 @@ static char *usage_str =
|
|||
#endif
|
||||
" --cpu=cpu-number,cpu-number - set cpu affinity.\n"
|
||||
" --cpu.print=name|pid - print the cpus in use.\n"
|
||||
" --dbus-system=filter|none - set system DBus access policy.\n"
|
||||
" --dbus-system.own=name - allow ownership of name on the system DBus.\n"
|
||||
" --dbus-system.talk-name - allow talking to name on the system DBus.\n"
|
||||
" --dbus-user=filter|none - set session DBus access policy.\n"
|
||||
" --dbus-user.own=name - allow ownership of name on the session DBus.\n"
|
||||
" --dbus-user.talk-name - allow talking to name on the session DBus.\n"
|
||||
" --debug - print sandbox debug messages.\n"
|
||||
" --debug-blacklists - debug blacklisting.\n"
|
||||
" --debug-caps - print all recognized capabilities.\n"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#define RUN_FIREJAIL_NETWORK_DIR RUN_FIREJAIL_DIR "/network"
|
||||
#define RUN_FIREJAIL_BANDWIDTH_DIR RUN_FIREJAIL_DIR "/bandwidth"
|
||||
#define RUN_FIREJAIL_PROFILE_DIR RUN_FIREJAIL_DIR "/profile"
|
||||
#define RUN_FIREJAIL_DBUS_DIR RUN_FIREJAIL_DIR "/dbus"
|
||||
#define RUN_NETWORK_LOCK_FILE RUN_FIREJAIL_DIR "/firejail-network.lock"
|
||||
#define RUN_DIRECTORY_LOCK_FILE RUN_FIREJAIL_DIR "/firejail-run.lock"
|
||||
#define RUN_RO_DIR RUN_FIREJAIL_DIR "/firejail.ro.dir"
|
||||
|
|
@ -56,6 +57,9 @@
|
|||
#define RUN_DHCLIENT_4_LEASES_FILE RUN_DHCLIENT_DIR "/dhclient.leases"
|
||||
#define RUN_DHCLIENT_4_PID_FILE RUN_DHCLIENT_DIR "/dhclient.pid"
|
||||
#define RUN_DHCLIENT_6_PID_FILE RUN_DHCLIENT_DIR "/dhclient6.pid"
|
||||
#define RUN_DBUS_DIR RUN_MNT_DIR "/dbus"
|
||||
#define RUN_DBUS_USER_SOCKET RUN_DBUS_DIR "/user"
|
||||
#define RUN_DBUS_SYSTEM_SOCKET RUN_DBUS_DIR "/system"
|
||||
|
||||
#define RUN_SECCOMP_DIR RUN_MNT_DIR "/seccomp"
|
||||
#define RUN_SECCOMP_LIST RUN_SECCOMP_DIR "/seccomp.list" // list of seccomp files installed
|
||||
|
|
|
|||
|
|
@ -447,7 +447,55 @@ xephyr-screen 640x480
|
|||
.br
|
||||
x11 xephyr
|
||||
|
||||
.SH DBus filtering
|
||||
|
||||
Access to the session and system DBus UNIX sockets can be allowed, filtered or
|
||||
disabled. To disable the abstract sockets (and force applications to use the
|
||||
filtered UNIX socket) you would need to request a new network namespace using
|
||||
\-\-net command. Another option is to remove unix from the \-\-protocol set.
|
||||
.br
|
||||
|
||||
.br
|
||||
Filtering requires installing the xdg-dbus-proxy utility. Filter rules can be
|
||||
specified for well-known DBus names, but they are also propagated to the owning
|
||||
unique name, too. The permissions are "sticky" and are kept even if the
|
||||
corresponding well-know name is released (however, applications rarely release
|
||||
well-known names in practice). Names may have a .* suffix to match all names
|
||||
underneath them, including themselves (e.g. "foo.bar.*" matches "foo.bar",
|
||||
"foo.bar.baz" and "foo.bar.baz.quux", but not "foobar"). For more information,
|
||||
see xdg-dbus-proxy(1).
|
||||
.br
|
||||
|
||||
.br
|
||||
Examples:
|
||||
|
||||
.TP
|
||||
\fBdbus-system filter
|
||||
Enable filtered access to the system DBus. Filters can be specified with the dbus-system.talk and dbus-system.own commands.
|
||||
.TP
|
||||
\fBdbus-system none
|
||||
Disable access to the system DBus. Once access is disabled, it cannot be relaxed to filtering.
|
||||
.TP
|
||||
\fBdbus-system.own org.gnome.ghex.*
|
||||
Allow the application to own the name org.gnome.ghex and all names underneath in on the system DBus.
|
||||
.TP
|
||||
\fBdbus-system.talk org.freedesktop.Notifications
|
||||
Allow the application to talk to the name org.freedesktop.Notifications on the system DBus.
|
||||
.TP
|
||||
\fBdbus-user filter
|
||||
Enable filtered access to the session DBus. Filters can be specified with the dbus-user.talk and dbus-user.own commands.
|
||||
.TP
|
||||
\fBdbus-user none
|
||||
Disable access to the session DBus. Once access is disabled, it cannot be relaxed to filtering.
|
||||
.TP
|
||||
\fBdbus-user.own org.gnome.ghex.*
|
||||
Allow the application to own the name org.gnome.ghex and all names underneath in on the session DBus.
|
||||
.TP
|
||||
\fBdbus-user.talk org.freedesktop.Notifications
|
||||
Allow the application to talk to the name org.freedesktop.Notifications on the session DBus.
|
||||
.TP
|
||||
\fBnodbus \fR(deprecated)
|
||||
Disable D-Bus access (both system and session buses). Equivalent to dbus-system none and dbus-user none.
|
||||
|
||||
.SH Resource limits, CPU affinity, Control Groups
|
||||
These profile entries define the limits on system resources (rlimits) for the processes inside the sandbox.
|
||||
|
|
@ -522,12 +570,6 @@ Disable 3D hardware acceleration.
|
|||
Disable automatic ~/.config/pulse init, for complex setups such as remote
|
||||
pulse servers or non-standard socket paths.
|
||||
.TP
|
||||
\fBnodbus
|
||||
Disable D-Bus access. Only the regular UNIX socket is handled by
|
||||
this command. To disable the abstract socket, you would need to
|
||||
request a new network namespace using the net command. Another
|
||||
option is to remove unix from protocol set.
|
||||
.TP
|
||||
\fBnodvd
|
||||
Disable DVD and audio CD devices.
|
||||
.TP
|
||||
|
|
|
|||
|
|
@ -325,6 +325,112 @@ $ firejail \-\-list
|
|||
.br
|
||||
$ firejail \-\-cpu.print=3272
|
||||
|
||||
.TP
|
||||
\fB\-\-dbus-system=filter|none
|
||||
Set system DBus sandboxing policy.
|
||||
.br
|
||||
|
||||
.br
|
||||
The \fBfilter\fR policy enables the system DBus filter. This option requires
|
||||
installing the xdg-dbus-proxy utility. Permissions for well-known can be
|
||||
specified with the --dbus-system.talk and --dbus-system.own options.
|
||||
.br
|
||||
|
||||
.br
|
||||
The \fBnone\fR policy disables access to the system DBus.
|
||||
.br
|
||||
|
||||
.br
|
||||
Only the regular system DBus UNIX socket is handled by this option. To disable
|
||||
the abstract sockets (and force applications to use the filtered UNIX socket)
|
||||
you would need to request a new network namespace using \-\-net command. Another
|
||||
option is to remove unix from the \-\-protocol set.
|
||||
.br
|
||||
|
||||
.br
|
||||
Example:
|
||||
.br
|
||||
$ firejail \-\-dbus-system=none
|
||||
|
||||
.TP
|
||||
\fB\-\-dbus-system.own=name
|
||||
Allows the application to own the specified well-known name on the system DBus.
|
||||
The name may have a .* suffix to match all names underneath it, including itself
|
||||
(e.g. "foo.bar.*" matches "foo.bar", "foo.bar.baz" and "foo.bar.baz.quux", but
|
||||
not "foobar").
|
||||
.br
|
||||
|
||||
.br
|
||||
Example:
|
||||
.br
|
||||
$ firejail --dbus-system=filter --dbus-system.own=org.gnome.ghex.*
|
||||
|
||||
.TP
|
||||
\fB\-\-dbus-system.talk=name
|
||||
Allows the application to talk to the specified well-known name on the system DBus.
|
||||
The name may have a .* suffix to match all names underneath it, including itself
|
||||
(e.g. "foo.bar.*" matches "foo.bar", "foo.bar.baz" and "foo.bar.baz.quux", but
|
||||
not "foobar").
|
||||
.br
|
||||
|
||||
.br
|
||||
Example:
|
||||
.br
|
||||
$ firejail --dbus-system=filter --dbus-system.talk=org.freedesktop.Notifications
|
||||
|
||||
.TP
|
||||
\fB\-\-dbus-user=filter|none
|
||||
Set session DBus sandboxing policy.
|
||||
.br
|
||||
|
||||
.br
|
||||
The \fBfilter\fR policy enables the session DBus filter. This option requires
|
||||
installing the xdg-dbus-proxy utility. Permissions for well-known names can be
|
||||
added with the --dbus-user.talk and --dbus-user.own options.
|
||||
.br
|
||||
|
||||
.br
|
||||
The \fBnone\fR policy disables access to the session DBus.
|
||||
.br
|
||||
|
||||
.br
|
||||
Only the regular session DBus UNIX socket is handled by this option. To disable
|
||||
the abstract sockets (and force applications to use the filtered UNIX socket)
|
||||
you would need to request a new network namespace using \-\-net command. Another
|
||||
option is to remove unix from the \-\-protocol set.
|
||||
.br
|
||||
|
||||
.br
|
||||
Example:
|
||||
.br
|
||||
$ firejail \-\-dbus-user=none
|
||||
|
||||
.TP
|
||||
\fB\-\-dbus-user.own=name
|
||||
Allows the application to own the specified well-known name on the session DBus.
|
||||
The name may have a .* suffix to match all names underneath it, including itself
|
||||
(e.g. "foo.bar.*" matches "foo.bar", "foo.bar.baz" and "foo.bar.baz.quux", but
|
||||
not "foobar").
|
||||
.br
|
||||
|
||||
.br
|
||||
Example:
|
||||
.br
|
||||
$ firejail --dbus-user=filter --dbus-user.own=org.gnome.ghex.*
|
||||
|
||||
.TP
|
||||
\fB\-\-dbus-user.talk=name
|
||||
Allows the application to talk to the specified well-known name on the session DBus.
|
||||
The name may have a .* suffix to match all names underneath it, including itself
|
||||
(e.g. "foo.bar.*" matches "foo.bar", "foo.bar.baz" and "foo.bar.baz.quux", but
|
||||
not "foobar").
|
||||
.br
|
||||
|
||||
.br
|
||||
Example:
|
||||
.br
|
||||
$ firejail --dbus-user=filter --dbus-user.talk=org.freedesktop.Notifications
|
||||
|
||||
.TP
|
||||
\fB\-\-debug\fR
|
||||
Print debug messages.
|
||||
|
|
@ -1170,12 +1276,8 @@ $ nc dict.org 2628
|
|||
220 pan.alephnull.com dictd 1.12.1/rf on Linux 3.14-1-amd64
|
||||
.br
|
||||
.TP
|
||||
\fB\-\-nodbus
|
||||
Disable D-Bus access (both system and session buses). Only the regular
|
||||
UNIX sockets are handled by this command. To disable the abstract
|
||||
sockets you would need to request a new network namespace using
|
||||
\-\-net command. Another option is to remove unix from \-\-protocol
|
||||
set.
|
||||
\fB\-\-nodbus \fR(deprecated)
|
||||
Disable D-Bus access (both system and session buses). Equivalent to --dbus-system=none --dbus-user=none.
|
||||
.br
|
||||
|
||||
.br
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue