From 482782e22968ccfe3dd53018d94fe33f1e22e01c Mon Sep 17 00:00:00 2001 From: netblue30 Date: Mon, 7 Sep 2015 20:14:06 -0400 Subject: implemented --whitelist option --- src/bash_completion/firejail.bash_completion | 6 +- src/firejail/firejail.h | 7 +- src/firejail/fs.c | 13 +- src/firejail/fs_home.c | 16 --- src/firejail/fs_whitelist.c | 205 +++++++++++++++++++++++++++ src/firejail/main.c | 9 ++ src/firejail/profile.c | 4 + src/firejail/sandbox.c | 10 +- src/firejail/usage.c | 1 + src/man/firejail-login.txt | 4 +- src/man/firejail.txt | 13 +- 11 files changed, 263 insertions(+), 25 deletions(-) create mode 100644 src/firejail/fs_whitelist.c diff --git a/src/bash_completion/firejail.bash_completion b/src/bash_completion/firejail.bash_completion index 50eccf536..98ca5e7a4 100644 --- a/src/bash_completion/firejail.bash_completion +++ b/src/bash_completion/firejail.bash_completion @@ -39,7 +39,11 @@ _firejail() _filedir return 0 ;; - --read-only) + --whitelist) + _filedir + return 0 + ;; + --read-only) _filedir return 0 ;; diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 315a8c7f4..116bd404a 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -28,6 +28,7 @@ #define MNT_DIR "/tmp/firejail/mnt" #define HOME_DIR "/tmp/firejail/mnt/home" #define ETC_DIR "/tmp/firejail/mnt/etc" +#define WHITELIST_HOME_DIR "/tmp/firejail/mnt/whome" #define DEFAULT_USER_PROFILE "generic" #define DEFAULT_ROOT_PROFILE "server" #define MAX_INCLUDE_LEVEL 6 @@ -146,6 +147,7 @@ extern int arg_shell_none; // run the program directly without a shell extern int arg_private_dev; // private dev directory extern int arg_private_etc; // private etc directory extern int arg_scan; // arp-scan all interfaces +extern int arg_whitelist; // whitelist commad extern int parent_to_child_fds[2]; extern int child_to_parent_fds[2]; @@ -186,8 +188,7 @@ void fs_build_firejail_dir(void); // build /tmp/firejail/mnt directory void fs_build_mnt_dir(void); // blacklist files or directoies by mounting empty files on top of them -void fs_blacklist(const char *homedir); -//void fs_blacklist(char **blacklist, const char *homedir); +void fs_blacklist(void); // remount a directory read-only void fs_rdonly(const char *dir); // mount /proc and /sys directories @@ -366,5 +367,7 @@ void run_no_sandbox(int argc, char **argv); void env_store(const char *str); void env_apply(void); +// fs_whitelist.c + #endif diff --git a/src/firejail/fs.c b/src/firejail/fs.c index 14c76a144..e7388a539 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c @@ -242,8 +242,11 @@ static void globbing(OPERATION op, const char *pattern, const char *noblacklist[ globfree(&globbuf); } + // blacklist files or directoies by mounting empty files on top of them -void fs_blacklist(const char *homedir) { +void fs_blacklist(void) { + char *homedir = cfg.homedir; + assert(homedir); ProfileEntry *entry = cfg.profile; if (!entry) return; @@ -261,7 +264,13 @@ void fs_blacklist(const char *homedir) { OPERATION op = OPERATION_MAX; char *ptr; - // process blacklist command + // whitelist commands handled by fs_whitelist() + if (strncmp(entry->data, "whitelist ", 10) == 0 || *entry->data == '\0') { + entry = entry->next; + continue; + } + + // process bind command if (strncmp(entry->data, "bind ", 5) == 0) { char *dname1 = entry->data + 5; char *dname2 = split_comma(dname1); diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c index 98d62b685..714417867 100644 --- a/src/firejail/fs_home.c +++ b/src/firejail/fs_home.c @@ -159,7 +159,6 @@ static void copy_xauthority(void) { // private mode (--private=homedir): // mount homedir on top of /home/user, // tmpfs on top of /root in nonroot mode, -// tmpfs on top of /tmp in root mode, // set skel files, // restore .Xauthority void fs_private_homedir(void) { @@ -214,7 +213,6 @@ void fs_private_homedir(void) { // private mode (--private): // mount tmpfs over /home/user, // tmpfs on top of /root in nonroot mode, -// tmpfs on top of /tmp in root mode // set skel files, // restore .Xauthority void fs_private(void) { @@ -353,20 +351,6 @@ void fs_check_private_dir(void) { } } -#if 0 -static int mkpath(char* file_path, mode_t mode) { - assert(file_path && *file_path); - char* p; - for (p=strchr(file_path+1, '/'); p; p=strchr(p+1, '/')) { - *p='\0'; - if (mkdir(file_path, mode)==-1) { - if (errno!=EEXIST) { *p='/'; return -1; } - } - *p='/'; - } - return 0; -} -#endif static void duplicate(char *name) { char *cmd; diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c new file mode 100644 index 000000000..fac08705d --- /dev/null +++ b/src/firejail/fs_whitelist.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2014, 2015 Firejail Authors + * + * This file is part of firejail project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "firejail.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static int mkpath(const char* path, mode_t mode) { + assert(path && *path); + + // work on a copy of the path + char *file_path = strdup(path); + if (!file_path) + errExit("strdup"); + + char* p; + for (p=strchr(file_path+1, '/'); p; p=strchr(p+1, '/')) { + *p='\0'; + if (mkdir(file_path, mode)==-1) { + if (errno != EEXIST) { + *p='/'; + free(file_path); + return -1; + } + } + else { +// TODO: set correct directory mode and properties + } + + *p='/'; + } + + free(file_path); + return 0; +} + +static void whitelist_path(const char *path) { + assert(path); + + // fname needs to start with /home/username + if (strncmp(path, cfg.homedir, strlen(cfg.homedir))) { + fprintf(stderr, "Error: file %s is not in user home directory, exiting...\n", path); + exit(1); + } + + const char *fname = path + strlen(cfg.homedir); + if (*fname == '\0') { + fprintf(stderr, "Error: file %s is not in user home directory, exiting...\n", path); + exit(1); + } + + char *wfile; + if (asprintf(&wfile, "%s/%s", WHITELIST_HOME_DIR, fname) == -1) + errExit("asprintf"); + + // check if the file exists + struct stat s; + if (stat(wfile, &s) == 0) { + if (arg_debug) + printf("Whitelisting %s\n", path); + } + else { + if (arg_debug) { + fprintf(stderr, "Warning: %s is an invalid file, skipping...\n", path); + } + return; + } + + // create the path if necessary + mkpath(path, 0755); + + // process directory + if (S_ISDIR(s.st_mode)) { + // create directory + int rv = mkdir(path, 0755); + if (rv == -1) + errExit("mkdir"); + + } + + // process regular file + else { + // create an empty file + FILE *fp = fopen(path, "w"); + if (!fp) { + fprintf(stderr, "Error: cannot create empty file in home directory\n"); + exit(1); + } + fclose(fp); + } + + // set file properties + if (chown(path, s.st_uid, s.st_gid) < 0) + errExit("chown"); + if (chmod(path, s.st_mode) < 0) + errExit("chmod"); + + // mount + if (mount(wfile, path, NULL, MS_BIND|MS_REC, NULL) < 0) + errExit("mount bind"); + + free(wfile); +} + + +// whitelist for /home/user directory +void fs_whitelist(void) { + char *homedir = cfg.homedir; + assert(homedir); + ProfileEntry *entry = cfg.profile; + if (!entry) + return; + + // realpath function will fail with ENOENT if the file is not found + // we need to expand the path before installing a new, empty home directory + while (entry) { + // handle only whitelist commands + if (strncmp(entry->data, "whitelist ", 10)) { + entry = entry->next; + continue; + } + + char *new_name = expand_home(entry->data + 10, cfg.homedir); + assert(new_name); + char *fname = realpath(new_name, NULL); + free(new_name); + if (fname) { + // change file name in entry->data + if (strcmp(fname, entry->data + 10) != 0) { + char *newdata; + if (asprintf(&newdata, "whitelist %s", fname) == -1) + errExit("asprintf"); + entry->data = newdata; + if (arg_debug) + printf("Replaced whitelist path: %s\n", entry->data); + } + + free(fname); + } + else { + // file not found, blank the entry in the list + if (arg_debug) + printf("Removed whitelist path: %s\n", entry->data); + *entry->data = '\0'; + } + entry = entry->next; + } + + // create /tmp/firejail/mnt/whome directory + fs_build_mnt_dir(); + int rv = mkdir(WHITELIST_HOME_DIR, S_IRWXU | S_IRWXG | S_IRWXO); + if (rv == -1) + errExit("mkdir"); + if (chown(WHITELIST_HOME_DIR, getuid(), getgid()) < 0) + errExit("chown"); + if (chmod(WHITELIST_HOME_DIR, 0755) < 0) + errExit("chmod"); + + // keep a copy of real home dir in /tmp/firejail/mnt/whome + if (mount(cfg.homedir, WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) + errExit("mount bind"); + + // start building the new home directory by mounting a tmpfs fielsystem + fs_private(); + + // go through profile rules again, and interpret whitelist commands + entry = cfg.profile; + while (entry) { + // handle only whitelist commands + if (strncmp(entry->data, "whitelist ", 10)) { + entry = entry->next; + continue; + } + + whitelist_path(entry->data + 10); + + entry = entry->next; + } + + // mask the real home directory, currently mounted on /tmp/firejail/mnt/whome + if (mount("tmpfs", WHITELIST_HOME_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) + errExit("mount tmpfs"); +} diff --git a/src/firejail/main.c b/src/firejail/main.c index 43a468c46..60c2a7cec 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -82,6 +82,7 @@ int arg_shell_none = 0; // run the program directly without a shell int arg_private_dev = 0; // private dev directory int arg_private_etc = 0; // private etc directory int arg_scan = 0; // arp-scan all interfaces +int arg_whitelist = 0; // whitelist commad int parent_to_child_fds[2]; int child_to_parent_fds[2]; @@ -581,6 +582,14 @@ int main(int argc, char **argv) { profile_check_line(line, 0); // will exit if something wrong profile_add(line); } + else if (strncmp(argv[i], "--whitelist=", 12) == 0) { + char *line; + if (asprintf(&line, "whitelist %s", argv[i] + 12) == -1) + errExit("asprintf"); + + profile_check_line(line, 0); // will exit if something wrong + profile_add(line); + } else if (strncmp(argv[i], "--read-only=", 12) == 0) { char *line; if (asprintf(&line, "read-only %s", argv[i] + 12) == -1) diff --git a/src/firejail/profile.c b/src/firejail/profile.c index 778478321..2863b454e 100644 --- a/src/firejail/profile.c +++ b/src/firejail/profile.c @@ -336,6 +336,10 @@ int profile_check_line(char *ptr, int lineno) { ptr += 10; else if (strncmp(ptr, "noblacklist ", 12) == 0) ptr += 12; + else if (strncmp(ptr, "whitelist ", 10) == 0) { + arg_whitelist = 1; + ptr += 10; + } else if (strncmp(ptr, "read-only ", 10) == 0) ptr += 10; else if (strncmp(ptr, "tmpfs ", 6) == 0) diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c index 53782a288..41fc20084 100644 --- a/src/firejail/sandbox.c +++ b/src/firejail/sandbox.c @@ -232,8 +232,14 @@ int sandbox(void* sandbox_arg) { //**************************** // apply the profile file //**************************** - if (cfg.profile) - fs_blacklist(cfg.homedir); + if (cfg.profile) { + // apply all whitelist commands ... + if (arg_whitelist) + fs_whitelist(); + + // ... followed by blacklist commands + fs_blacklist(); + } //**************************** // private mode diff --git a/src/firejail/usage.c b/src/firejail/usage.c index fbb36fad7..1f611001d 100644 --- a/src/firejail/usage.c +++ b/src/firejail/usage.c @@ -227,6 +227,7 @@ void usage(void) { printf("\t--trace - trace open, access and connect system calls.\n\n"); printf("\t--tree - print a tree of all sandboxed processes.\n\n"); printf("\t--version - print program version and exit.\n\n"); + printf("\t--whitelist=dirname_or_filename - whitelist directory or file.\n\n"); printf("\t--zsh - use /usr/bin/zsh as default shell.\n\n"); printf("\n"); printf("\n"); diff --git a/src/man/firejail-login.txt b/src/man/firejail-login.txt index 1d6a8d80e..c3ee50ecf 100644 --- a/src/man/firejail-login.txt +++ b/src/man/firejail-login.txt @@ -16,9 +16,11 @@ Example: .SH RESTRICTED SHELL To configure a restricted shell, replace /bin/bash with /usr/bin/firejail in /etc/password file for each user that needs to be restricted. Alternatively, -you can specify /usr/bin/firejail in adduser command: +you can specify /usr/bin/firejail using adduser or usermod commands: adduser \-\-shell /usr/bin/firejail username +.br +usermod \-\-shell /usr/bin/firejail username .SH FILES /etc/firejail/login.users diff --git a/src/man/firejail.txt b/src/man/firejail.txt index cfd00456b..4bf537c95 100644 --- a/src/man/firejail.txt +++ b/src/man/firejail.txt @@ -109,7 +109,7 @@ $ firejail \-\-blacklist=/sbin \-\-blacklist=/usr/sbin .br $ firejail \-\-blacklist=~/.mozilla .br -$ firejail "\-\-blacklist=My Virtual Machines" +$ firejail "\-\-blacklist=/home/username/My Virtual Machines" .TP \fB\-c Execute command and exit. @@ -1091,6 +1091,17 @@ $ firejail \-\-version .br firejail version 0.9.27 .TP +\fB\-\-whitelist=dirname_or_filename +Whitelist directory or file. Only files in user home directory are accepted. +.br + +.br +Example: +.br +$ firejail \-\-whitelist=~/.mozilla \-\-whitelist=~/Downloads +.br +$ firejail "\-\-whitelist=/home/username/My Virtual Machines" +.TP \fB\-\-zsh Use /usr/bin/zsh as default user shell. .br -- cgit v1.2.3-54-g00ecf