Merge pull request #4229 from smitsohu/whitelist2

Whitelist2
This commit is contained in:
netblue30 2021-05-18 09:00:45 -05:00 committed by GitHub
commit ed7db097bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 540 additions and 976 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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