diff --git a/.gitignore b/.gitignore index 0c803b1358c..cbb1b2e8367 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ firejail-users.5 firejail.1 firemon.1 firecfg.1 +jailtest.5 mkdeb.sh src/firejail/firejail src/firemon/firemon @@ -40,6 +41,7 @@ src/fbuilder/fbuilder src/profstats/profstats src/bash_completion/firejail.bash_completion src/zsh_completion/_firejail +src/jailtest/jailtest uids.h seccomp seccomp.debug diff --git a/Makefile.in b/Makefile.in index 593afdacf65..b0deee03b21 100644 --- a/Makefile.in +++ b/Makefile.in @@ -23,13 +23,13 @@ endif COMPLETIONDIRS = src/zsh_completion src/bash_completion all: all_items mydirs $(MAN_TARGET) filters -APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats +APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats src/jailtest/jailtest SBOX_APPS = src/faudit/faudit src/fbuilder/fbuilder src/ftee/ftee SBOX_APPS_NON_DUMPABLE = src/fcopy/fcopy src/fldd/fldd src/fnet/fnet src/fnetfilter/fnetfilter MYDIRS = src/lib $(MAN_SRC) $(COMPLETIONDIRS) MYLIBS = src/libpostexecseccomp/libpostexecseccomp.so src/libtrace/libtrace.so src/libtracelog/libtracelog.so COMPLETIONS = src/zsh_completion/_firejail src/bash_completion/firejail.bash_completion -MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 firejail-users.5 +MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 firejail-users.5 jailtest.5 SBOX_APPS_NON_DUMPABLE += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp SECCOMP_FILTERS = seccomp seccomp.debug seccomp.32 seccomp.block_secondary seccomp.mdwx seccomp.mdwx.32 ALL_ITEMS = $(APPS) $(SBOX_APPS) $(SBOX_APPS_NON_DUMPABLE) $(MYLIBS) @@ -109,6 +109,8 @@ endif install -m 0755 src/firemon/firemon $(DESTDIR)$(bindir) # firecfg executable install -m 0755 src/firecfg/firecfg $(DESTDIR)$(bindir) + # jailtest executable + install -m 0755 src/jailtest/jailtest $(DESTDIR)$(bindir) # libraries and plugins install -m 0755 -d $(DESTDIR)$(libdir)/firejail install -m 0644 -t $(DESTDIR)$(libdir)/firejail $(MYLIBS) $(SECCOMP_FILTERS) src/firecfg/firecfg.config @@ -177,6 +179,7 @@ uninstall: rm -f $(DESTDIR)$(bindir)/firemon rm -f $(DESTDIR)$(bindir)/firecfg rm -fr $(DESTDIR)$(libdir)/firejail + rm -fr $(DESTDIR)$(libdir)/jailtest rm -fr $(DESTDIR)$(datarootdir)/doc/firejail for man in $(MANPAGES); do \ rm -f $(DESTDIR)$(mandir)/man5/$$man*; \ diff --git a/README.md b/README.md index 4e0d2a91a4c..3c8c6afb86b 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,84 @@ We also keep a list of profile fixes for previous released versions in [etc-fixe Milestone page: https://github.com/netblue30/firejail/milestone/1 Release discussion: https://github.com/netblue30/firejail/issues/3696 +### jailtest +````` +JAILTEST(1) JAILTEST man page JAILTEST(1) + +NAME + jailtest - Simple utility program to test running sandboxes + +SYNOPSIS + sudo jailtest [OPTIONS] [directory] + +DESCRIPTION + WORK IN PROGRESS! jailtest attaches itself to all sandboxes started by + the user and performs some basic tests on the sandbox filesystem: + + 1. Virtual directories + jailtest extracts a list with the main virtual directories in‐ + stalled by the sandbox. These directories are build by firejail + at startup using --private* and --whitelist commands. + + 2. Noexec test + jailtest inserts executable programs in /home/username, /tmp, + and /var/tmp directories and tries to run them form inside the + sandbox, thus testing if the directory is executable or not. + + 3. Read access test + jailtest creates test files in the directories specified by the + user and tries to read them from inside the sandbox. + + The program is running as root exclusively under sudo. + +OPTIONS + --debug + Print debug messages + + -?, --help + Print options end exit. + --version + Print program version and exit. + + [directory] + One or more directories in user home to test for read access. + +OUTPUT + For each sandbox detected we print the following line: + + PID:USER:Sandbox Name:Command + + It is followed by relevant sandbox information, such as the virtual di‐ + rectories and various warnings. + +EXAMPLE + $ sudo jailtest ~/.ssh ~/.gnupg + 1429:netblue::/usr/bin/firejail /opt/firefox/firefox + Virtual dirs: /home/netblue, /tmp, /var/tmp, /dev, /etc, + 5602:netblue::/usr/bin/firejail /usr/bin/ssh netblue@x.y.z.net + Virtual dirs: /var/tmp, /dev, + Warning: I can read ~/.ssh + 5926:netblue::/usr/bin/firejail /usr/bin/gimp-2.10 + Virtual dirs: /tmp, /var/tmp, /dev, + Warning: I can run programs in /home/netblue + 6394:netblue:libreoffice:/usr/bin/firejail libreoffice + Virtual dirs: /tmp, /var/tmp, /dev, + +LICENSE + 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. + + Homepage: https://firejail.wordpress.com + +SEE ALSO + firejail(1), firecfg(1), firejail-profile(5), firejail-login(5) fire‐ + jail-users(5) + +0.9.65 Feb 2021 JAILTEST(1) +````` ### Profile Statistics diff --git a/configure b/configure index fa240107011..84bcafaf7a8 100755 --- a/configure +++ b/configure @@ -4269,7 +4269,7 @@ fi ac_config_files="$ac_config_files mkdeb.sh" -ac_config_files="$ac_config_files Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile src/ftee/Makefile src/faudit/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile" +ac_config_files="$ac_config_files Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile src/ftee/Makefile src/faudit/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile src/jailtest/Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure @@ -5000,7 +5000,10 @@ do "src/fsec-optimize/Makefile") CONFIG_FILES="$CONFIG_FILES src/fsec-optimize/Makefile" ;; "src/profstats/Makefile") CONFIG_FILES="$CONFIG_FILES src/profstats/Makefile" ;; "src/man/Makefile") CONFIG_FILES="$CONFIG_FILES src/man/Makefile" ;; + "src/zsh_completion/Makefile") CONFIG_FILES="$CONFIG_FILES src/zsh_completion/Makefile" ;; + "src/bash_completion/Makefile") CONFIG_FILES="$CONFIG_FILES src/bash_completion/Makefile" ;; "test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;; + "src/jailtest/Makefile") CONFIG_FILES="$CONFIG_FILES src/jailtest/Makefile" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac diff --git a/configure.ac b/configure.ac index aa2d0fb6bdb..b2e9a7b8642 100644 --- a/configure.ac +++ b/configure.ac @@ -234,7 +234,8 @@ AC_CONFIG_FILES([mkdeb.sh], [chmod +x mkdeb.sh]) AC_OUTPUT(Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile \ src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile \ src/ftee/Makefile src/faudit/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile \ -src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile) +src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile \ +src/jailtest/Makefile) echo echo "Configuration options:" diff --git a/src/jailtest/Makefile.in b/src/jailtest/Makefile.in new file mode 100644 index 00000000000..9c9c0c50800 --- /dev/null +++ b/src/jailtest/Makefile.in @@ -0,0 +1,14 @@ +all: jailtest + +include ../common.mk + +%.o : %.c $(H_FILE_LIST) ../include/common.h ../include/pid.h + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@ + +jailtest: $(OBJS) + $(CC) $(LDFLAGS) -o $@ $(OBJS) ../lib/common.o ../lib/pid.o $(LIBS) $(EXTRA_LDFLAGS) + +clean:; rm -fr *.o jailtest *.gcov *.gcda *.gcno *.plist + +distclean: clean + rm -fr Makefile diff --git a/src/jailtest/access.c b/src/jailtest/access.c new file mode 100644 index 00000000000..e68227bd2bf --- /dev/null +++ b/src/jailtest/access.c @@ -0,0 +1,124 @@ +#include "jailtest.h" +#include +#include + +typedef struct { + char *tfile; + char *tdir; +} TestDir; + +#define MAX_TEST_FILES 16 +TestDir td[MAX_TEST_FILES]; +static int files_cnt = 0; + +void access_setup(const char *directory) { + // I am root! + assert(directory); + assert(user_home_dir); + + if (files_cnt >= MAX_TEST_FILES) { + fprintf(stderr, "Error: maximum number of test directories exceded\n"); + exit(1); + } + + char *fname = strdup(directory); + if (!fname) + errExit("strdup"); + if (strncmp(fname, "~/", 2) == 0) { + free(fname); + if (asprintf(&fname, "%s/%s", user_home_dir, directory + 2) == -1) + errExit("asprintf"); + } + + char *path = realpath(fname, NULL); + free(fname); + if (path == NULL) { + fprintf(stderr, "Warning: invalid directory %s, skipping...\n", directory); + return; + } + + // file in home directory + if (strncmp(path, user_home_dir, strlen(user_home_dir)) != 0) { + fprintf(stderr, "Warning: file %s is not in user home directory, skipping...\n", directory); + free(path); + return; + } + + // try to open the dir as root + DIR *dir = opendir(path); + if (!dir) { + fprintf(stderr, "Warning: directory %s not found, skipping\n", directory); + free(path); + return; + } + closedir(dir); + + // create a test file + char *test_file; + if (asprintf(&test_file, "%s/jailtest-access-%d", path, getpid()) == -1) + errExit("asprintf"); + + FILE *fp = fopen(test_file, "w"); + if (!fp) { + printf("Warning: I cannot create test file in directory %s, skipping...\n", directory); + return; + } + fprintf(fp, "this file was created by firetest utility, you can safely delete it\n"); + fclose(fp); + int rv = chown(test_file, user_uid, user_gid); + if (rv) + errExit("chown"); + + char *dname = strdup(directory); + if (!dname) + errExit("strdup"); + td[files_cnt].tdir = dname; + td[files_cnt].tfile = test_file; + files_cnt++; +} + +void access_destroy(void) { + // remove test files + int i; + + for (i = 0; i < files_cnt; i++) { + int rv = unlink(td[i].tfile); + (void) rv; + } + files_cnt = 0; +} + +void access_test(void) { + // I am root in sandbox mount namespace + assert(user_uid); + int i; + + pid_t child = fork(); + if (child == -1) + errExit("fork"); + + if (child == 0) { // child + // drop privileges + if (setgid(user_gid) != 0) + errExit("setgid"); + if (setuid(user_uid) != 0) + errExit("setuid"); + + for (i = 0; i < files_cnt; i++) { + assert(td[i].tfile); + + // try to open the file for reading + FILE *fp = fopen(td[i].tfile, "r"); + if (fp) { + + printf(" Warning: I can read %s\n", td[i].tdir); + fclose(fp); + } + } + exit(0); + } + + // wait for the child to finish + int status; + wait(&status); +} diff --git a/src/jailtest/jailtest.h b/src/jailtest/jailtest.h new file mode 100644 index 00000000000..678f94befc6 --- /dev/null +++ b/src/jailtest/jailtest.h @@ -0,0 +1,32 @@ +#ifndef JAILTEST_H +#define JAILTEST_H + +#include "../include/common.h" + +// main.c +extern uid_t user_uid; +extern gid_t user_gid; +extern char *user_name; +extern char *user_home_dir; + +// access.c +void access_setup(const char *directory); +void access_test(void); +void access_destroy(void); + +// noexec.c +void noexec_setup(void); +void noexec_test(const char *msg); + +// virtual.c +void virtual_setup(const char *directory); +void virtual_destroy(void); +void virtual_test(void); + +// utils.c +char *get_sudo_user(void); +char *get_homedir(const char *user, uid_t *uid, gid_t *gid); +int find_child(pid_t parent, pid_t *child); +pid_t switch_to_child(pid_t pid); + +#endif \ No newline at end of file diff --git a/src/jailtest/main.c b/src/jailtest/main.c new file mode 100644 index 00000000000..78f162706d9 --- /dev/null +++ b/src/jailtest/main.c @@ -0,0 +1,134 @@ +#include "jailtest.h" +#include "../include/firejail_user.h" +#include "../include/pid.h" +#include + +uid_t user_uid = 0; +gid_t user_gid = 0; +char *user_name = NULL; +char *user_home_dir = NULL; +int arg_debug = 0; + +static char *usage_str = + "Usage: jailtest [options] directory [directory]\n\n" + "Options:\n" + " --debug - print debug messages.\n" + " --help, -? - this help screen.\n" + " --version - print program version and exit.\n"; + + +static void usage(void) { + printf("firetest - version %s\n\n", VERSION); + puts(usage_str); +} + +static void cleanup(void) { + // running only as root + if (getuid() == 0) { + if (arg_debug) + printf("cleaning up!\n"); + access_destroy(); + virtual_destroy(); + } +} + +int main(int argc, char **argv) { + int i; + int findex = 0; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "--help") == 0) { + usage(); + return 0; + } + else if (strcmp(argv[i], "--version") == 0) { + printf("firetest version %s\n\n", VERSION); + return 0; + } + else if (strncmp(argv[i], "--hello=", 8) == 0) { // used by noexec test + printf(" Warning: I can run programs in %s\n", argv[i] + 8); + return 0; + } + else if (strcmp(argv[i], "--debug") == 0) + arg_debug = 1; + else if (strncmp(argv[i], "--", 2) == 0) { + fprintf(stderr, "Error: invalid option\n"); + return 1; + } + else { + findex = i; + break; + } + } + + // user setup + if (getuid() != 0) { + fprintf(stderr, "Error: you need to be root (via sudo) to run this program\n"); + exit(1); + } + user_name = get_sudo_user(); + assert(user_name); + user_home_dir = get_homedir(user_name, &user_uid, &user_gid); + if (user_uid == 0) { + fprintf(stderr, "Error: root user not supported\n"); + exit(1); + } + + // test setup + atexit(cleanup); + if (findex > 0) { + for (i = findex; i < argc; i++) + access_setup(argv[i]); + } + + noexec_setup(); + virtual_setup(user_home_dir); + virtual_setup("/tmp"); + virtual_setup("/var/tmp"); + virtual_setup("/dev"); + virtual_setup("/etc"); + virtual_setup("/bin"); + + // print processes + pid_read(0); + for (i = 0; i < max_pids; i++) { + if (pids[i].level == 1) { + uid_t uid = pid_get_uid(i); + if (uid != user_uid) // not interested in other user sandboxes + continue; + + // in case the pid is that of a firejail process, use the pid of the first child process + uid_t pid = switch_to_child(i); + pid_print_list(i, 0); // no wrapping + + pid_t child = fork(); + if (child == -1) + errExit("fork"); + if (child == 0) { + int rv = join_namespace(pid, "mnt"); + if (rv == 0) { + virtual_test(); + noexec_test(user_home_dir); + noexec_test("/tmp"); + noexec_test("/var/tmp"); + access_test(); + } + else { + printf(" Error: I cannot join the process mount space\n"); + exit(1); + } + + // drop privileges in order not to trigger cleanup() + if (setgid(user_gid) != 0) + errExit("setgid"); + if (setuid(user_uid) != 0) + errExit("setuid"); + return 0; + } + int status; + wait(&status); + } + } + + return 0; +} diff --git a/src/jailtest/noexec.c b/src/jailtest/noexec.c new file mode 100644 index 00000000000..d2f85514ab7 --- /dev/null +++ b/src/jailtest/noexec.c @@ -0,0 +1,94 @@ +#include "jailtest.h" +#include +#include +#include + +static unsigned char *execfile = NULL; +static int execfile_len = 0; + +void noexec_setup(void) { + // grab a copy of myself + char *self = realpath("/proc/self/exe", NULL); + if (self) { + struct stat s; + if (access(self, X_OK) == 0 && stat(self, &s) == 0) { + assert(s.st_size); + execfile = malloc(s.st_size); + + int fd = open(self, O_RDONLY); + if (fd == -1) + errExit("open"); + int len = 0; + do { + int rv = read(fd, execfile + len, s.st_size - len); + if (rv == -1) + errExit("read"); + if (rv == 0) { + // something went wrong! + free(execfile); + execfile = NULL; + printf("Warning: I cannot grab a copy of myself, skipping noexec test...\n"); + break; + } + len += rv; + } + while (len < s.st_size); + execfile_len = s.st_size; + close(fd); + } + } +} + + +void noexec_test(const char *path) { + assert(user_uid); + + // I am root in sandbox mount namespace + if (!execfile) + return; + + char *fname; + if (asprintf(&fname, "%s/jailtest-noexec-%d", path, getpid()) == -1) + errExit("asprintf"); + + pid_t child = fork(); + if (child == -1) + errExit("fork"); + + if (child == 0) { // child + // drop privileges + if (setgid(user_gid) != 0) + errExit("setgid"); + if (setuid(user_uid) != 0) + errExit("setuid"); + int fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0700); + if (fd == -1) { + printf(" I cannot create files in %s, skipping noexec...\n", path); + exit(1); + } + + int len = 0; + while (len < execfile_len) { + int rv = write(fd, execfile + len, execfile_len - len); + if (rv == -1 || rv == 0) { + printf(" I cannot create files in %s, skipping noexec....\n", path); + exit(1); + } + len += rv; + } + fchmod(fd, 0700); + close(fd); + + char *arg; + if (asprintf(&arg, "--hello=%s", path) == -1) + errExit("asprintf"); + int rv = execl(fname, fname, arg, NULL); + (void) rv; // if we get here execl failed + exit(0); + } + + int status; + wait(&status); + int rv = unlink(fname); + (void) rv; +} \ No newline at end of file diff --git a/src/jailtest/utils.c b/src/jailtest/utils.c new file mode 100644 index 00000000000..b24783355df --- /dev/null +++ b/src/jailtest/utils.c @@ -0,0 +1,124 @@ +#include "jailtest.h" +#include +#include +#include + +#define BUFLEN 4096 + +char *get_sudo_user(void) { + char *user = getenv("SUDO_USER"); + if (!user) { + user = getpwuid(getuid())->pw_name; + if (!user) { + fprintf(stderr, "Error: cannot detect login user\n"); + exit(1); + } + } + + return user; +} + +char *get_homedir(const char *user, uid_t *uid, gid_t *gid) { + // find home directory + struct passwd *pw = getpwnam(user); + if (!pw) + goto errexit; + + char *home = pw->pw_dir; + if (!home) + goto errexit; + + *uid = pw->pw_uid; + *gid = pw->pw_gid; + + return home; + +errexit: + fprintf(stderr, "Error: cannot find home directory for user %s\n", user); + exit(1); +} + +int find_child(pid_t parent, pid_t *child) { + *child = 0; // use it to flag a found child + + DIR *dir; + if (!(dir = opendir("/proc"))) { + // sleep 2 seconds and try again + sleep(2); + if (!(dir = opendir("/proc"))) { + fprintf(stderr, "Error: cannot open /proc directory\n"); + exit(1); + } + } + + struct dirent *entry; + char *end; + while (*child == 0 && (entry = readdir(dir))) { + pid_t pid = strtol(entry->d_name, &end, 10); + if (end == entry->d_name || *end) + continue; + if (pid == parent) + continue; + + // open stat file + char *file; + if (asprintf(&file, "/proc/%u/status", pid) == -1) { + perror("asprintf"); + exit(1); + } + FILE *fp = fopen(file, "r"); + if (!fp) { + free(file); + continue; + } + + // look for firejail executable name + char buf[BUFLEN]; + while (fgets(buf, BUFLEN - 1, fp)) { + if (strncmp(buf, "PPid:", 5) == 0) { + char *ptr = buf + 5; + while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) { + ptr++; + } + if (*ptr == '\0') { + fprintf(stderr, "Error: cannot read /proc file\n"); + exit(1); + } + if (parent == atoi(ptr)) { + // we don't want /usr/bin/xdg-dbus-proxy! + char *cmdline = pid_proc_cmdline(pid); + if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0) + *child = pid; + free(cmdline); + } + break; // stop reading the file + } + } + fclose(fp); + free(file); + } + closedir(dir); + return (*child)? 0:1; // 0 = found, 1 = not found +} + +pid_t switch_to_child(pid_t pid) { + pid_t rv = pid; + errno = 0; + char *comm = pid_proc_comm(pid); + if (!comm) { + if (errno == ENOENT) + fprintf(stderr, "Error: cannot find process with pid %d\n", pid); + else + fprintf(stderr, "Error: cannot read /proc file\n"); + exit(1); + } + + if (strcmp(comm, "firejail") == 0) { + if (find_child(pid, &rv) == 1) { + fprintf(stderr, "Error: no valid sandbox\n"); + exit(1); + } + } + free(comm); + return rv; +} diff --git a/src/jailtest/virtual.c b/src/jailtest/virtual.c new file mode 100644 index 00000000000..48296fdb1c7 --- /dev/null +++ b/src/jailtest/virtual.c @@ -0,0 +1,99 @@ +#include "jailtest.h" +#include +#include + + +#define MAX_TEST_FILES 16 +static char *dirs[MAX_TEST_FILES]; +static char *files[MAX_TEST_FILES]; +static int files_cnt = 0; + +void virtual_setup(const char *directory) { + // I am root! + assert(directory); + assert(*directory == '/'); + assert(files_cnt < MAX_TEST_FILES); + + // try to open the dir as root + DIR *dir = opendir(directory); + if (!dir) { + fprintf(stderr, "Warning: directory %s not found, skipping\n", directory); + return; + } + closedir(dir); + + // create a test file + char *test_file; + if (asprintf(&test_file, "%s/jailtest-private-%d", directory, getpid()) == -1) + errExit("asprintf"); + + FILE *fp = fopen(test_file, "w"); + if (!fp) { + printf("Warning: I cannot create test file in directory %s, skipping...\n", directory); + return; + } + fprintf(fp, "this file was created by firetest utility, you can safely delete it\n"); + fclose(fp); + if (strcmp(directory, user_home_dir) == 0) { + int rv = chown(test_file, user_uid, user_gid); + if (rv) + errExit("chown"); + } + + char *dname = strdup(directory); + if (!dname) + errExit("strdup"); + dirs[files_cnt] = dname; + files[files_cnt] = test_file; + files_cnt++; +} + +void virtual_destroy(void) { + // remove test files + int i; + + for (i = 0; i < files_cnt; i++) { + int rv = unlink(files[i]); + (void) rv; + } + files_cnt = 0; +} + +void virtual_test(void) { + // I am root in sandbox mount namespace + assert(user_uid); + int i; + + printf(" Virtual dirs: "); fflush(0); + + for (i = 0; i < files_cnt; i++) { + assert(files[i]); + + // I am root! + pid_t child = fork(); + if (child == -1) + errExit("fork"); + + if (child == 0) { // child + // drop privileges + if (setgid(user_gid) != 0) + errExit("setgid"); + if (setuid(user_uid) != 0) + errExit("setuid"); + + // try to open the file for reading + FILE *fp = fopen(files[i], "r"); + if (fp) + fclose(fp); + else + printf("%s, ", dirs[i]); + fflush(0); + exit(0); + } + + // wait for the child to finish + int status; + wait(&status); + } + printf("\n"); +} diff --git a/src/man/Makefile.in b/src/man/Makefile.in index 1c444430731..1a1f8ba08cd 100644 --- a/src/man/Makefile.in +++ b/src/man/Makefile.in @@ -1,4 +1,4 @@ -all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man +all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man jailtest.man include ../common.mk %.man: %.txt diff --git a/src/man/jailtest.txt b/src/man/jailtest.txt new file mode 100644 index 00000000000..bc1999163f0 --- /dev/null +++ b/src/man/jailtest.txt @@ -0,0 +1,82 @@ +.TH JAILTEST 1 "MONTH YEAR" "VERSION" "JAILTEST man page" +.SH NAME +jailtest \- Simple utility program to test running sandboxes +.SH SYNOPSIS +sudo jailtest [OPTIONS] [directory] +.SH DESCRIPTION +WORK IN PROGRESS! +jailtest attaches itself to all sandboxes started by the user and performs some basic tests +on the sandbox filesystem: +.TP +\fB1. Virtual directories +jailtest extracts a list with the main virtual directories installed by the sandbox. +These directories are build by firejail at startup using --private* and --whitelist commands. +.TP +\fB2. Noexec test +jailtest inserts executable programs in /home/username, /tmp, and /var/tmp directories +and tries to run them form inside the sandbox, thus testing if the directory is executable or not. +.TP +\fB3. Read access test +jailtest creates test files in the directories specified by the user and tries to read +them from inside the sandbox. + +.TP +The program is running as root exclusively under sudo. + +.SH OPTIONS +.TP +\fB\-\-debug +Print debug messages +.TP +\fB\-?\fR, \fB\-\-help\fR +Print options end exit. +.TP +\fB\-\-version +Print program version and exit. +.TP +\fB[directory] +One or more directories in user home to test for read access. + +.SH OUTPUT +For each sandbox detected we print the following line: + + PID:USER:Sandbox Name:Command + +It is followed by relevant sandbox information, such as the virtual directories and various warnings. + +.SH EXAMPLE + +.br +$ sudo jailtest ~/.ssh ~/.gnupg +.br +1429:netblue::/usr/bin/firejail /opt/firefox/firefox +.br + Virtual dirs: /home/netblue, /tmp, /var/tmp, /dev, /etc, +.br +5602:netblue::/usr/bin/firejail /usr/bin/ssh netblue@x.y.z.net +.br + Virtual dirs: /var/tmp, /dev, +.br + Warning: I can read ~/.ssh +.br +5926:netblue::/usr/bin/firejail /usr/bin/gimp-2.10 +.br + Virtual dirs: /tmp, /var/tmp, /dev, +.br + Warning: I can run programs in /home/netblue +.br +6394:netblue:libreoffice:/usr/bin/firejail libreoffice +.br + Virtual dirs: /tmp, /var/tmp, /dev, +.br + +.SH LICENSE +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. +.PP +Homepage: https://firejail.wordpress.com +.SH SEE ALSO +\&\flfirejail\fR\|(1), +\&\flfirecfg\fR\|(1), +\&\flfirejail-profile\fR\|(5), +\&\flfirejail-login\fR\|(5) +\&\flfirejail-users\fR\|(5)