diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile.in | 7 | ||||
-rw-r--r-- | README.md | 77 | ||||
-rwxr-xr-x | configure | 5 | ||||
-rw-r--r-- | configure.ac | 3 | ||||
-rw-r--r-- | src/jailtest/Makefile.in | 14 | ||||
-rw-r--r-- | src/jailtest/access.c | 124 | ||||
-rw-r--r-- | src/jailtest/jailtest.h | 32 | ||||
-rw-r--r-- | src/jailtest/main.c | 134 | ||||
-rw-r--r-- | src/jailtest/noexec.c | 94 | ||||
-rw-r--r-- | src/jailtest/utils.c | 124 | ||||
-rw-r--r-- | src/jailtest/virtual.c | 99 | ||||
-rw-r--r-- | src/man/Makefile.in | 2 | ||||
-rw-r--r-- | src/man/jailtest.txt | 82 |
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 | |||
22 | firejail.1 | 22 | firejail.1 |
23 | firemon.1 | 23 | firemon.1 |
24 | firecfg.1 | 24 | firecfg.1 |
25 | jailtest.5 | ||
25 | mkdeb.sh | 26 | mkdeb.sh |
26 | src/firejail/firejail | 27 | src/firejail/firejail |
27 | src/firemon/firemon | 28 | src/firemon/firemon |
@@ -40,6 +41,7 @@ src/fbuilder/fbuilder | |||
40 | src/profstats/profstats | 41 | src/profstats/profstats |
41 | src/bash_completion/firejail.bash_completion | 42 | src/bash_completion/firejail.bash_completion |
42 | src/zsh_completion/_firejail | 43 | src/zsh_completion/_firejail |
44 | src/jailtest/jailtest | ||
43 | uids.h | 45 | uids.h |
44 | seccomp | 46 | seccomp |
45 | seccomp.debug | 47 | seccomp.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 | ||
24 | COMPLETIONDIRS = src/zsh_completion src/bash_completion | 24 | COMPLETIONDIRS = src/zsh_completion src/bash_completion |
25 | all: all_items mydirs $(MAN_TARGET) filters | 25 | all: all_items mydirs $(MAN_TARGET) filters |
26 | APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats | 26 | APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats src/jailtest/jailtest |
27 | SBOX_APPS = src/faudit/faudit src/fbuilder/fbuilder src/ftee/ftee | 27 | SBOX_APPS = src/faudit/faudit src/fbuilder/fbuilder src/ftee/ftee |
28 | SBOX_APPS_NON_DUMPABLE = src/fcopy/fcopy src/fldd/fldd src/fnet/fnet src/fnetfilter/fnetfilter | 28 | SBOX_APPS_NON_DUMPABLE = src/fcopy/fcopy src/fldd/fldd src/fnet/fnet src/fnetfilter/fnetfilter |
29 | MYDIRS = src/lib $(MAN_SRC) $(COMPLETIONDIRS) | 29 | MYDIRS = src/lib $(MAN_SRC) $(COMPLETIONDIRS) |
30 | MYLIBS = src/libpostexecseccomp/libpostexecseccomp.so src/libtrace/libtrace.so src/libtracelog/libtracelog.so | 30 | MYLIBS = src/libpostexecseccomp/libpostexecseccomp.so src/libtrace/libtrace.so src/libtracelog/libtracelog.so |
31 | COMPLETIONS = src/zsh_completion/_firejail src/bash_completion/firejail.bash_completion | 31 | COMPLETIONS = src/zsh_completion/_firejail src/bash_completion/firejail.bash_completion |
32 | MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 firejail-users.5 | 32 | MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 firejail-users.5 jailtest.5 |
33 | SBOX_APPS_NON_DUMPABLE += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp | 33 | SBOX_APPS_NON_DUMPABLE += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp |
34 | SECCOMP_FILTERS = seccomp seccomp.debug seccomp.32 seccomp.block_secondary seccomp.mdwx seccomp.mdwx.32 | 34 | SECCOMP_FILTERS = seccomp seccomp.debug seccomp.32 seccomp.block_secondary seccomp.mdwx seccomp.mdwx.32 |
35 | ALL_ITEMS = $(APPS) $(SBOX_APPS) $(SBOX_APPS_NON_DUMPABLE) $(MYLIBS) | 35 | ALL_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*; \ |
@@ -198,7 +198,84 @@ We also keep a list of profile fixes for previous released versions in [etc-fixe | |||
198 | Milestone page: https://github.com/netblue30/firejail/milestone/1 | 198 | Milestone page: https://github.com/netblue30/firejail/milestone/1 |
199 | Release discussion: https://github.com/netblue30/firejail/issues/3696 | 199 | Release discussion: https://github.com/netblue30/firejail/issues/3696 |
200 | 200 | ||
201 | ### jailtest | ||
202 | ````` | ||
203 | JAILTEST(1) JAILTEST man page JAILTEST(1) | ||
204 | |||
205 | NAME | ||
206 | jailtest - Simple utility program to test running sandboxes | ||
207 | |||
208 | SYNOPSIS | ||
209 | sudo jailtest [OPTIONS] [directory] | ||
210 | |||
211 | DESCRIPTION | ||
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 | |||
231 | OPTIONS | ||
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 | |||
244 | OUTPUT | ||
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 | |||
252 | EXAMPLE | ||
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 | |||
265 | LICENSE | ||
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 | |||
273 | SEE ALSO | ||
274 | firejail(1), firecfg(1), firejail-profile(5), firejail-login(5) fire‐ | ||
275 | jail-users(5) | ||
276 | |||
277 | 0.9.65 Feb 2021 JAILTEST(1) | ||
278 | ````` | ||
202 | 279 | ||
203 | ### Profile Statistics | 280 | ### Profile Statistics |
204 | 281 | ||
@@ -4269,7 +4269,7 @@ fi | |||
4269 | 4269 | ||
4270 | ac_config_files="$ac_config_files mkdeb.sh" | 4270 | ac_config_files="$ac_config_files mkdeb.sh" |
4271 | 4271 | ||
4272 | 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" | 4272 | 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" |
4273 | 4273 | ||
4274 | cat >confcache <<\_ACEOF | 4274 | cat >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]) | |||
234 | AC_OUTPUT(Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile \ | 234 | AC_OUTPUT(Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile \ |
235 | src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile \ | 235 | src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile \ |
236 | src/ftee/Makefile src/faudit/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile \ | 236 | src/ftee/Makefile src/faudit/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile \ |
237 | src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile) | 237 | src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile \ |
238 | src/jailtest/Makefile) | ||
238 | 239 | ||
239 | echo | 240 | echo |
240 | echo "Configuration options:" | 241 | echo "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 @@ | |||
1 | all: jailtest | ||
2 | |||
3 | include ../common.mk | ||
4 | |||
5 | %.o : %.c $(H_FILE_LIST) ../include/common.h ../include/pid.h | ||
6 | $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@ | ||
7 | |||
8 | jailtest: $(OBJS) | ||
9 | $(CC) $(LDFLAGS) -o $@ $(OBJS) ../lib/common.o ../lib/pid.o $(LIBS) $(EXTRA_LDFLAGS) | ||
10 | |||
11 | clean:; rm -fr *.o jailtest *.gcov *.gcda *.gcno *.plist | ||
12 | |||
13 | distclean: 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 | |||
5 | typedef struct { | ||
6 | char *tfile; | ||
7 | char *tdir; | ||
8 | } TestDir; | ||
9 | |||
10 | #define MAX_TEST_FILES 16 | ||
11 | TestDir td[MAX_TEST_FILES]; | ||
12 | static int files_cnt = 0; | ||
13 | |||
14 | void 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 | |||
80 | void 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 | |||
91 | void 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 | ||
7 | extern uid_t user_uid; | ||
8 | extern gid_t user_gid; | ||
9 | extern char *user_name; | ||
10 | extern char *user_home_dir; | ||
11 | |||
12 | // access.c | ||
13 | void access_setup(const char *directory); | ||
14 | void access_test(void); | ||
15 | void access_destroy(void); | ||
16 | |||
17 | // noexec.c | ||
18 | void noexec_setup(void); | ||
19 | void noexec_test(const char *msg); | ||
20 | |||
21 | // virtual.c | ||
22 | void virtual_setup(const char *directory); | ||
23 | void virtual_destroy(void); | ||
24 | void virtual_test(void); | ||
25 | |||
26 | // utils.c | ||
27 | char *get_sudo_user(void); | ||
28 | char *get_homedir(const char *user, uid_t *uid, gid_t *gid); | ||
29 | int find_child(pid_t parent, pid_t *child); | ||
30 | pid_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 | |||
6 | uid_t user_uid = 0; | ||
7 | gid_t user_gid = 0; | ||
8 | char *user_name = NULL; | ||
9 | char *user_home_dir = NULL; | ||
10 | int arg_debug = 0; | ||
11 | |||
12 | static 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 | |||
20 | static void usage(void) { | ||
21 | printf("firetest - version %s\n\n", VERSION); | ||
22 | puts(usage_str); | ||
23 | } | ||
24 | |||
25 | static 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 | |||
35 | int 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 | |||
6 | static unsigned char *execfile = NULL; | ||
7 | static int execfile_len = 0; | ||
8 | |||
9 | void 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 | |||
43 | void 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 | |||
8 | char *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 | |||
21 | char *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 | |||
36 | errexit: | ||
37 | fprintf(stderr, "Error: cannot find home directory for user %s\n", user); | ||
38 | exit(1); | ||
39 | } | ||
40 | |||
41 | int 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 | |||
104 | pid_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 | ||
7 | static char *dirs[MAX_TEST_FILES]; | ||
8 | static char *files[MAX_TEST_FILES]; | ||
9 | static int files_cnt = 0; | ||
10 | |||
11 | void 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 | |||
51 | void 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 | |||
62 | void 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 @@ | |||
1 | all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man | 1 | all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man jailtest.man |
2 | include ../common.mk | 2 | include ../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 | ||
3 | jailtest \- Simple utility program to test running sandboxes | ||
4 | .SH SYNOPSIS | ||
5 | sudo jailtest [OPTIONS] [directory] | ||
6 | .SH DESCRIPTION | ||
7 | WORK IN PROGRESS! | ||
8 | jailtest attaches itself to all sandboxes started by the user and performs some basic tests | ||
9 | on the sandbox filesystem: | ||
10 | .TP | ||
11 | \fB1. Virtual directories | ||
12 | jailtest extracts a list with the main virtual directories installed by the sandbox. | ||
13 | These directories are build by firejail at startup using --private* and --whitelist commands. | ||
14 | .TP | ||
15 | \fB2. Noexec test | ||
16 | jailtest inserts executable programs in /home/username, /tmp, and /var/tmp directories | ||
17 | and tries to run them form inside the sandbox, thus testing if the directory is executable or not. | ||
18 | .TP | ||
19 | \fB3. Read access test | ||
20 | jailtest creates test files in the directories specified by the user and tries to read | ||
21 | them from inside the sandbox. | ||
22 | |||
23 | .TP | ||
24 | The program is running as root exclusively under sudo. | ||
25 | |||
26 | .SH OPTIONS | ||
27 | .TP | ||
28 | \fB\-\-debug | ||
29 | Print debug messages | ||
30 | .TP | ||
31 | \fB\-?\fR, \fB\-\-help\fR | ||
32 | Print options end exit. | ||
33 | .TP | ||
34 | \fB\-\-version | ||
35 | Print program version and exit. | ||
36 | .TP | ||
37 | \fB[directory] | ||
38 | One or more directories in user home to test for read access. | ||
39 | |||
40 | .SH OUTPUT | ||
41 | For each sandbox detected we print the following line: | ||
42 | |||
43 | PID:USER:Sandbox Name:Command | ||
44 | |||
45 | It 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 | ||
52 | 1429:netblue::/usr/bin/firejail /opt/firefox/firefox | ||
53 | .br | ||
54 | Virtual dirs: /home/netblue, /tmp, /var/tmp, /dev, /etc, | ||
55 | .br | ||
56 | 5602: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 | ||
62 | 5926: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 | ||
68 | 6394:netblue:libreoffice:/usr/bin/firejail libreoffice | ||
69 | .br | ||
70 | Virtual dirs: /tmp, /var/tmp, /dev, | ||
71 | .br | ||
72 | |||
73 | .SH LICENSE | ||
74 | 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. | ||
75 | .PP | ||
76 | Homepage: 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) | ||