aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar netblue30 <netblue30@protonmail.com>2021-02-20 10:06:58 -0500
committerLibravatar netblue30 <netblue30@protonmail.com>2021-02-20 10:06:58 -0500
commit42e2db1275e37bf669a074c023ea9f9a8b40db43 (patch)
tree59169acd88cbce9160b1657a7016c789559e0e20
parentrun sort.py (diff)
downloadfirejail-42e2db1275e37bf669a074c023ea9f9a8b40db43.tar.gz
firejail-42e2db1275e37bf669a074c023ea9f9a8b40db43.tar.zst
firejail-42e2db1275e37bf669a074c023ea9f9a8b40db43.zip
jaitest - simple sandbox testing utility program
-rw-r--r--.gitignore2
-rw-r--r--Makefile.in7
-rw-r--r--README.md77
-rwxr-xr-xconfigure5
-rw-r--r--configure.ac3
-rw-r--r--src/jailtest/Makefile.in14
-rw-r--r--src/jailtest/access.c124
-rw-r--r--src/jailtest/jailtest.h32
-rw-r--r--src/jailtest/main.c134
-rw-r--r--src/jailtest/noexec.c94
-rw-r--r--src/jailtest/utils.c124
-rw-r--r--src/jailtest/virtual.c99
-rw-r--r--src/man/Makefile.in2
-rw-r--r--src/man/jailtest.txt82
14 files changed, 794 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore
index 0c803b135..cbb1b2e83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ firejail-users.5
22firejail.1 22firejail.1
23firemon.1 23firemon.1
24firecfg.1 24firecfg.1
25jailtest.5
25mkdeb.sh 26mkdeb.sh
26src/firejail/firejail 27src/firejail/firejail
27src/firemon/firemon 28src/firemon/firemon
@@ -40,6 +41,7 @@ src/fbuilder/fbuilder
40src/profstats/profstats 41src/profstats/profstats
41src/bash_completion/firejail.bash_completion 42src/bash_completion/firejail.bash_completion
42src/zsh_completion/_firejail 43src/zsh_completion/_firejail
44src/jailtest/jailtest
43uids.h 45uids.h
44seccomp 46seccomp
45seccomp.debug 47seccomp.debug
diff --git a/Makefile.in b/Makefile.in
index 593afdacf..b0deee03b 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -23,13 +23,13 @@ endif
23 23
24COMPLETIONDIRS = src/zsh_completion src/bash_completion 24COMPLETIONDIRS = src/zsh_completion src/bash_completion
25all: all_items mydirs $(MAN_TARGET) filters 25all: all_items mydirs $(MAN_TARGET) filters
26APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats 26APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats src/jailtest/jailtest
27SBOX_APPS = src/faudit/faudit src/fbuilder/fbuilder src/ftee/ftee 27SBOX_APPS = src/faudit/faudit src/fbuilder/fbuilder src/ftee/ftee
28SBOX_APPS_NON_DUMPABLE = src/fcopy/fcopy src/fldd/fldd src/fnet/fnet src/fnetfilter/fnetfilter 28SBOX_APPS_NON_DUMPABLE = src/fcopy/fcopy src/fldd/fldd src/fnet/fnet src/fnetfilter/fnetfilter
29MYDIRS = src/lib $(MAN_SRC) $(COMPLETIONDIRS) 29MYDIRS = src/lib $(MAN_SRC) $(COMPLETIONDIRS)
30MYLIBS = src/libpostexecseccomp/libpostexecseccomp.so src/libtrace/libtrace.so src/libtracelog/libtracelog.so 30MYLIBS = src/libpostexecseccomp/libpostexecseccomp.so src/libtrace/libtrace.so src/libtracelog/libtracelog.so
31COMPLETIONS = src/zsh_completion/_firejail src/bash_completion/firejail.bash_completion 31COMPLETIONS = src/zsh_completion/_firejail src/bash_completion/firejail.bash_completion
32MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 firejail-users.5 32MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 firejail-users.5 jailtest.5
33SBOX_APPS_NON_DUMPABLE += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp 33SBOX_APPS_NON_DUMPABLE += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp
34SECCOMP_FILTERS = seccomp seccomp.debug seccomp.32 seccomp.block_secondary seccomp.mdwx seccomp.mdwx.32 34SECCOMP_FILTERS = seccomp seccomp.debug seccomp.32 seccomp.block_secondary seccomp.mdwx seccomp.mdwx.32
35ALL_ITEMS = $(APPS) $(SBOX_APPS) $(SBOX_APPS_NON_DUMPABLE) $(MYLIBS) 35ALL_ITEMS = $(APPS) $(SBOX_APPS) $(SBOX_APPS_NON_DUMPABLE) $(MYLIBS)
@@ -109,6 +109,8 @@ endif
109 install -m 0755 src/firemon/firemon $(DESTDIR)$(bindir) 109 install -m 0755 src/firemon/firemon $(DESTDIR)$(bindir)
110 # firecfg executable 110 # firecfg executable
111 install -m 0755 src/firecfg/firecfg $(DESTDIR)$(bindir) 111 install -m 0755 src/firecfg/firecfg $(DESTDIR)$(bindir)
112 # jailtest executable
113 install -m 0755 src/jailtest/jailtest $(DESTDIR)$(bindir)
112 # libraries and plugins 114 # libraries and plugins
113 install -m 0755 -d $(DESTDIR)$(libdir)/firejail 115 install -m 0755 -d $(DESTDIR)$(libdir)/firejail
114 install -m 0644 -t $(DESTDIR)$(libdir)/firejail $(MYLIBS) $(SECCOMP_FILTERS) src/firecfg/firecfg.config 116 install -m 0644 -t $(DESTDIR)$(libdir)/firejail $(MYLIBS) $(SECCOMP_FILTERS) src/firecfg/firecfg.config
@@ -177,6 +179,7 @@ uninstall:
177 rm -f $(DESTDIR)$(bindir)/firemon 179 rm -f $(DESTDIR)$(bindir)/firemon
178 rm -f $(DESTDIR)$(bindir)/firecfg 180 rm -f $(DESTDIR)$(bindir)/firecfg
179 rm -fr $(DESTDIR)$(libdir)/firejail 181 rm -fr $(DESTDIR)$(libdir)/firejail
182 rm -fr $(DESTDIR)$(libdir)/jailtest
180 rm -fr $(DESTDIR)$(datarootdir)/doc/firejail 183 rm -fr $(DESTDIR)$(datarootdir)/doc/firejail
181 for man in $(MANPAGES); do \ 184 for man in $(MANPAGES); do \
182 rm -f $(DESTDIR)$(mandir)/man5/$$man*; \ 185 rm -f $(DESTDIR)$(mandir)/man5/$$man*; \
diff --git a/README.md b/README.md
index 4e0d2a91a..3c8c6afb8 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
198Milestone page: https://github.com/netblue30/firejail/milestone/1 198Milestone page: https://github.com/netblue30/firejail/milestone/1
199Release discussion: https://github.com/netblue30/firejail/issues/3696 199Release discussion: https://github.com/netblue30/firejail/issues/3696
200 200
201### jailtest
202`````
203JAILTEST(1) JAILTEST man page JAILTEST(1)
204
205NAME
206 jailtest - Simple utility program to test running sandboxes
207
208SYNOPSIS
209 sudo jailtest [OPTIONS] [directory]
210
211DESCRIPTION
212 WORK IN PROGRESS! jailtest attaches itself to all sandboxes started by
213 the user and performs some basic tests on the sandbox filesystem:
214
215 1. Virtual directories
216 jailtest extracts a list with the main virtual directories in‐
217 stalled by the sandbox. These directories are build by firejail
218 at startup using --private* and --whitelist commands.
219
220 2. Noexec test
221 jailtest inserts executable programs in /home/username, /tmp,
222 and /var/tmp directories and tries to run them form inside the
223 sandbox, thus testing if the directory is executable or not.
224
225 3. Read access test
226 jailtest creates test files in the directories specified by the
227 user and tries to read them from inside the sandbox.
228
229 The program is running as root exclusively under sudo.
230
231OPTIONS
232 --debug
233 Print debug messages
234
235 -?, --help
236 Print options end exit.
201 237
238 --version
239 Print program version and exit.
240
241 [directory]
242 One or more directories in user home to test for read access.
243
244OUTPUT
245 For each sandbox detected we print the following line:
246
247 PID:USER:Sandbox Name:Command
248
249 It is followed by relevant sandbox information, such as the virtual di‐
250 rectories and various warnings.
251
252EXAMPLE
253 $ sudo jailtest ~/.ssh ~/.gnupg
254 1429:netblue::/usr/bin/firejail /opt/firefox/firefox
255 Virtual dirs: /home/netblue, /tmp, /var/tmp, /dev, /etc,
256 5602:netblue::/usr/bin/firejail /usr/bin/ssh netblue@x.y.z.net
257 Virtual dirs: /var/tmp, /dev,
258 Warning: I can read ~/.ssh
259 5926:netblue::/usr/bin/firejail /usr/bin/gimp-2.10
260 Virtual dirs: /tmp, /var/tmp, /dev,
261 Warning: I can run programs in /home/netblue
262 6394:netblue:libreoffice:/usr/bin/firejail libreoffice
263 Virtual dirs: /tmp, /var/tmp, /dev,
264
265LICENSE
266 This program is free software; you can redistribute it and/or modify it
267 under the terms of the GNU General Public License as published by the
268 Free Software Foundation; either version 2 of the License, or (at your
269 option) any later version.
270
271 Homepage: https://firejail.wordpress.com
272
273SEE ALSO
274 firejail(1), firecfg(1), firejail-profile(5), firejail-login(5) fire‐
275 jail-users(5)
276
2770.9.65 Feb 2021 JAILTEST(1)
278`````
202 279
203### Profile Statistics 280### Profile Statistics
204 281
diff --git a/configure b/configure
index fa2401070..84bcafaf7 100755
--- a/configure
+++ b/configure
@@ -4269,7 +4269,7 @@ fi
4269 4269
4270ac_config_files="$ac_config_files mkdeb.sh" 4270ac_config_files="$ac_config_files mkdeb.sh"
4271 4271
4272ac_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" 4272ac_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"
4273 4273
4274cat >confcache <<\_ACEOF 4274cat >confcache <<\_ACEOF
4275# This file is a shell script that caches the results of configure 4275# This file is a shell script that caches the results of configure
@@ -5000,7 +5000,10 @@ do
5000 "src/fsec-optimize/Makefile") CONFIG_FILES="$CONFIG_FILES src/fsec-optimize/Makefile" ;; 5000 "src/fsec-optimize/Makefile") CONFIG_FILES="$CONFIG_FILES src/fsec-optimize/Makefile" ;;
5001 "src/profstats/Makefile") CONFIG_FILES="$CONFIG_FILES src/profstats/Makefile" ;; 5001 "src/profstats/Makefile") CONFIG_FILES="$CONFIG_FILES src/profstats/Makefile" ;;
5002 "src/man/Makefile") CONFIG_FILES="$CONFIG_FILES src/man/Makefile" ;; 5002 "src/man/Makefile") CONFIG_FILES="$CONFIG_FILES src/man/Makefile" ;;
5003 "src/zsh_completion/Makefile") CONFIG_FILES="$CONFIG_FILES src/zsh_completion/Makefile" ;;
5004 "src/bash_completion/Makefile") CONFIG_FILES="$CONFIG_FILES src/bash_completion/Makefile" ;;
5003 "test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;; 5005 "test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;;
5006 "src/jailtest/Makefile") CONFIG_FILES="$CONFIG_FILES src/jailtest/Makefile" ;;
5004 5007
5005 *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; 5008 *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
5006 esac 5009 esac
diff --git a/configure.ac b/configure.ac
index aa2d0fb6b..b2e9a7b86 100644
--- a/configure.ac
+++ b/configure.ac
@@ -234,7 +234,8 @@ AC_CONFIG_FILES([mkdeb.sh], [chmod +x mkdeb.sh])
234AC_OUTPUT(Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile \ 234AC_OUTPUT(Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile \
235src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile \ 235src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile \
236src/ftee/Makefile src/faudit/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile \ 236src/ftee/Makefile src/faudit/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile \
237src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile) 237src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile \
238src/jailtest/Makefile)
238 239
239echo 240echo
240echo "Configuration options:" 241echo "Configuration options:"
diff --git a/src/jailtest/Makefile.in b/src/jailtest/Makefile.in
new file mode 100644
index 000000000..9c9c0c508
--- /dev/null
+++ b/src/jailtest/Makefile.in
@@ -0,0 +1,14 @@
1all: jailtest
2
3include ../common.mk
4
5%.o : %.c $(H_FILE_LIST) ../include/common.h ../include/pid.h
6 $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@
7
8jailtest: $(OBJS)
9 $(CC) $(LDFLAGS) -o $@ $(OBJS) ../lib/common.o ../lib/pid.o $(LIBS) $(EXTRA_LDFLAGS)
10
11clean:; rm -fr *.o jailtest *.gcov *.gcda *.gcno *.plist
12
13distclean: clean
14 rm -fr Makefile
diff --git a/src/jailtest/access.c b/src/jailtest/access.c
new file mode 100644
index 000000000..e68227bd2
--- /dev/null
+++ b/src/jailtest/access.c
@@ -0,0 +1,124 @@
1#include "jailtest.h"
2#include <dirent.h>
3#include <sys/wait.h>
4
5typedef struct {
6 char *tfile;
7 char *tdir;
8} TestDir;
9
10#define MAX_TEST_FILES 16
11TestDir td[MAX_TEST_FILES];
12static int files_cnt = 0;
13
14void access_setup(const char *directory) {
15 // I am root!
16 assert(directory);
17 assert(user_home_dir);
18
19 if (files_cnt >= MAX_TEST_FILES) {
20 fprintf(stderr, "Error: maximum number of test directories exceded\n");
21 exit(1);
22 }
23
24 char *fname = strdup(directory);
25 if (!fname)
26 errExit("strdup");
27 if (strncmp(fname, "~/", 2) == 0) {
28 free(fname);
29 if (asprintf(&fname, "%s/%s", user_home_dir, directory + 2) == -1)
30 errExit("asprintf");
31 }
32
33 char *path = realpath(fname, NULL);
34 free(fname);
35 if (path == NULL) {
36 fprintf(stderr, "Warning: invalid directory %s, skipping...\n", directory);
37 return;
38 }
39
40 // file in home directory
41 if (strncmp(path, user_home_dir, strlen(user_home_dir)) != 0) {
42 fprintf(stderr, "Warning: file %s is not in user home directory, skipping...\n", directory);
43 free(path);
44 return;
45 }
46
47 // try to open the dir as root
48 DIR *dir = opendir(path);
49 if (!dir) {
50 fprintf(stderr, "Warning: directory %s not found, skipping\n", directory);
51 free(path);
52 return;
53 }
54 closedir(dir);
55
56 // create a test file
57 char *test_file;
58 if (asprintf(&test_file, "%s/jailtest-access-%d", path, getpid()) == -1)
59 errExit("asprintf");
60
61 FILE *fp = fopen(test_file, "w");
62 if (!fp) {
63 printf("Warning: I cannot create test file in directory %s, skipping...\n", directory);
64 return;
65 }
66 fprintf(fp, "this file was created by firetest utility, you can safely delete it\n");
67 fclose(fp);
68 int rv = chown(test_file, user_uid, user_gid);
69 if (rv)
70 errExit("chown");
71
72 char *dname = strdup(directory);
73 if (!dname)
74 errExit("strdup");
75 td[files_cnt].tdir = dname;
76 td[files_cnt].tfile = test_file;
77 files_cnt++;
78}
79
80void access_destroy(void) {
81 // remove test files
82 int i;
83
84 for (i = 0; i < files_cnt; i++) {
85 int rv = unlink(td[i].tfile);
86 (void) rv;
87 }
88 files_cnt = 0;
89}
90
91void access_test(void) {
92 // I am root in sandbox mount namespace
93 assert(user_uid);
94 int i;
95
96 pid_t child = fork();
97 if (child == -1)
98 errExit("fork");
99
100 if (child == 0) { // child
101 // drop privileges
102 if (setgid(user_gid) != 0)
103 errExit("setgid");
104 if (setuid(user_uid) != 0)
105 errExit("setuid");
106
107 for (i = 0; i < files_cnt; i++) {
108 assert(td[i].tfile);
109
110 // try to open the file for reading
111 FILE *fp = fopen(td[i].tfile, "r");
112 if (fp) {
113
114 printf(" Warning: I can read %s\n", td[i].tdir);
115 fclose(fp);
116 }
117 }
118 exit(0);
119 }
120
121 // wait for the child to finish
122 int status;
123 wait(&status);
124}
diff --git a/src/jailtest/jailtest.h b/src/jailtest/jailtest.h
new file mode 100644
index 000000000..678f94bef
--- /dev/null
+++ b/src/jailtest/jailtest.h
@@ -0,0 +1,32 @@
1#ifndef JAILTEST_H
2#define JAILTEST_H
3
4#include "../include/common.h"
5
6// main.c
7extern uid_t user_uid;
8extern gid_t user_gid;
9extern char *user_name;
10extern char *user_home_dir;
11
12// access.c
13void access_setup(const char *directory);
14void access_test(void);
15void access_destroy(void);
16
17// noexec.c
18void noexec_setup(void);
19void noexec_test(const char *msg);
20
21// virtual.c
22void virtual_setup(const char *directory);
23void virtual_destroy(void);
24void virtual_test(void);
25
26// utils.c
27char *get_sudo_user(void);
28char *get_homedir(const char *user, uid_t *uid, gid_t *gid);
29int find_child(pid_t parent, pid_t *child);
30pid_t switch_to_child(pid_t pid);
31
32#endif \ No newline at end of file
diff --git a/src/jailtest/main.c b/src/jailtest/main.c
new file mode 100644
index 000000000..78f162706
--- /dev/null
+++ b/src/jailtest/main.c
@@ -0,0 +1,134 @@
1#include "jailtest.h"
2#include "../include/firejail_user.h"
3#include "../include/pid.h"
4#include <sys/wait.h>
5
6uid_t user_uid = 0;
7gid_t user_gid = 0;
8char *user_name = NULL;
9char *user_home_dir = NULL;
10int arg_debug = 0;
11
12static char *usage_str =
13 "Usage: jailtest [options] directory [directory]\n\n"
14 "Options:\n"
15 " --debug - print debug messages.\n"
16 " --help, -? - this help screen.\n"
17 " --version - print program version and exit.\n";
18
19
20static void usage(void) {
21 printf("firetest - version %s\n\n", VERSION);
22 puts(usage_str);
23}
24
25static void cleanup(void) {
26 // running only as root
27 if (getuid() == 0) {
28 if (arg_debug)
29 printf("cleaning up!\n");
30 access_destroy();
31 virtual_destroy();
32 }
33}
34
35int main(int argc, char **argv) {
36 int i;
37 int findex = 0;
38
39 for (i = 1; i < argc; i++) {
40 if (strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "--help") == 0) {
41 usage();
42 return 0;
43 }
44 else if (strcmp(argv[i], "--version") == 0) {
45 printf("firetest version %s\n\n", VERSION);
46 return 0;
47 }
48 else if (strncmp(argv[i], "--hello=", 8) == 0) { // used by noexec test
49 printf(" Warning: I can run programs in %s\n", argv[i] + 8);
50 return 0;
51 }
52 else if (strcmp(argv[i], "--debug") == 0)
53 arg_debug = 1;
54 else if (strncmp(argv[i], "--", 2) == 0) {
55 fprintf(stderr, "Error: invalid option\n");
56 return 1;
57 }
58 else {
59 findex = i;
60 break;
61 }
62 }
63
64 // user setup
65 if (getuid() != 0) {
66 fprintf(stderr, "Error: you need to be root (via sudo) to run this program\n");
67 exit(1);
68 }
69 user_name = get_sudo_user();
70 assert(user_name);
71 user_home_dir = get_homedir(user_name, &user_uid, &user_gid);
72 if (user_uid == 0) {
73 fprintf(stderr, "Error: root user not supported\n");
74 exit(1);
75 }
76
77 // test setup
78 atexit(cleanup);
79 if (findex > 0) {
80 for (i = findex; i < argc; i++)
81 access_setup(argv[i]);
82 }
83
84 noexec_setup();
85 virtual_setup(user_home_dir);
86 virtual_setup("/tmp");
87 virtual_setup("/var/tmp");
88 virtual_setup("/dev");
89 virtual_setup("/etc");
90 virtual_setup("/bin");
91
92 // print processes
93 pid_read(0);
94 for (i = 0; i < max_pids; i++) {
95 if (pids[i].level == 1) {
96 uid_t uid = pid_get_uid(i);
97 if (uid != user_uid) // not interested in other user sandboxes
98 continue;
99
100 // in case the pid is that of a firejail process, use the pid of the first child process
101 uid_t pid = switch_to_child(i);
102 pid_print_list(i, 0); // no wrapping
103
104 pid_t child = fork();
105 if (child == -1)
106 errExit("fork");
107 if (child == 0) {
108 int rv = join_namespace(pid, "mnt");
109 if (rv == 0) {
110 virtual_test();
111 noexec_test(user_home_dir);
112 noexec_test("/tmp");
113 noexec_test("/var/tmp");
114 access_test();
115 }
116 else {
117 printf(" Error: I cannot join the process mount space\n");
118 exit(1);
119 }
120
121 // drop privileges in order not to trigger cleanup()
122 if (setgid(user_gid) != 0)
123 errExit("setgid");
124 if (setuid(user_uid) != 0)
125 errExit("setuid");
126 return 0;
127 }
128 int status;
129 wait(&status);
130 }
131 }
132
133 return 0;
134}
diff --git a/src/jailtest/noexec.c b/src/jailtest/noexec.c
new file mode 100644
index 000000000..d2f85514a
--- /dev/null
+++ b/src/jailtest/noexec.c
@@ -0,0 +1,94 @@
1#include "jailtest.h"
2#include <sys/wait.h>
3#include <sys/stat.h>
4#include <fcntl.h>
5
6static unsigned char *execfile = NULL;
7static int execfile_len = 0;
8
9void noexec_setup(void) {
10 // grab a copy of myself
11 char *self = realpath("/proc/self/exe", NULL);
12 if (self) {
13 struct stat s;
14 if (access(self, X_OK) == 0 && stat(self, &s) == 0) {
15 assert(s.st_size);
16 execfile = malloc(s.st_size);
17
18 int fd = open(self, O_RDONLY);
19 if (fd == -1)
20 errExit("open");
21 int len = 0;
22 do {
23 int rv = read(fd, execfile + len, s.st_size - len);
24 if (rv == -1)
25 errExit("read");
26 if (rv == 0) {
27 // something went wrong!
28 free(execfile);
29 execfile = NULL;
30 printf("Warning: I cannot grab a copy of myself, skipping noexec test...\n");
31 break;
32 }
33 len += rv;
34 }
35 while (len < s.st_size);
36 execfile_len = s.st_size;
37 close(fd);
38 }
39 }
40}
41
42
43void noexec_test(const char *path) {
44 assert(user_uid);
45
46 // I am root in sandbox mount namespace
47 if (!execfile)
48 return;
49
50 char *fname;
51 if (asprintf(&fname, "%s/jailtest-noexec-%d", path, getpid()) == -1)
52 errExit("asprintf");
53
54 pid_t child = fork();
55 if (child == -1)
56 errExit("fork");
57
58 if (child == 0) { // child
59 // drop privileges
60 if (setgid(user_gid) != 0)
61 errExit("setgid");
62 if (setuid(user_uid) != 0)
63 errExit("setuid");
64 int fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0700);
65 if (fd == -1) {
66 printf(" I cannot create files in %s, skipping noexec...\n", path);
67 exit(1);
68 }
69
70 int len = 0;
71 while (len < execfile_len) {
72 int rv = write(fd, execfile + len, execfile_len - len);
73 if (rv == -1 || rv == 0) {
74 printf(" I cannot create files in %s, skipping noexec....\n", path);
75 exit(1);
76 }
77 len += rv;
78 }
79 fchmod(fd, 0700);
80 close(fd);
81
82 char *arg;
83 if (asprintf(&arg, "--hello=%s", path) == -1)
84 errExit("asprintf");
85 int rv = execl(fname, fname, arg, NULL);
86 (void) rv; // if we get here execl failed
87 exit(0);
88 }
89
90 int status;
91 wait(&status);
92 int rv = unlink(fname);
93 (void) rv;
94} \ No newline at end of file
diff --git a/src/jailtest/utils.c b/src/jailtest/utils.c
new file mode 100644
index 000000000..b24783355
--- /dev/null
+++ b/src/jailtest/utils.c
@@ -0,0 +1,124 @@
1#include "jailtest.h"
2#include <errno.h>
3#include <pwd.h>
4#include <dirent.h>
5
6#define BUFLEN 4096
7
8char *get_sudo_user(void) {
9 char *user = getenv("SUDO_USER");
10 if (!user) {
11 user = getpwuid(getuid())->pw_name;
12 if (!user) {
13 fprintf(stderr, "Error: cannot detect login user\n");
14 exit(1);
15 }
16 }
17
18 return user;
19}
20
21char *get_homedir(const char *user, uid_t *uid, gid_t *gid) {
22 // find home directory
23 struct passwd *pw = getpwnam(user);
24 if (!pw)
25 goto errexit;
26
27 char *home = pw->pw_dir;
28 if (!home)
29 goto errexit;
30
31 *uid = pw->pw_uid;
32 *gid = pw->pw_gid;
33
34 return home;
35
36errexit:
37 fprintf(stderr, "Error: cannot find home directory for user %s\n", user);
38 exit(1);
39}
40
41int find_child(pid_t parent, pid_t *child) {
42 *child = 0; // use it to flag a found child
43
44 DIR *dir;
45 if (!(dir = opendir("/proc"))) {
46 // sleep 2 seconds and try again
47 sleep(2);
48 if (!(dir = opendir("/proc"))) {
49 fprintf(stderr, "Error: cannot open /proc directory\n");
50 exit(1);
51 }
52 }
53
54 struct dirent *entry;
55 char *end;
56 while (*child == 0 && (entry = readdir(dir))) {
57 pid_t pid = strtol(entry->d_name, &end, 10);
58 if (end == entry->d_name || *end)
59 continue;
60 if (pid == parent)
61 continue;
62
63 // open stat file
64 char *file;
65 if (asprintf(&file, "/proc/%u/status", pid) == -1) {
66 perror("asprintf");
67 exit(1);
68 }
69 FILE *fp = fopen(file, "r");
70 if (!fp) {
71 free(file);
72 continue;
73 }
74
75 // look for firejail executable name
76 char buf[BUFLEN];
77 while (fgets(buf, BUFLEN - 1, fp)) {
78 if (strncmp(buf, "PPid:", 5) == 0) {
79 char *ptr = buf + 5;
80 while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) {
81 ptr++;
82 }
83 if (*ptr == '\0') {
84 fprintf(stderr, "Error: cannot read /proc file\n");
85 exit(1);
86 }
87 if (parent == atoi(ptr)) {
88 // we don't want /usr/bin/xdg-dbus-proxy!
89 char *cmdline = pid_proc_cmdline(pid);
90 if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0)
91 *child = pid;
92 free(cmdline);
93 }
94 break; // stop reading the file
95 }
96 }
97 fclose(fp);
98 free(file);
99 }
100 closedir(dir);
101 return (*child)? 0:1; // 0 = found, 1 = not found
102}
103
104pid_t switch_to_child(pid_t pid) {
105 pid_t rv = pid;
106 errno = 0;
107 char *comm = pid_proc_comm(pid);
108 if (!comm) {
109 if (errno == ENOENT)
110 fprintf(stderr, "Error: cannot find process with pid %d\n", pid);
111 else
112 fprintf(stderr, "Error: cannot read /proc file\n");
113 exit(1);
114 }
115
116 if (strcmp(comm, "firejail") == 0) {
117 if (find_child(pid, &rv) == 1) {
118 fprintf(stderr, "Error: no valid sandbox\n");
119 exit(1);
120 }
121 }
122 free(comm);
123 return rv;
124}
diff --git a/src/jailtest/virtual.c b/src/jailtest/virtual.c
new file mode 100644
index 000000000..48296fdb1
--- /dev/null
+++ b/src/jailtest/virtual.c
@@ -0,0 +1,99 @@
1#include "jailtest.h"
2#include <dirent.h>
3#include <sys/wait.h>
4
5
6#define MAX_TEST_FILES 16
7static char *dirs[MAX_TEST_FILES];
8static char *files[MAX_TEST_FILES];
9static int files_cnt = 0;
10
11void virtual_setup(const char *directory) {
12 // I am root!
13 assert(directory);
14 assert(*directory == '/');
15 assert(files_cnt < MAX_TEST_FILES);
16
17 // try to open the dir as root
18 DIR *dir = opendir(directory);
19 if (!dir) {
20 fprintf(stderr, "Warning: directory %s not found, skipping\n", directory);
21 return;
22 }
23 closedir(dir);
24
25 // create a test file
26 char *test_file;
27 if (asprintf(&test_file, "%s/jailtest-private-%d", directory, getpid()) == -1)
28 errExit("asprintf");
29
30 FILE *fp = fopen(test_file, "w");
31 if (!fp) {
32 printf("Warning: I cannot create test file in directory %s, skipping...\n", directory);
33 return;
34 }
35 fprintf(fp, "this file was created by firetest utility, you can safely delete it\n");
36 fclose(fp);
37 if (strcmp(directory, user_home_dir) == 0) {
38 int rv = chown(test_file, user_uid, user_gid);
39 if (rv)
40 errExit("chown");
41 }
42
43 char *dname = strdup(directory);
44 if (!dname)
45 errExit("strdup");
46 dirs[files_cnt] = dname;
47 files[files_cnt] = test_file;
48 files_cnt++;
49}
50
51void virtual_destroy(void) {
52 // remove test files
53 int i;
54
55 for (i = 0; i < files_cnt; i++) {
56 int rv = unlink(files[i]);
57 (void) rv;
58 }
59 files_cnt = 0;
60}
61
62void virtual_test(void) {
63 // I am root in sandbox mount namespace
64 assert(user_uid);
65 int i;
66
67 printf(" Virtual dirs: "); fflush(0);
68
69 for (i = 0; i < files_cnt; i++) {
70 assert(files[i]);
71
72 // I am root!
73 pid_t child = fork();
74 if (child == -1)
75 errExit("fork");
76
77 if (child == 0) { // child
78 // drop privileges
79 if (setgid(user_gid) != 0)
80 errExit("setgid");
81 if (setuid(user_uid) != 0)
82 errExit("setuid");
83
84 // try to open the file for reading
85 FILE *fp = fopen(files[i], "r");
86 if (fp)
87 fclose(fp);
88 else
89 printf("%s, ", dirs[i]);
90 fflush(0);
91 exit(0);
92 }
93
94 // wait for the child to finish
95 int status;
96 wait(&status);
97 }
98 printf("\n");
99}
diff --git a/src/man/Makefile.in b/src/man/Makefile.in
index 1c4444307..1a1f8ba08 100644
--- a/src/man/Makefile.in
+++ b/src/man/Makefile.in
@@ -1,4 +1,4 @@
1all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man 1all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man jailtest.man
2include ../common.mk 2include ../common.mk
3 3
4%.man: %.txt 4%.man: %.txt
diff --git a/src/man/jailtest.txt b/src/man/jailtest.txt
new file mode 100644
index 000000000..bc1999163
--- /dev/null
+++ b/src/man/jailtest.txt
@@ -0,0 +1,82 @@
1.TH JAILTEST 1 "MONTH YEAR" "VERSION" "JAILTEST man page"
2.SH NAME
3jailtest \- Simple utility program to test running sandboxes
4.SH SYNOPSIS
5sudo jailtest [OPTIONS] [directory]
6.SH DESCRIPTION
7WORK IN PROGRESS!
8jailtest attaches itself to all sandboxes started by the user and performs some basic tests
9on the sandbox filesystem:
10.TP
11\fB1. Virtual directories
12jailtest extracts a list with the main virtual directories installed by the sandbox.
13These directories are build by firejail at startup using --private* and --whitelist commands.
14.TP
15\fB2. Noexec test
16jailtest inserts executable programs in /home/username, /tmp, and /var/tmp directories
17and tries to run them form inside the sandbox, thus testing if the directory is executable or not.
18.TP
19\fB3. Read access test
20jailtest creates test files in the directories specified by the user and tries to read
21them from inside the sandbox.
22
23.TP
24The program is running as root exclusively under sudo.
25
26.SH OPTIONS
27.TP
28\fB\-\-debug
29Print debug messages
30.TP
31\fB\-?\fR, \fB\-\-help\fR
32Print options end exit.
33.TP
34\fB\-\-version
35Print program version and exit.
36.TP
37\fB[directory]
38One or more directories in user home to test for read access.
39
40.SH OUTPUT
41For each sandbox detected we print the following line:
42
43 PID:USER:Sandbox Name:Command
44
45It is followed by relevant sandbox information, such as the virtual directories and various warnings.
46
47.SH EXAMPLE
48
49.br
50$ sudo jailtest ~/.ssh ~/.gnupg
51.br
521429:netblue::/usr/bin/firejail /opt/firefox/firefox
53.br
54 Virtual dirs: /home/netblue, /tmp, /var/tmp, /dev, /etc,
55.br
565602:netblue::/usr/bin/firejail /usr/bin/ssh netblue@x.y.z.net
57.br
58 Virtual dirs: /var/tmp, /dev,
59.br
60 Warning: I can read ~/.ssh
61.br
625926:netblue::/usr/bin/firejail /usr/bin/gimp-2.10
63.br
64 Virtual dirs: /tmp, /var/tmp, /dev,
65.br
66 Warning: I can run programs in /home/netblue
67.br
686394:netblue:libreoffice:/usr/bin/firejail libreoffice
69.br
70 Virtual dirs: /tmp, /var/tmp, /dev,
71.br
72
73.SH LICENSE
74This 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.
75.PP
76Homepage: https://firejail.wordpress.com
77.SH SEE ALSO
78\&\flfirejail\fR\|(1),
79\&\flfirecfg\fR\|(1),
80\&\flfirejail-profile\fR\|(5),
81\&\flfirejail-login\fR\|(5)
82\&\flfirejail-users\fR\|(5)