mirror of
https://github.com/netblue30/firejail.git
synced 2026-05-15 14:16:14 -06:00
commit
ed7db097bd
13 changed files with 540 additions and 976 deletions
|
|
@ -116,6 +116,10 @@
|
|||
# Enable or disable whitelisting support, default enabled.
|
||||
# whitelist yes
|
||||
|
||||
# Disable whitelist top level directories, in addition to those
|
||||
# that are disabled out of the box. None by default; this is an example.
|
||||
# whitelist-disable-topdir /etc,/usr/etc
|
||||
|
||||
# Enable or disable X11 sandboxing support, default enabled.
|
||||
# x11 yes
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ char *xvfb_extra_params = "";
|
|||
char *netfilter_default = NULL;
|
||||
unsigned long join_timeout = 5000000; // microseconds
|
||||
char *config_seccomp_error_action_str = "EPERM";
|
||||
char **whitelist_reject_topdirs = NULL;
|
||||
|
||||
int checkcfg(int val) {
|
||||
assert(val < CFG_MAX);
|
||||
|
|
@ -238,6 +239,31 @@ int checkcfg(int val) {
|
|||
errExit("strdup");
|
||||
}
|
||||
|
||||
else if (strncmp(ptr, "whitelist-disable-topdir ", 25) == 0) {
|
||||
char *str = strdup(ptr + 25);
|
||||
if (!str)
|
||||
errExit("strdup");
|
||||
|
||||
size_t cnt = 0;
|
||||
size_t sz = 4;
|
||||
whitelist_reject_topdirs = malloc(sz * sizeof(char *));
|
||||
if (!whitelist_reject_topdirs)
|
||||
errExit("malloc");
|
||||
|
||||
char *tok = strtok(str, ",");
|
||||
while (tok) {
|
||||
whitelist_reject_topdirs[cnt++] = tok;
|
||||
if (cnt >= sz) {
|
||||
sz *= 2;
|
||||
whitelist_reject_topdirs = realloc(whitelist_reject_topdirs, sz * sizeof(char *));
|
||||
if (!whitelist_reject_topdirs)
|
||||
errExit("realloc");
|
||||
}
|
||||
tok = strtok(NULL, ",");
|
||||
}
|
||||
whitelist_reject_topdirs[cnt] = NULL;
|
||||
}
|
||||
|
||||
else
|
||||
goto errout;
|
||||
|
||||
|
|
|
|||
|
|
@ -131,9 +131,9 @@ void fs_chroot(const char *rootdir) {
|
|||
assert(rootdir);
|
||||
|
||||
// fails if there is any symlink or if rootdir is not a directory
|
||||
int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int parentfd = safer_openat(-1, rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (parentfd == -1)
|
||||
errExit("safe_fd");
|
||||
errExit("safer_openat");
|
||||
// rootdir has to be owned by root and is not allowed to be generally writable,
|
||||
// this also excludes /tmp and friends
|
||||
struct stat s;
|
||||
|
|
@ -215,12 +215,12 @@ void fs_chroot(const char *rootdir) {
|
|||
|
||||
if (arg_debug)
|
||||
printf("Mounting %s on chroot %s\n", orig_pulse, orig_pulse);
|
||||
int src = safe_fd(orig_pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int src = safer_openat(-1, orig_pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (src == -1) {
|
||||
fprintf(stderr, "Error: cannot open %s\n", orig_pulse);
|
||||
exit(1);
|
||||
}
|
||||
int dst = safe_fd(pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int dst = safer_openat(-1, pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (dst == -1) {
|
||||
fprintf(stderr, "Error: cannot open %s\n", pulse);
|
||||
exit(1);
|
||||
|
|
|
|||
|
|
@ -416,7 +416,7 @@ void dbus_proxy_stop(void) {
|
|||
}
|
||||
|
||||
static void socket_overlay(char *socket_path, char *proxy_path) {
|
||||
int fd = safe_fd(proxy_path, O_PATH | O_NOFOLLOW | O_CLOEXEC);
|
||||
int fd = safer_openat(-1, proxy_path, O_PATH | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
errExit("opening DBus proxy socket");
|
||||
struct stat s;
|
||||
|
|
|
|||
|
|
@ -122,26 +122,22 @@ typedef struct interface_t {
|
|||
uint8_t configured;
|
||||
} Interface;
|
||||
|
||||
typedef struct topdir_t {
|
||||
char *path;
|
||||
int fd;
|
||||
} TopDir;
|
||||
|
||||
typedef struct profile_entry_t {
|
||||
struct profile_entry_t *next;
|
||||
char *data; // command
|
||||
|
||||
// whitelist command parameters
|
||||
char *link; // link name - set if the file is a link
|
||||
enum {
|
||||
WLDIR_HOME = 1, // whitelist in home directory
|
||||
WLDIR_TMP, // whitelist in /tmp directory
|
||||
WLDIR_MEDIA, // whitelist in /media directory
|
||||
WLDIR_MNT, // whitelist in /mnt directory
|
||||
WLDIR_VAR, // whitelist in /var directory
|
||||
WLDIR_DEV, // whitelist in /dev directory
|
||||
WLDIR_OPT, // whitelist in /opt directory
|
||||
WLDIR_SRV, // whitelist in /srv directory
|
||||
WLDIR_ETC, // whitelist in /etc directory
|
||||
WLDIR_SHARE, // whitelist in /usr/share directory
|
||||
WLDIR_MODULE, // whitelist in /sys/module directory
|
||||
WLDIR_RUN // whitelist in /run/user/$uid directory
|
||||
} wldir;
|
||||
struct wparam_t {
|
||||
char *file; // resolved file path
|
||||
char *link; // link path
|
||||
TopDir *top; // top level directory
|
||||
} *wparam;
|
||||
|
||||
} ProfileEntry;
|
||||
|
||||
typedef struct config_t {
|
||||
|
|
@ -529,7 +525,7 @@ void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid);
|
|||
unsigned extract_timeout(const char *str);
|
||||
void disable_file_or_dir(const char *fname);
|
||||
void disable_file_path(const char *path, const char *file);
|
||||
int safe_fd(const char *path, int flags);
|
||||
int safer_openat(int dirfd, const char *path, int flags);
|
||||
int has_handler(pid_t pid, int signal);
|
||||
void enter_network_namespace(pid_t pid);
|
||||
int read_pid(const char *name, pid_t *pid);
|
||||
|
|
@ -794,6 +790,7 @@ extern char *xvfb_extra_params;
|
|||
extern char *netfilter_default;
|
||||
extern unsigned long join_timeout;
|
||||
extern char *config_seccomp_error_action_str;
|
||||
extern char **whitelist_reject_topdirs;
|
||||
|
||||
int checkcfg(int val);
|
||||
void print_compiletime_support(void);
|
||||
|
|
|
|||
|
|
@ -453,7 +453,7 @@ void fs_tmpfs(const char *dir, unsigned check_owner) {
|
|||
if (arg_debug)
|
||||
printf("Mounting tmpfs on %s, check owner: %s\n", dir, (check_owner)? "yes": "no");
|
||||
// get a file descriptor for dir, fails if there is any symlink
|
||||
int fd = safe_fd(dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int fd = safer_openat(-1, dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
errExit("while opening directory");
|
||||
struct stat s;
|
||||
|
|
@ -493,7 +493,7 @@ static void fs_remount_simple(const char *path, OPERATION op) {
|
|||
assert(path);
|
||||
|
||||
// open path without following symbolic links
|
||||
int fd1 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
|
||||
int fd1 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd1 == -1)
|
||||
goto out;
|
||||
struct stat s1;
|
||||
|
|
@ -559,7 +559,7 @@ static void fs_remount_simple(const char *path, OPERATION op) {
|
|||
|
||||
// mount --bind -o remount,ro path
|
||||
// need to open path again without following symbolic links
|
||||
int fd2 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
|
||||
int fd2 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd2 == -1)
|
||||
errExit("open");
|
||||
struct stat s2;
|
||||
|
|
@ -992,9 +992,9 @@ void fs_overlayfs(void) {
|
|||
char *firejail;
|
||||
if (asprintf(&firejail, "%s/.firejail", cfg.homedir) == -1)
|
||||
errExit("asprintf");
|
||||
int fd = safe_fd(firejail, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int fd = safer_openat(-1, firejail, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
errExit("safe_fd");
|
||||
errExit("safer_openat");
|
||||
free(firejail);
|
||||
// create basedir if it doesn't exist
|
||||
// the new directory will be owned by root
|
||||
|
|
|
|||
|
|
@ -262,10 +262,10 @@ void fs_private_homedir(void) {
|
|||
if (arg_debug)
|
||||
printf("Mount-bind %s on top of %s\n", private_homedir, homedir);
|
||||
// get file descriptors for homedir and private_homedir, fails if there is any symlink
|
||||
int src = safe_fd(private_homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int src = safer_openat(-1, private_homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (src == -1)
|
||||
errExit("opening private directory");
|
||||
int dst = safe_fd(homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int dst = safer_openat(-1, homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (dst == -1)
|
||||
errExit("opening home directory");
|
||||
// both mount source and target should be owned by the user
|
||||
|
|
@ -576,7 +576,7 @@ void fs_private_home_list(void) {
|
|||
if (arg_debug)
|
||||
printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir);
|
||||
|
||||
int fd = safe_fd(homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int fd = safer_openat(-1, homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
errExit("opening home directory");
|
||||
// home directory should be owned by the user
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -131,7 +131,7 @@ void pulseaudio_init(void) {
|
|||
|
||||
// if ~/.config/pulse exists and there are no symbolic links, mount the new directory
|
||||
// else set environment variable
|
||||
int fd = safe_fd(homeusercfg, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int fd = safer_openat(-1, homeusercfg, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
pulseaudio_fallback(pulsecfg);
|
||||
goto out;
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ static void sanitize_home(void) {
|
|||
if (arg_debug)
|
||||
printf("Cleaning /home directory\n");
|
||||
// open user home directory in order to keep it around
|
||||
int fd = safe_fd(cfg.homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int fd = safer_openat(-1, cfg.homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
goto errout;
|
||||
if (fstat(fd, &s) == -1) { // FUSE
|
||||
|
|
|
|||
|
|
@ -544,11 +544,13 @@ char *split_comma(char *str) {
|
|||
}
|
||||
|
||||
|
||||
// remove consecutive and trailing slashes
|
||||
// and return allocated memory
|
||||
// e.g. /home//user/ -> /home/user
|
||||
// simplify absolute path by removing
|
||||
// 1) consecutive and trailing slashes, and
|
||||
// 2) segments with a single dot
|
||||
// for example /foo//./bar/ -> /foo/bar
|
||||
char *clean_pathname(const char *path) {
|
||||
assert(path);
|
||||
assert(path && path[0] == '/');
|
||||
|
||||
size_t len = strlen(path);
|
||||
char *rv = malloc(len + 1);
|
||||
if (!rv)
|
||||
|
|
@ -557,15 +559,23 @@ char *clean_pathname(const char *path) {
|
|||
size_t i = 0;
|
||||
size_t j = 0;
|
||||
while (path[i]) {
|
||||
while (path[i] == '/' && path[i+1] == '/')
|
||||
i++;
|
||||
if (path[i] == '/') {
|
||||
while (path[i+1] == '/' ||
|
||||
(path[i+1] == '.' && path[i+2] == '/'))
|
||||
i++;
|
||||
}
|
||||
|
||||
rv[j++] = path[i++];
|
||||
}
|
||||
rv[j] = '\0';
|
||||
|
||||
// remove a trailing dot
|
||||
if (j > 1 && rv[j - 1] == '.' && rv[j - 2] == '/')
|
||||
rv[--j] = '\0';
|
||||
|
||||
// remove a trailing slash
|
||||
if (j > 1 && rv[j - 1] == '/')
|
||||
rv[j - 1] = '\0';
|
||||
rv[--j] = '\0';
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
|
@ -905,9 +915,9 @@ int remove_overlay_directory(void) {
|
|||
errExit("fork");
|
||||
if (child == 0) {
|
||||
// open ~/.firejail, fails if there is any symlink
|
||||
int fd = safe_fd(path, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
int fd = safer_openat(-1, path, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
errExit("safe_fd");
|
||||
errExit("safer_openat");
|
||||
// chdir to ~/.firejail
|
||||
if (fchdir(fd) == -1)
|
||||
errExit("fchdir");
|
||||
|
|
@ -1125,13 +1135,13 @@ void disable_file_path(const char *path, const char *file) {
|
|||
}
|
||||
|
||||
// open an existing file without following any symbolic link
|
||||
int safe_fd(const char *path, int flags) {
|
||||
// relative paths are interpreted relative to dirfd
|
||||
// ignore dirfd if path is absolute
|
||||
// https://web.archive.org/web/20180419120236/https://blogs.gnome.org/jamesh/2018/04/19/secure-mounts
|
||||
int safer_openat(int dirfd, const char *path, int flags) {
|
||||
assert(path && path[0]);
|
||||
flags |= O_NOFOLLOW;
|
||||
assert(path);
|
||||
if (*path != '/' || strstr(path, "..")) {
|
||||
fprintf(stderr, "Error: invalid path %s\n", path);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int fd = -1;
|
||||
|
||||
#ifdef __NR_openat2 // kernel 5.6 or better
|
||||
|
|
@ -1139,7 +1149,7 @@ int safe_fd(const char *path, int flags) {
|
|||
memset(&oh, 0, sizeof(oh));
|
||||
oh.flags = flags;
|
||||
oh.resolve = RESOLVE_NO_SYMLINKS;
|
||||
fd = syscall(__NR_openat2, -1, path, &oh, sizeof(struct open_how));
|
||||
fd = syscall(__NR_openat2, dirfd, path, &oh, sizeof(struct open_how));
|
||||
if (fd != -1 || errno != ENOSYS)
|
||||
return fd;
|
||||
#endif
|
||||
|
|
@ -1150,18 +1160,23 @@ int safe_fd(const char *path, int flags) {
|
|||
if (!dup)
|
||||
errExit("strdup");
|
||||
char *tok = strtok(dup, "/");
|
||||
if (!tok) { // root directory
|
||||
if (!tok) { // nothing to do, path is the root directory
|
||||
free(dup);
|
||||
return open("/", flags);
|
||||
return openat(dirfd, path, flags);
|
||||
}
|
||||
char *last_tok = EMPTY_STRING;
|
||||
int parentfd = open("/", O_PATH|O_CLOEXEC);
|
||||
if (parentfd == -1)
|
||||
errExit("open");
|
||||
|
||||
while(1) {
|
||||
int parentfd;
|
||||
if (path[0] == '/')
|
||||
parentfd = open("/", O_PATH|O_CLOEXEC);
|
||||
else
|
||||
parentfd = fcntl(dirfd, F_DUPFD_CLOEXEC, 0);
|
||||
if (parentfd == -1)
|
||||
errExit("open/fcntl");
|
||||
|
||||
while (1) {
|
||||
// open path component, assuming it is a directory; this fails with ENOTDIR if it is a symbolic link
|
||||
// if token is a single dot, the previous directory is reopened
|
||||
// if token is a single dot, the directory referred to by parentfd is reopened
|
||||
fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
// if the following token is NULL, the current token is the final path component
|
||||
|
|
@ -1292,13 +1307,11 @@ pid_t require_pid(const char *name) {
|
|||
// return 1 if there is a link somewhere in path of directory
|
||||
static int has_link(const char *dir) {
|
||||
assert(dir);
|
||||
int fd = safe_fd(dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
if ((errno == ELOOP || errno == ENOTDIR) && is_dir(dir))
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
int fd = safer_openat(-1, dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd != -1)
|
||||
close(fd);
|
||||
else if (errno == ELOOP || (errno == ENOTDIR && is_dir(dir)))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1239,9 +1239,9 @@ void x11_xorg(void) {
|
|||
}
|
||||
}
|
||||
// get a file descriptor for ~/.Xauthority
|
||||
int dst = safe_fd(dest, O_PATH|O_NOFOLLOW|O_CLOEXEC);
|
||||
int dst = safer_openat(-1, dest, O_PATH|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (dst == -1)
|
||||
errExit("safe_fd");
|
||||
errExit("safer_openat");
|
||||
// check if the actual mount destination is a user owned regular file
|
||||
if (fstat(dst, &s) == -1)
|
||||
errExit("fstat");
|
||||
|
|
@ -1263,9 +1263,9 @@ void x11_xorg(void) {
|
|||
fs_remount(RUN_XAUTHORITY_SEC_DIR, MOUNT_NOEXEC, 0);
|
||||
|
||||
// get a file descriptor for the new Xauthority file
|
||||
int src = safe_fd(tmpfname, O_PATH|O_NOFOLLOW|O_CLOEXEC);
|
||||
int src = safer_openat(-1, tmpfname, O_PATH|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (src == -1)
|
||||
errExit("safe_fd");
|
||||
errExit("safer_openat");
|
||||
if (fstat(src, &s) == -1)
|
||||
errExit("fstat");
|
||||
if (!S_ISREG(s.st_mode)) {
|
||||
|
|
@ -1373,7 +1373,7 @@ void fs_x11(void) {
|
|||
char *wx11file;
|
||||
if (asprintf(&wx11file, "%s/X%d", RUN_WHITELIST_X11_DIR, display) == -1)
|
||||
errExit("asprintf");
|
||||
fd = safe_fd(wx11file, O_PATH|O_NOFOLLOW|O_CLOEXEC);
|
||||
fd = safer_openat(-1, wx11file, O_PATH|O_NOFOLLOW|O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
errExit("opening X11 socket");
|
||||
// confirm once more we are mounting a socket
|
||||
|
|
|
|||
|
|
@ -84,18 +84,6 @@
|
|||
#define RUN_DEVLOG_FILE RUN_MNT_DIR "/devlog"
|
||||
|
||||
#define RUN_WHITELIST_X11_DIR RUN_MNT_DIR "/orig-x11"
|
||||
#define RUN_WHITELIST_HOME_USER_DIR RUN_MNT_DIR "/orig-home-user" // home directory whitelisting
|
||||
#define RUN_WHITELIST_RUN_USER_DIR RUN_MNT_DIR "/orig-run-user" // run directory whitelisting
|
||||
#define RUN_WHITELIST_TMP_DIR RUN_MNT_DIR "/orig-tmp"
|
||||
#define RUN_WHITELIST_MEDIA_DIR RUN_MNT_DIR "/orig-media"
|
||||
#define RUN_WHITELIST_MNT_DIR RUN_MNT_DIR "/orig-mnt"
|
||||
#define RUN_WHITELIST_VAR_DIR RUN_MNT_DIR "/orig-var"
|
||||
#define RUN_WHITELIST_DEV_DIR RUN_MNT_DIR "/orig-dev"
|
||||
#define RUN_WHITELIST_OPT_DIR RUN_MNT_DIR "/orig-opt"
|
||||
#define RUN_WHITELIST_SRV_DIR RUN_MNT_DIR "/orig-srv"
|
||||
#define RUN_WHITELIST_ETC_DIR RUN_MNT_DIR "/orig-etc"
|
||||
#define RUN_WHITELIST_SHARE_DIR RUN_MNT_DIR "/orig-share"
|
||||
#define RUN_WHITELIST_MODULE_DIR RUN_MNT_DIR "/orig-module"
|
||||
|
||||
#define RUN_XAUTHORITY_FILE RUN_MNT_DIR "/.Xauthority" // private options
|
||||
#define RUN_XAUTH_FILE RUN_MNT_DIR "/xauth" // x11=xorg
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue