aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common.mk.in7
-rw-r--r--src/fbuilder/build_profile.c54
-rw-r--r--src/fbuilder/build_seccomp.c29
-rw-r--r--src/fbuilder/main.c10
-rw-r--r--src/fcopy/main.c2
-rw-r--r--src/firecfg/firecfg.config7
-rw-r--r--src/firejail/arp.c4
-rw-r--r--src/firejail/checkcfg.c29
-rw-r--r--src/firejail/chroot.c8
-rw-r--r--src/firejail/dbus.c2
-rw-r--r--src/firejail/firejail.h30
-rw-r--r--src/firejail/fs.c10
-rw-r--r--src/firejail/fs_home.c6
-rw-r--r--src/firejail/fs_whitelist.c1363
-rw-r--r--src/firejail/main.c16
-rw-r--r--src/firejail/profile.c14
-rw-r--r--src/firejail/pulseaudio.c2
-rw-r--r--src/firejail/restrict_users.c2
-rw-r--r--src/firejail/sandbox.c10
-rw-r--r--src/firejail/sbox.c2
-rw-r--r--src/firejail/shutdown.c6
-rw-r--r--src/firejail/util.c83
-rw-r--r--src/firejail/x11.c71
-rw-r--r--src/include/rundefs.h16
-rw-r--r--src/jailcheck/Makefile.in (renamed from src/jailtest/Makefile.in)6
-rw-r--r--src/jailcheck/access.c (renamed from src/jailtest/access.c)4
-rw-r--r--src/jailcheck/apparmor.c (renamed from src/jailtest/apparmor.c)2
-rw-r--r--src/jailcheck/jailcheck.h (renamed from src/jailtest/jailtest.h)4
-rw-r--r--src/jailcheck/main.c (renamed from src/jailtest/main.c)4
-rw-r--r--src/jailcheck/noexec.c (renamed from src/jailtest/noexec.c)4
-rw-r--r--src/jailcheck/seccomp.c (renamed from src/jailtest/seccomp.c)2
-rw-r--r--src/jailcheck/sysfiles.c (renamed from src/jailtest/sysfiles.c)2
-rw-r--r--src/jailcheck/utils.c (renamed from src/jailtest/utils.c)2
-rw-r--r--src/jailcheck/virtual.c (renamed from src/jailtest/virtual.c)4
-rw-r--r--src/man/Makefile.in2
-rw-r--r--src/man/firecfg.txt2
-rw-r--r--src/man/firejail-login.txt2
-rw-r--r--src/man/firejail-profile.txt18
-rw-r--r--src/man/firejail-users.txt2
-rw-r--r--src/man/firejail.txt13
-rw-r--r--src/man/firemon.txt2
-rw-r--r--src/man/jailcheck.txt (renamed from src/man/jailtest.txt)17
42 files changed, 731 insertions, 1144 deletions
diff --git a/src/common.mk.in b/src/common.mk.in
index b379aef7f..f88da55ac 100644
--- a/src/common.mk.in
+++ b/src/common.mk.in
@@ -23,6 +23,11 @@ HAVE_FIRETUNNEL=@HAVE_FIRETUNNEL@
23HAVE_PRIVATE_HOME=@HAVE_PRIVATE_HOME@ 23HAVE_PRIVATE_HOME=@HAVE_PRIVATE_HOME@
24HAVE_GCOV=@HAVE_GCOV@ 24HAVE_GCOV=@HAVE_GCOV@
25HAVE_SELINUX=@HAVE_SELINUX@ 25HAVE_SELINUX=@HAVE_SELINUX@
26ifeq (@HAVE_SUID@, yes)
27HAVE_SUID=-DHAVE_SUID
28else
29HAVE_SUID=
30endif
26HAVE_DBUSPROXY=@HAVE_DBUSPROXY@ 31HAVE_DBUSPROXY=@HAVE_DBUSPROXY@
27HAVE_USERTMPFS=@HAVE_USERTMPFS@ 32HAVE_USERTMPFS=@HAVE_USERTMPFS@
28HAVE_OUTPUT=@HAVE_OUTPUT@ 33HAVE_OUTPUT=@HAVE_OUTPUT@
@@ -37,7 +42,7 @@ BINOBJS = $(foreach file, $(OBJS), $file)
37CFLAGS = @CFLAGS@ 42CFLAGS = @CFLAGS@
38CFLAGS += -ggdb $(HAVE_FATAL_WARNINGS) -O2 -DVERSION='"$(VERSION)"' $(HAVE_GCOV) 43CFLAGS += -ggdb $(HAVE_FATAL_WARNINGS) -O2 -DVERSION='"$(VERSION)"' $(HAVE_GCOV)
39CFLAGS += -DPREFIX='"$(prefix)"' -DSYSCONFDIR='"$(sysconfdir)/firejail"' -DLIBDIR='"$(libdir)"' -DBINDIR='"$(bindir)"' 44CFLAGS += -DPREFIX='"$(prefix)"' -DSYSCONFDIR='"$(sysconfdir)/firejail"' -DLIBDIR='"$(libdir)"' -DBINDIR='"$(bindir)"'
40MANFLAGS = $(HAVE_LTS) $(HAVE_OUTPUT) $(HAVE_X11) $(HAVE_PRIVATE_HOME) $(HAVE_APPARMOR) $(HAVE_OVERLAYFS) $(HAVE_USERTMPFS) $(HAVE_DBUSPROXY) $(HAVE_FIRETUNNEL) $(HAVE_GLOBALCFG) $(HAVE_CHROOT) $(HAVE_NETWORK) $(HAVE_USERNS) $(HAVE_FILE_TRANSFER) $(HAVE_WHITELIST) $(HAVE_SELINUX) $(HAVE_FORCE_NONEWPRIVS) 45MANFLAGS = $(HAVE_LTS) $(HAVE_OUTPUT) $(HAVE_X11) $(HAVE_PRIVATE_HOME) $(HAVE_APPARMOR) $(HAVE_OVERLAYFS) $(HAVE_USERTMPFS) $(HAVE_DBUSPROXY) $(HAVE_FIRETUNNEL) $(HAVE_GLOBALCFG) $(HAVE_CHROOT) $(HAVE_NETWORK) $(HAVE_USERNS) $(HAVE_FILE_TRANSFER) $(HAVE_WHITELIST) $(HAVE_SELINUX) $(HAVE_SUID) $(HAVE_FORCE_NONEWPRIVS)
41CFLAGS += $(MANFLAGS) 46CFLAGS += $(MANFLAGS)
42CFLAGS += -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security 47CFLAGS += -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security
43LDFLAGS += -pie -fPIE -Wl,-z,relro -Wl,-z,now -lpthread 48LDFLAGS += -pie -fPIE -Wl,-z,relro -Wl,-z,now -lpthread
diff --git a/src/fbuilder/build_profile.c b/src/fbuilder/build_profile.c
index 1726b4dbb..5df19f511 100644
--- a/src/fbuilder/build_profile.c
+++ b/src/fbuilder/build_profile.c
@@ -24,21 +24,6 @@
24#define TRACE_OUTPUT "/tmp/firejail-trace.XXXXXX" 24#define TRACE_OUTPUT "/tmp/firejail-trace.XXXXXX"
25#define STRACE_OUTPUT "/tmp/firejail-strace.XXXXXX" 25#define STRACE_OUTPUT "/tmp/firejail-strace.XXXXXX"
26 26
27/* static char *cmdlist[] = { */
28/* "/usr/bin/firejail", */
29/* "--quiet", */
30/* "--output=" TRACE_OUTPUT, */
31/* "--noprofile", */
32/* "--caps.drop=all", */
33/* "--nonewprivs", */
34/* "--trace", */
35/* "--shell=none", */
36/* "/usr/bin/strace", // also used as a marker in build_profile() */
37/* "-c", */
38/* "-f", */
39/* "-o" STRACE_OUTPUT, */
40/* }; */
41
42void build_profile(int argc, char **argv, int index, FILE *fp) { 27void build_profile(int argc, char **argv, int index, FILE *fp) {
43 // next index is the application name 28 // next index is the application name
44 if (index >= argc) { 29 if (index >= argc) {
@@ -158,14 +143,14 @@ void build_profile(int argc, char **argv, int index, FILE *fp) {
158 fprintf(fp, "### Enable as many of them as you can! A very important one is\n"); 143 fprintf(fp, "### Enable as many of them as you can! A very important one is\n");
159 fprintf(fp, "### \"disable-exec.inc\". This will make among other things your home\n"); 144 fprintf(fp, "### \"disable-exec.inc\". This will make among other things your home\n");
160 fprintf(fp, "### and /tmp directories non-executable.\n"); 145 fprintf(fp, "### and /tmp directories non-executable.\n");
161 fprintf(fp, "include disable-common.inc\n"); 146 fprintf(fp, "include disable-common.inc\t# dangerous directories like ~/.ssh and ~/.gnupg\n");
162 fprintf(fp, "#include disable-devel.inc\n"); 147 fprintf(fp, "#include disable-devel.inc\t# development tools such as gcc and gdb\n");
163 fprintf(fp, "#include disable-exec.inc\n"); 148 fprintf(fp, "#include disable-exec.inc\t# non-executable directories such as /var, /tmp, and /home\n");
164 fprintf(fp, "#include disable-interpreters.inc\n"); 149 fprintf(fp, "#include disable-interpreters.inc\t# perl, python, lua etc.\n");
165 fprintf(fp, "include disable-passwdmgr.inc\n"); 150 fprintf(fp, "include disable-passwdmgr.inc\t# password managers\n");
166 fprintf(fp, "include disable-programs.inc\n"); 151 fprintf(fp, "include disable-programs.inc\t# user configuration for programs such as firefox, vlc etc.\n");
167 fprintf(fp, "#include disable-shell.inc\n"); 152 fprintf(fp, "#include disable-shell.inc\t# sh, bash, zsh etc.\n");
168 fprintf(fp, "#include disable-xdg.inc\n"); 153 fprintf(fp, "#include disable-xdg.inc\t# standard user directories: Documents, Pictures, Videos, Music\n");
169 fprintf(fp, "\n"); 154 fprintf(fp, "\n");
170 155
171 fprintf(fp, "### Home Directory Whitelisting ###\n"); 156 fprintf(fp, "### Home Directory Whitelisting ###\n");
@@ -180,18 +165,19 @@ void build_profile(int argc, char **argv, int index, FILE *fp) {
180 build_var(trace_output, fp); 165 build_var(trace_output, fp);
181 fprintf(fp, "\n"); 166 fprintf(fp, "\n");
182 167
183 fprintf(fp, "#apparmor\n"); 168 fprintf(fp, "#apparmor\t# if you have AppArmor running, try this one!\n");
184 fprintf(fp, "caps.drop all\n"); 169 fprintf(fp, "caps.drop all\n");
185 fprintf(fp, "ipc-namespace\n"); 170 fprintf(fp, "ipc-namespace\n");
186 fprintf(fp, "netfilter\n"); 171 fprintf(fp, "netfilter\n");
187 fprintf(fp, "#nodvd\n"); 172 fprintf(fp, "#no3d\t# disable 3D acceleration\n");
188 fprintf(fp, "#nogroups\n"); 173 fprintf(fp, "#nodvd\t# disable DVD and CD devices\n");
189 fprintf(fp, "#noinput\n"); 174 fprintf(fp, "#nogroups\t# disable supplementary user groups\n");
175 fprintf(fp, "#noinput\t# disable input devices\n");
190 fprintf(fp, "nonewprivs\n"); 176 fprintf(fp, "nonewprivs\n");
191 fprintf(fp, "noroot\n"); 177 fprintf(fp, "noroot\n");
192 fprintf(fp, "#notv\n"); 178 fprintf(fp, "#notv\t# disable DVB TV devices\n");
193 fprintf(fp, "#nou2f\n"); 179 fprintf(fp, "#nou2f\t# disable U2F devices\n");
194 fprintf(fp, "#novideo\n"); 180 fprintf(fp, "#novideo\t# disable video capture devices\n");
195 build_protocol(trace_output, fp); 181 build_protocol(trace_output, fp);
196 fprintf(fp, "seccomp\n"); 182 fprintf(fp, "seccomp\n");
197 if (!have_strace) { 183 if (!have_strace) {
@@ -203,19 +189,21 @@ void build_profile(int argc, char **argv, int index, FILE *fp) {
203 else 189 else
204 build_seccomp(strace_output, fp); 190 build_seccomp(strace_output, fp);
205 fprintf(fp, "shell none\n"); 191 fprintf(fp, "shell none\n");
206 fprintf(fp, "#tracelog\n"); 192 fprintf(fp, "tracelog\n");
207 fprintf(fp, "\n"); 193 fprintf(fp, "\n");
208 194
209 fprintf(fp, "#disable-mnt\n"); 195 fprintf(fp, "#disable-mnt\t# no access to /mnt, /media, /run/mount and /run/media\n");
210 build_bin(trace_output, fp); 196 build_bin(trace_output, fp);
211 fprintf(fp, "#private-lib\n"); 197 fprintf(fp, "#private-cache\t# run with an empty ~/.cache directory\n");
212 build_dev(trace_output, fp); 198 build_dev(trace_output, fp);
213 build_etc(trace_output, fp); 199 build_etc(trace_output, fp);
200 fprintf(fp, "#private-lib\n");
214 build_tmp(trace_output, fp); 201 build_tmp(trace_output, fp);
215 fprintf(fp, "\n"); 202 fprintf(fp, "\n");
216 203
217 fprintf(fp, "#dbus-user none\n"); 204 fprintf(fp, "#dbus-user none\n");
218 fprintf(fp, "#dbus-system none\n"); 205 fprintf(fp, "#dbus-system none\n");
206 fprintf(fp, "\n");
219 fprintf(fp, "#memory-deny-write-execute\n"); 207 fprintf(fp, "#memory-deny-write-execute\n");
220 208
221 if (!arg_debug) { 209 if (!arg_debug) {
diff --git a/src/fbuilder/build_seccomp.c b/src/fbuilder/build_seccomp.c
index dc3cce456..b3187227e 100644
--- a/src/fbuilder/build_seccomp.c
+++ b/src/fbuilder/build_seccomp.c
@@ -82,11 +82,12 @@ void build_seccomp(const char *fname, FILE *fp) {
82//*************************************** 82//***************************************
83// protocol 83// protocol
84//*************************************** 84//***************************************
85int unix_s = 0; 85static int unix_s = 0;
86int inet = 0; 86static int inet = 0;
87int inet6 = 0; 87static int inet6 = 0;
88int netlink = 0; 88static int netlink = 0;
89int packet = 0; 89static int packet = 0;
90static int bluetooth = 0;
90static void process_protocol(const char *fname) { 91static void process_protocol(const char *fname) {
91 assert(fname); 92 assert(fname);
92 93
@@ -135,6 +136,8 @@ static void process_protocol(const char *fname) {
135 netlink = 1; 136 netlink = 1;
136 else if (strncmp(ptr, "AF_PACKET ", 10) == 0) 137 else if (strncmp(ptr, "AF_PACKET ", 10) == 0)
137 packet = 1; 138 packet = 1;
139 else if (strncmp(ptr, "AF_BLUETOOTH ", 13) == 0)
140 bluetooth = 1;
138 } 141 }
139 142
140 fclose(fp); 143 fclose(fp);
@@ -161,22 +164,22 @@ void build_protocol(const char *fname, FILE *fp) {
161 } 164 }
162 165
163 int net = 0; 166 int net = 0;
164 if (unix_s || inet || inet6 || netlink || packet) { 167 if (unix_s || inet || inet6 || netlink || packet || bluetooth) {
165 fprintf(fp, "protocol "); 168 fprintf(fp, "protocol ");
166 if (unix_s) 169 if (unix_s)
167 fprintf(fp, "unix,"); 170 fprintf(fp, "unix,");
168 if (inet) { 171 if (inet || inet6) {
169 fprintf(fp, "inet,"); 172 fprintf(fp, "inet,inet6,");
170 net = 1;
171 }
172 if (inet6) {
173 fprintf(fp, "inet6,");
174 net = 1; 173 net = 1;
175 } 174 }
176 if (netlink) 175 if (netlink)
177 fprintf(fp, "netlink,"); 176 fprintf(fp, "netlink,");
178 if (packet) { 177 if (packet) {
179 fprintf(fp, "packet"); 178 fprintf(fp, "packet,");
179 net = 1;
180 }
181 if (bluetooth) {
182 fprintf(fp, "bluetooth");
180 net = 1; 183 net = 1;
181 } 184 }
182 fprintf(fp, "\n"); 185 fprintf(fp, "\n");
diff --git a/src/fbuilder/main.c b/src/fbuilder/main.c
index 35ec49519..6c9fc507c 100644
--- a/src/fbuilder/main.c
+++ b/src/fbuilder/main.c
@@ -39,7 +39,7 @@ printf("\n");
39 int i; 39 int i;
40 int prog_index = 0; 40 int prog_index = 0;
41 FILE *fp = stdout; 41 FILE *fp = stdout;
42 int prof_file = 0; 42 char *prof_file = NULL;
43 43
44 // parse arguments and extract program index 44 // parse arguments and extract program index
45 for (i = 1; i < argc; i++) { 45 for (i = 1; i < argc; i++) {
@@ -70,8 +70,7 @@ printf("\n");
70 fprintf(stderr, "Error: cannot open profile file.\n"); 70 fprintf(stderr, "Error: cannot open profile file.\n");
71 exit(1); 71 exit(1);
72 } 72 }
73 prof_file = 1; 73 prof_file = argv[i] + 8;
74 // do nothing, this is passed down from firejail
75 } 74 }
76 else { 75 else {
77 if (*argv[i] == '-') { 76 if (*argv[i] == '-') {
@@ -87,8 +86,11 @@ printf("\n");
87 if (prog_index == 0) { 86 if (prog_index == 0) {
88 fprintf(stderr, "Error : program and arguments required\n"); 87 fprintf(stderr, "Error : program and arguments required\n");
89 usage(); 88 usage();
90 if (prof_file) 89 if (prof_file) {
91 fclose(fp); 90 fclose(fp);
91 int rv = unlink(prof_file);
92 (void) rv;
93 }
92 exit(1); 94 exit(1);
93 } 95 }
94 96
diff --git a/src/fcopy/main.c b/src/fcopy/main.c
index 572e9f601..869549821 100644
--- a/src/fcopy/main.c
+++ b/src/fcopy/main.c
@@ -340,7 +340,7 @@ static char *check(const char *src) {
340 340
341errexit: 341errexit:
342 free(rsrc); 342 free(rsrc);
343 fprintf(stderr, "Error fcopy: invalid file %s\n", src); 343 fprintf(stderr, "Error fcopy: invalid ownership for file %s\n", src);
344 exit(1); 344 exit(1);
345} 345}
346 346
diff --git a/src/firecfg/firecfg.config b/src/firecfg/firecfg.config
index 474904ebf..245e6a4a0 100644
--- a/src/firecfg/firecfg.config
+++ b/src/firecfg/firecfg.config
@@ -186,7 +186,6 @@ display-im6.q16
186dnox 186dnox
187dnscrypt-proxy 187dnscrypt-proxy
188dnsmasq 188dnsmasq
189dolphin
190dolphin-emu 189dolphin-emu
191dooble 190dooble
192dooble-qt4 191dooble-qt4
@@ -271,6 +270,7 @@ freetube
271freshclam 270freshclam
272frogatto 271frogatto
273frozen-bubble 272frozen-bubble
273funnyboat
274gajim 274gajim
275gajim-history-manager 275gajim-history-manager
276galculator 276galculator
@@ -357,6 +357,7 @@ gradio
357gramps 357gramps
358gravity-beams-and-evaporating-stars 358gravity-beams-and-evaporating-stars
359gthumb 359gthumb
360gtk-pipe-viewer
360gtk-straw-viewer 361gtk-straw-viewer
361gtk-youtube-viewer 362gtk-youtube-viewer
362gtk2-youtube-viewer 363gtk2-youtube-viewer
@@ -443,6 +444,7 @@ kube
443kwrite 444kwrite
444leafpad 445leafpad
445# less - breaks man 446# less - breaks man
447librecad
446libreoffice 448libreoffice
447librewolf 449librewolf
448librewolf-nightly 450librewolf-nightly
@@ -450,6 +452,7 @@ liferea
450lightsoff 452lightsoff
451lincity-ng 453lincity-ng
452links 454links
455links2
453linphone 456linphone
454lmms 457lmms
455lobase 458lobase
@@ -626,6 +629,7 @@ pinball
626pingus 629pingus
627pinta 630pinta
628pioneer 631pioneer
632pipe-viewer
629pithos 633pithos
630pitivi 634pitivi
631pix 635pix
@@ -868,6 +872,7 @@ xfce4-notes
868xfce4-screenshooter 872xfce4-screenshooter
869xiphos 873xiphos
870xlinks 874xlinks
875xlinks2
871xmms 876xmms
872xmr-stak 877xmr-stak
873xonotic 878xonotic
diff --git a/src/firejail/arp.c b/src/firejail/arp.c
index 1e9641097..bbab9a6d9 100644
--- a/src/firejail/arp.c
+++ b/src/firejail/arp.c
@@ -277,7 +277,7 @@ static uint32_t arp_random(const char *dev, Bridge *br) {
277 int i = 0; 277 int i = 0;
278 for (i = 0; i < 10; i++) { 278 for (i = 0; i < 10; i++) {
279 dest = start + ((uint32_t) rand()) % range; 279 dest = start + ((uint32_t) rand()) % range;
280 if (dest == ifip) // do not allow the interface address 280 if (dest == ifip || dest == cfg.defaultgw) // do not allow the interface address or the default gateway
281 continue; // try again 281 continue; // try again
282 282
283 // if we've made it up to here, we have a valid address 283 // if we've made it up to here, we have a valid address
@@ -325,7 +325,7 @@ static uint32_t arp_sequential(const char *dev, Bridge *br) {
325 325
326 // loop through addresses and stop as soon as you find an unused one 326 // loop through addresses and stop as soon as you find an unused one
327 while (dest <= last) { 327 while (dest <= last) {
328 if (dest == ifip) { 328 if (dest == ifip || dest == cfg.defaultgw) {
329 dest++; 329 dest++;
330 continue; 330 continue;
331 } 331 }
diff --git a/src/firejail/checkcfg.c b/src/firejail/checkcfg.c
index b42ae1a64..d7690a4fc 100644
--- a/src/firejail/checkcfg.c
+++ b/src/firejail/checkcfg.c
@@ -35,6 +35,7 @@ char *xvfb_extra_params = "";
35char *netfilter_default = NULL; 35char *netfilter_default = NULL;
36unsigned long join_timeout = 5000000; // microseconds 36unsigned long join_timeout = 5000000; // microseconds
37char *config_seccomp_error_action_str = "EPERM"; 37char *config_seccomp_error_action_str = "EPERM";
38char **whitelist_reject_topdirs = NULL;
38 39
39int checkcfg(int val) { 40int checkcfg(int val) {
40 assert(val < CFG_MAX); 41 assert(val < CFG_MAX);
@@ -102,7 +103,6 @@ int checkcfg(int val) {
102 PARSE_YESNO(CFG_USERNS, "userns") 103 PARSE_YESNO(CFG_USERNS, "userns")
103 PARSE_YESNO(CFG_CHROOT, "chroot") 104 PARSE_YESNO(CFG_CHROOT, "chroot")
104 PARSE_YESNO(CFG_FIREJAIL_PROMPT, "firejail-prompt") 105 PARSE_YESNO(CFG_FIREJAIL_PROMPT, "firejail-prompt")
105 PARSE_YESNO(CFG_FOLLOW_SYMLINK_AS_USER, "follow-symlink-as-user")
106 PARSE_YESNO(CFG_FORCE_NONEWPRIVS, "force-nonewprivs") 106 PARSE_YESNO(CFG_FORCE_NONEWPRIVS, "force-nonewprivs")
107 PARSE_YESNO(CFG_SECCOMP, "seccomp") 107 PARSE_YESNO(CFG_SECCOMP, "seccomp")
108 PARSE_YESNO(CFG_WHITELIST, "whitelist") 108 PARSE_YESNO(CFG_WHITELIST, "whitelist")
@@ -242,6 +242,31 @@ int checkcfg(int val) {
242 errExit("strdup"); 242 errExit("strdup");
243 } 243 }
244 244
245 else if (strncmp(ptr, "whitelist-disable-topdir ", 25) == 0) {
246 char *str = strdup(ptr + 25);
247 if (!str)
248 errExit("strdup");
249
250 size_t cnt = 0;
251 size_t sz = 4;
252 whitelist_reject_topdirs = malloc(sz * sizeof(char *));
253 if (!whitelist_reject_topdirs)
254 errExit("malloc");
255
256 char *tok = strtok(str, ",");
257 while (tok) {
258 whitelist_reject_topdirs[cnt++] = tok;
259 if (cnt >= sz) {
260 sz *= 2;
261 whitelist_reject_topdirs = realloc(whitelist_reject_topdirs, sz * sizeof(char *));
262 if (!whitelist_reject_topdirs)
263 errExit("realloc");
264 }
265 tok = strtok(NULL, ",");
266 }
267 whitelist_reject_topdirs[cnt] = NULL;
268 }
269
245 else 270 else
246 goto errout; 271 goto errout;
247 272
@@ -273,7 +298,7 @@ errout:
273 298
274void print_compiletime_support(void) { 299void print_compiletime_support(void) {
275 printf("Compile time support:\n"); 300 printf("Compile time support:\n");
276 printf("\t- Always force nonewprivs support is %s\n", 301 printf("\t- always force nonewprivs support is %s\n",
277#ifdef HAVE_FORCE_NONEWPRIVS 302#ifdef HAVE_FORCE_NONEWPRIVS
278 "enabled" 303 "enabled"
279#else 304#else
diff --git a/src/firejail/chroot.c b/src/firejail/chroot.c
index d7e96cf4c..757ffb1f7 100644
--- a/src/firejail/chroot.c
+++ b/src/firejail/chroot.c
@@ -131,9 +131,9 @@ void fs_chroot(const char *rootdir) {
131 assert(rootdir); 131 assert(rootdir);
132 132
133 // fails if there is any symlink or if rootdir is not a directory 133 // fails if there is any symlink or if rootdir is not a directory
134 int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 134 int parentfd = safer_openat(-1, rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
135 if (parentfd == -1) 135 if (parentfd == -1)
136 errExit("safe_fd"); 136 errExit("safer_openat");
137 // rootdir has to be owned by root and is not allowed to be generally writable, 137 // rootdir has to be owned by root and is not allowed to be generally writable,
138 // this also excludes /tmp and friends 138 // this also excludes /tmp and friends
139 struct stat s; 139 struct stat s;
@@ -215,12 +215,12 @@ void fs_chroot(const char *rootdir) {
215 215
216 if (arg_debug) 216 if (arg_debug)
217 printf("Mounting %s on chroot %s\n", orig_pulse, orig_pulse); 217 printf("Mounting %s on chroot %s\n", orig_pulse, orig_pulse);
218 int src = safe_fd(orig_pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 218 int src = safer_openat(-1, orig_pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
219 if (src == -1) { 219 if (src == -1) {
220 fprintf(stderr, "Error: cannot open %s\n", orig_pulse); 220 fprintf(stderr, "Error: cannot open %s\n", orig_pulse);
221 exit(1); 221 exit(1);
222 } 222 }
223 int dst = safe_fd(pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 223 int dst = safer_openat(-1, pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
224 if (dst == -1) { 224 if (dst == -1) {
225 fprintf(stderr, "Error: cannot open %s\n", pulse); 225 fprintf(stderr, "Error: cannot open %s\n", pulse);
226 exit(1); 226 exit(1);
diff --git a/src/firejail/dbus.c b/src/firejail/dbus.c
index 658b84537..b8aa2c974 100644
--- a/src/firejail/dbus.c
+++ b/src/firejail/dbus.c
@@ -416,7 +416,7 @@ void dbus_proxy_stop(void) {
416} 416}
417 417
418static void socket_overlay(char *socket_path, char *proxy_path) { 418static void socket_overlay(char *socket_path, char *proxy_path) {
419 int fd = safe_fd(proxy_path, O_PATH | O_NOFOLLOW | O_CLOEXEC); 419 int fd = safer_openat(-1, proxy_path, O_PATH | O_NOFOLLOW | O_CLOEXEC);
420 if (fd == -1) 420 if (fd == -1)
421 errExit("opening DBus proxy socket"); 421 errExit("opening DBus proxy socket");
422 struct stat s; 422 struct stat s;
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h
index 18907fc63..dbe4c9dbb 100644
--- a/src/firejail/firejail.h
+++ b/src/firejail/firejail.h
@@ -122,26 +122,22 @@ typedef struct interface_t {
122 uint8_t configured; 122 uint8_t configured;
123} Interface; 123} Interface;
124 124
125typedef struct topdir_t {
126 char *path;
127 int fd;
128} TopDir;
129
125typedef struct profile_entry_t { 130typedef struct profile_entry_t {
126 struct profile_entry_t *next; 131 struct profile_entry_t *next;
127 char *data; // command 132 char *data; // command
128 133
129 // whitelist command parameters 134 // whitelist command parameters
130 char *link; // link name - set if the file is a link 135 struct wparam_t {
131 enum { 136 char *file; // resolved file path
132 WLDIR_HOME = 1, // whitelist in home directory 137 char *link; // link path
133 WLDIR_TMP, // whitelist in /tmp directory 138 TopDir *top; // top level directory
134 WLDIR_MEDIA, // whitelist in /media directory 139 } *wparam;
135 WLDIR_MNT, // whitelist in /mnt directory 140
136 WLDIR_VAR, // whitelist in /var directory
137 WLDIR_DEV, // whitelist in /dev directory
138 WLDIR_OPT, // whitelist in /opt directory
139 WLDIR_SRV, // whitelist in /srv directory
140 WLDIR_ETC, // whitelist in /etc directory
141 WLDIR_SHARE, // whitelist in /usr/share directory
142 WLDIR_MODULE, // whitelist in /sys/module directory
143 WLDIR_RUN // whitelist in /run/user/$uid directory
144 } wldir;
145} ProfileEntry; 141} ProfileEntry;
146 142
147typedef struct config_t { 143typedef struct config_t {
@@ -529,7 +525,7 @@ void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid);
529unsigned extract_timeout(const char *str); 525unsigned extract_timeout(const char *str);
530void disable_file_or_dir(const char *fname); 526void disable_file_or_dir(const char *fname);
531void disable_file_path(const char *path, const char *file); 527void disable_file_path(const char *path, const char *file);
532int safe_fd(const char *path, int flags); 528int safer_openat(int dirfd, const char *path, int flags);
533int has_handler(pid_t pid, int signal); 529int has_handler(pid_t pid, int signal);
534void enter_network_namespace(pid_t pid); 530void enter_network_namespace(pid_t pid);
535int read_pid(const char *name, pid_t *pid); 531int read_pid(const char *name, pid_t *pid);
@@ -775,7 +771,6 @@ enum {
775 CFG_PRIVATE_OPT, 771 CFG_PRIVATE_OPT,
776 CFG_PRIVATE_SRV, 772 CFG_PRIVATE_SRV,
777 CFG_FIREJAIL_PROMPT, 773 CFG_FIREJAIL_PROMPT,
778 CFG_FOLLOW_SYMLINK_AS_USER,
779 CFG_DISABLE_MNT, 774 CFG_DISABLE_MNT,
780 CFG_JOIN, 775 CFG_JOIN,
781 CFG_ARP_PROBES, 776 CFG_ARP_PROBES,
@@ -798,6 +793,7 @@ extern char *xvfb_extra_params;
798extern char *netfilter_default; 793extern char *netfilter_default;
799extern unsigned long join_timeout; 794extern unsigned long join_timeout;
800extern char *config_seccomp_error_action_str; 795extern char *config_seccomp_error_action_str;
796extern char **whitelist_reject_topdirs;
801 797
802int checkcfg(int val); 798int checkcfg(int val);
803void print_compiletime_support(void); 799void print_compiletime_support(void);
diff --git a/src/firejail/fs.c b/src/firejail/fs.c
index fc67a15f3..09de11de9 100644
--- a/src/firejail/fs.c
+++ b/src/firejail/fs.c
@@ -453,7 +453,7 @@ void fs_tmpfs(const char *dir, unsigned check_owner) {
453 if (arg_debug) 453 if (arg_debug)
454 printf("Mounting tmpfs on %s, check owner: %s\n", dir, (check_owner)? "yes": "no"); 454 printf("Mounting tmpfs on %s, check owner: %s\n", dir, (check_owner)? "yes": "no");
455 // get a file descriptor for dir, fails if there is any symlink 455 // get a file descriptor for dir, fails if there is any symlink
456 int fd = safe_fd(dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 456 int fd = safer_openat(-1, dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
457 if (fd == -1) 457 if (fd == -1)
458 errExit("while opening directory"); 458 errExit("while opening directory");
459 struct stat s; 459 struct stat s;
@@ -493,7 +493,7 @@ static void fs_remount_simple(const char *path, OPERATION op) {
493 assert(path); 493 assert(path);
494 494
495 // open path without following symbolic links 495 // open path without following symbolic links
496 int fd1 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); 496 int fd1 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
497 if (fd1 == -1) 497 if (fd1 == -1)
498 goto out; 498 goto out;
499 struct stat s1; 499 struct stat s1;
@@ -559,7 +559,7 @@ static void fs_remount_simple(const char *path, OPERATION op) {
559 559
560 // mount --bind -o remount,ro path 560 // mount --bind -o remount,ro path
561 // need to open path again without following symbolic links 561 // need to open path again without following symbolic links
562 int fd2 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); 562 int fd2 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
563 if (fd2 == -1) 563 if (fd2 == -1)
564 errExit("open"); 564 errExit("open");
565 struct stat s2; 565 struct stat s2;
@@ -992,9 +992,9 @@ void fs_overlayfs(void) {
992 char *firejail; 992 char *firejail;
993 if (asprintf(&firejail, "%s/.firejail", cfg.homedir) == -1) 993 if (asprintf(&firejail, "%s/.firejail", cfg.homedir) == -1)
994 errExit("asprintf"); 994 errExit("asprintf");
995 int fd = safe_fd(firejail, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 995 int fd = safer_openat(-1, firejail, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
996 if (fd == -1) 996 if (fd == -1)
997 errExit("safe_fd"); 997 errExit("safer_openat");
998 free(firejail); 998 free(firejail);
999 // create basedir if it doesn't exist 999 // create basedir if it doesn't exist
1000 // the new directory will be owned by root 1000 // the new directory will be owned by root
diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c
index c7b87235a..4bcefa443 100644
--- a/src/firejail/fs_home.c
+++ b/src/firejail/fs_home.c
@@ -262,10 +262,10 @@ void fs_private_homedir(void) {
262 if (arg_debug) 262 if (arg_debug)
263 printf("Mount-bind %s on top of %s\n", private_homedir, homedir); 263 printf("Mount-bind %s on top of %s\n", private_homedir, homedir);
264 // get file descriptors for homedir and private_homedir, fails if there is any symlink 264 // get file descriptors for homedir and private_homedir, fails if there is any symlink
265 int src = safe_fd(private_homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 265 int src = safer_openat(-1, private_homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
266 if (src == -1) 266 if (src == -1)
267 errExit("opening private directory"); 267 errExit("opening private directory");
268 int dst = safe_fd(homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 268 int dst = safer_openat(-1, homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
269 if (dst == -1) 269 if (dst == -1)
270 errExit("opening home directory"); 270 errExit("opening home directory");
271 // both mount source and target should be owned by the user 271 // both mount source and target should be owned by the user
@@ -576,7 +576,7 @@ void fs_private_home_list(void) {
576 if (arg_debug) 576 if (arg_debug)
577 printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir); 577 printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir);
578 578
579 int fd = safe_fd(homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 579 int fd = safer_openat(-1, homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
580 if (fd == -1) 580 if (fd == -1)
581 errExit("opening home directory"); 581 errExit("opening home directory");
582 // home directory should be owned by the user 582 // home directory should be owned by the user
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c
index 698d47b69..9a7a1bac7 100644
--- a/src/firejail/fs_whitelist.c
+++ b/src/firejail/fs_whitelist.c
@@ -16,50 +16,46 @@
16 * You should have received a copy of the GNU General Public License along 16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19 */
20#include "firejail.h" 20#include "firejail.h"
21#include <sys/mount.h> 21#include <sys/mount.h>
22#include <sys/stat.h> 22#include <sys/stat.h>
23#include <linux/limits.h>
24#include <fnmatch.h> 23#include <fnmatch.h>
25#include <glob.h> 24#include <glob.h>
26#include <dirent.h>
27#include <errno.h> 25#include <errno.h>
28 26
29#include <fcntl.h> 27#include <fcntl.h>
30#ifndef O_PATH 28#ifndef O_PATH
31# define O_PATH 010000000 29#define O_PATH 010000000
32#endif 30#endif
33 31
32#define TOP_MAX 64 // maximum number of top level directories
33
34// mountinfo functionality test; 34// mountinfo functionality test;
35// 1. enable TEST_MOUNTINFO definition 35// 1. enable TEST_MOUNTINFO definition
36// 2. run firejail --whitelist=/any/directory 36// 2. run firejail --whitelist=/any/directory
37//#define TEST_MOUNTINFO 37//#define TEST_MOUNTINFO
38 38
39#define EMPTY_STRING ("") 39static size_t homedir_len = 0; // cache length of homedir string
40static size_t homedir_len; // cache length of homedir string 40static size_t runuser_len = 0; // cache length of runuser string
41static size_t runuser_len; // cache length of runuser string 41static char *runuser = NULL;
42static char *runuser;
43 42
44 43
45static int mkpath(const char* path, mode_t mode) {
46 assert(path && *path);
47 mode |= 0111;
48 44
49 // create directories with uid/gid as root, or as current user if inside home or run/user/$uid directory 45static void whitelist_error(const char *path) {
50 int userprivs = 0; 46 assert(path);
51 if ((strncmp(path, cfg.homedir, homedir_len) == 0 && path[homedir_len] == '/') ||
52 (strncmp(path, runuser, runuser_len) == 0 && path[runuser_len] == '/')) {
53 EUID_USER();
54 userprivs = 1;
55 }
56 47
48 fprintf(stderr, "Error: invalid whitelist path %s\n", path);
49 exit(1);
50}
51
52static int whitelist_mkpath(const char* path, mode_t mode) {
57 // work on a copy of the path 53 // work on a copy of the path
58 char *dup = strdup(path); 54 char *dup = strdup(path);
59 if (!dup) 55 if (!dup)
60 errExit("strdup"); 56 errExit("strdup");
61 57
62 // don't create the last path element 58 // only create leading directories, don't create the file
63 char *p = strrchr(dup, '/'); 59 char *p = strrchr(dup, '/');
64 assert(p); 60 assert(p);
65 *p = '\0'; 61 *p = '\0';
@@ -69,10 +65,10 @@ static int mkpath(const char* path, mode_t mode) {
69 errExit("open"); 65 errExit("open");
70 66
71 // traverse the path, return -1 if a symlink is encountered 67 // traverse the path, return -1 if a symlink is encountered
72 int done = 0;
73 int fd = -1; 68 int fd = -1;
69 int done = 0;
74 char *tok = strtok(dup, "/"); 70 char *tok = strtok(dup, "/");
75 assert(tok); // path is no top level directory 71 assert(tok);
76 while (tok) { 72 while (tok) {
77 // create the directory if necessary 73 // create the directory if necessary
78 if (mkdirat(parentfd, tok, mode) == -1) { 74 if (mkdirat(parentfd, tok, mode) == -1) {
@@ -81,9 +77,6 @@ static int mkpath(const char* path, mode_t mode) {
81 perror("mkdir"); 77 perror("mkdir");
82 close(parentfd); 78 close(parentfd);
83 free(dup); 79 free(dup);
84 if (userprivs) {
85 EUID_ROOT();
86 }
87 return -1; 80 return -1;
88 } 81 }
89 } 82 }
@@ -96,9 +89,6 @@ static int mkpath(const char* path, mode_t mode) {
96 perror("open"); 89 perror("open");
97 close(parentfd); 90 close(parentfd);
98 free(dup); 91 free(dup);
99 if (userprivs) {
100 EUID_ROOT();
101 }
102 return -1; 92 return -1;
103 } 93 }
104 // move on to next path segment 94 // move on to next path segment
@@ -111,195 +101,110 @@ static int mkpath(const char* path, mode_t mode) {
111 fs_logger2("mkpath", path); 101 fs_logger2("mkpath", path);
112 102
113 free(dup); 103 free(dup);
114 if (userprivs) {
115 EUID_ROOT();
116 }
117 return fd; 104 return fd;
118} 105}
119 106
120static void whitelist_path(ProfileEntry *entry) { 107static void whitelist_file(int dirfd, const char *relpath, const char *path) {
121 assert(entry); 108 assert(relpath && path);
122 const char *path = entry->data + 10;
123 const char *fname;
124 char *wfile = NULL;
125
126 if (entry->wldir == WLDIR_HOME) {
127 if (strncmp(path, cfg.homedir, homedir_len) != 0 || path[homedir_len] != '/')
128 // either symlink pointing outside home directory
129 // or entire home directory, skip the mount
130 return;
131
132 fname = path + homedir_len + 1; // strlen("/home/user/")
133
134 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_HOME_USER_DIR, fname) == -1)
135 errExit("asprintf");
136 }
137 else if (entry->wldir == WLDIR_TMP) {
138 fname = path + 5; // strlen("/tmp/")
139
140 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_TMP_DIR, fname) == -1)
141 errExit("asprintf");
142 }
143 else if (entry->wldir == WLDIR_MEDIA) {
144 fname = path + 7; // strlen("/media/")
145
146 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_MEDIA_DIR, fname) == -1)
147 errExit("asprintf");
148 }
149 else if (entry->wldir == WLDIR_MNT) {
150 fname = path + 5; // strlen("/mnt/")
151
152 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_MNT_DIR, fname) == -1)
153 errExit("asprintf");
154 }
155 else if (entry->wldir == WLDIR_VAR) {
156 if (strncmp(path, "/var/", 5) != 0)
157 // symlink pointing outside /var, skip the mount
158 return;
159
160 fname = path + 5; // strlen("/var/")
161 109
162 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_VAR_DIR, fname) == -1) 110 // open mount source, using a file descriptor that refers to the
163 errExit("asprintf"); 111 // top level directory
164 } 112 // as the top level directory was opened before mounting the tmpfs
165 else if (entry->wldir == WLDIR_DEV) { 113 // we still have full access to all directory contents
166 if (strncmp(path, "/dev/", 5) != 0) 114 // take care to not follow symbolic links (dirfd was obtained without
167 // symlink pointing outside /dev, skip the mount 115 // following a link, too)
168 return; 116 int fd = safer_openat(dirfd, relpath, O_PATH|O_NOFOLLOW|O_CLOEXEC);
169
170 fname = path + 5; // strlen("/dev/")
171
172 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_DEV_DIR, fname) == -1)
173 errExit("asprintf");
174 }
175 else if (entry->wldir == WLDIR_OPT) {
176 fname = path + 5; // strlen("/opt/")
177
178 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_OPT_DIR, fname) == -1)
179 errExit("asprintf");
180 }
181 else if (entry->wldir == WLDIR_SRV) {
182 fname = path + 5; // strlen("/srv/")
183
184 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_SRV_DIR, fname) == -1)
185 errExit("asprintf");
186 }
187 else if (entry->wldir == WLDIR_ETC) {
188 if (strncmp(path, "/etc/", 5) != 0)
189 // symlink pointing outside /etc, skip the mount
190 return;
191
192 fname = path + 5; // strlen("/etc/")
193
194 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_ETC_DIR, fname) == -1)
195 errExit("asprintf");
196 }
197 else if (entry->wldir == WLDIR_SHARE) {
198 fname = path + 11; // strlen("/usr/share/")
199
200 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_SHARE_DIR, fname) == -1)
201 errExit("asprintf");
202 }
203 else if (entry->wldir == WLDIR_MODULE) {
204 fname = path + 12; // strlen("/sys/module/")
205
206 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_MODULE_DIR, fname) == -1)
207 errExit("asprintf");
208 }
209 else if (entry->wldir == WLDIR_RUN) {
210 fname = path + runuser_len + 1; // strlen("/run/user/$uid/")
211
212 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_RUN_USER_DIR, fname) == -1)
213 errExit("asprintf");
214 }
215 assert(wfile);
216
217 if (arg_debug || arg_debug_whitelists)
218 printf("Whitelisting %s\n", path);
219
220 // confirm again the mount source exists and there is no symlink
221 struct stat wfilestat;
222 EUID_USER();
223 int fd = safe_fd(wfile, O_PATH|O_NOFOLLOW|O_CLOEXEC);
224 EUID_ROOT();
225 if (fd == -1) { 117 if (fd == -1) {
226 if (arg_debug || arg_debug_whitelists) 118 if (arg_debug || arg_debug_whitelists)
227 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); 119 printf("Debug %d: skip whitelist %s\n", __LINE__, path);
228 free(wfile);
229 return; 120 return;
230 } 121 }
231 if (fstat(fd, &wfilestat) == -1) 122 struct stat s;
123 if (fstat(fd, &s) == -1)
232 errExit("fstat"); 124 errExit("fstat");
233 close(fd); 125 if (S_ISLNK(s.st_mode)) {
234 if (S_ISLNK(wfilestat.st_mode)) {
235 if (arg_debug || arg_debug_whitelists) 126 if (arg_debug || arg_debug_whitelists)
236 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); 127 printf("Debug %d: skip whitelist %s\n", __LINE__, path);
237 free(wfile); 128 close(fd);
238 return; 129 return;
239 } 130 }
240 131
241 // create path of the mount target if necessary 132 // create mount target as root, except if inside home or run/user/$UID directory
242 int fd2 = mkpath(path, 0755); 133 int userprivs = 0;
134 if ((strncmp(path, cfg.homedir, homedir_len) == 0 && path[homedir_len] == '/') ||
135 (strncmp(path, runuser, runuser_len) == 0 && path[runuser_len] == '/')) {
136 EUID_USER();
137 userprivs = 1;
138 }
139
140 // create path of the mount target
141 int fd2 = whitelist_mkpath(path, 0755);
243 if (fd2 == -1) { 142 if (fd2 == -1) {
244 // something went wrong during path creation or a symlink was found; 143 // something went wrong during path creation or a symlink was found;
245 // if there is a symlink somewhere in the path of the mount target, 144 // if there is a symlink somewhere in the path of the mount target,
246 // assume the file is whitelisted already 145 // assume the file is whitelisted already
247 if (arg_debug || arg_debug_whitelists) 146 if (arg_debug || arg_debug_whitelists)
248 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); 147 printf("Debug %d: skip whitelist %s\n", __LINE__, path);
249 free(wfile); 148 close(fd);
149 if (userprivs)
150 EUID_ROOT();
250 return; 151 return;
251 } 152 }
252 153
253 // get file name of the mount target 154 // get file name of the mount target
254 const char *file = gnu_basename(path); 155 const char *file = gnu_basename(path);
255 156
256 // create the mount target if necessary and open it, a symlink is rejected 157 // create mount target itself and open it, a symlink is rejected
257 int fd3 = -1; 158 int fd3 = -1;
258 if (S_ISDIR(wfilestat.st_mode)) { 159 if (S_ISDIR(s.st_mode)) {
259 // directory foo can exist already: 160 // directory foo can exist already:
260 // firejail --whitelist=/foo/bar --whitelist=/foo 161 // firejail --whitelist=~/foo/bar --whitelist=~/foo
261 if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) { 162 if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) {
262 if (arg_debug || arg_debug_whitelists) { 163 if (arg_debug || arg_debug_whitelists) {
263 perror("mkdir"); 164 perror("mkdir");
264 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); 165 printf("Debug %d: skip whitelist %s\n", __LINE__, path);
265 } 166 }
167 close(fd);
266 close(fd2); 168 close(fd2);
267 free(wfile); 169 if (userprivs)
170 EUID_ROOT();
268 return; 171 return;
269 } 172 }
270 fd3 = openat(fd2, file, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 173 fd3 = openat(fd2, file, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
271 } 174 }
272 else { 175 else
273 // create an empty file, fails with EEXIST if it is whitelisted already: 176 // create an empty file, fails with EEXIST if it is whitelisted already:
274 // firejail --whitelist=/foo --whitelist=/foo/bar 177 // firejail --whitelist=/foo --whitelist=/foo/bar
275 fd3 = openat(fd2, file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR); 178 fd3 = openat(fd2, file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR);
276 }
277 179
278 if (fd3 == -1) { 180 if (fd3 == -1) {
279 if (arg_debug || arg_debug_whitelists) { 181 if (errno != EEXIST && (arg_debug || arg_debug_whitelists)) {
280 if (errno != EEXIST) { 182 perror("open");
281 perror("open"); 183 printf("Debug %d: skip whitelist %s\n", __LINE__, path);
282 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path);
283 }
284 } 184 }
185 close(fd);
285 close(fd2); 186 close(fd2);
286 free(wfile); 187 if (userprivs)
188 EUID_ROOT();
287 return; 189 return;
288 } 190 }
191
289 close(fd2); 192 close(fd2);
193 if (userprivs)
194 EUID_ROOT();
290 195
291 fs_logger2("whitelist", path); 196 if (arg_debug || arg_debug_whitelists)
197 printf("Whitelisting %s\n", path);
292 198
293 // in order to make this mount resilient against symlink attacks, use 199 // in order to make this mount resilient against symlink attacks, use
294 // a magic link in /proc/self/fd instead of mounting on path directly 200 // magic links in /proc/self/fd instead of mounting the paths directly
295 char *proc; 201 char *proc_src, *proc_dst;
296 if (asprintf(&proc, "/proc/self/fd/%d", fd3) == -1) 202 if (asprintf(&proc_src, "/proc/self/fd/%d", fd) == -1)
203 errExit("asprintf");
204 if (asprintf(&proc_dst, "/proc/self/fd/%d", fd3) == -1)
297 errExit("asprintf"); 205 errExit("asprintf");
298 if (mount(wfile, proc, NULL, MS_BIND|MS_REC, NULL) < 0) 206 if (mount(proc_src, proc_dst, NULL, MS_BIND | MS_REC, NULL) < 0)
299 errExit("mount bind"); 207 errExit("mount bind");
300 free(proc);
301 close(fd3);
302
303 // check the last mount operation 208 // check the last mount operation
304 MountData *mptr = get_last_mount(); // will do exit(1) if the mount cannot be found 209 MountData *mptr = get_last_mount(); // will do exit(1) if the mount cannot be found
305#ifdef TEST_MOUNTINFO 210#ifdef TEST_MOUNTINFO
@@ -316,35 +221,50 @@ static void whitelist_path(ProfileEntry *entry) {
316 // - there should be more than one '/' char in dest string 221 // - there should be more than one '/' char in dest string
317 if (mptr->dir == strrchr(mptr->dir, '/')) 222 if (mptr->dir == strrchr(mptr->dir, '/'))
318 errLogExit("invalid whitelist mount"); 223 errLogExit("invalid whitelist mount");
319 // confirm the right file was mounted by comparing device and inode numbers 224 free(proc_src);
320 int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); 225 free(proc_dst);
321 if (fd4 == -1) 226 close(fd);
322 errExit("safe_fd"); 227 close(fd3);
323 struct stat s; 228 fs_logger2("whitelist", path);
324 if (fstat(fd4, &s) == -1)
325 errExit("fstat");
326 if (s.st_dev != wfilestat.st_dev || s.st_ino != wfilestat.st_ino)
327 errLogExit("invalid whitelist mount");
328 close(fd4);
329
330 free(wfile);
331 return;
332} 229}
333 230
334static void whitelist_home(int topdir) { 231static void whitelist_symlink(const char *link, const char *target) {
335 ProfileEntry entry; 232 assert(link && target);
336 memset(&entry, 0, sizeof(entry)); 233
337 char *cmd; 234 // create files as root, except if inside home or run/user/$UID directory
338 if (asprintf(&cmd, "whitelist %s", cfg.homedir) == -1) 235 int userprivs = 0;
339 errExit("asprintf"); 236 if ((strncmp(link, cfg.homedir, homedir_len) == 0 && link[homedir_len] == '/') ||
340 entry.data = cmd; 237 (strncmp(link, runuser, runuser_len) == 0 && link[runuser_len] == '/')) {
341 entry.wldir = topdir; 238 EUID_USER();
342 // creates path owned by root, except homedir is inside /run/user/$uid 239 userprivs = 1;
343 // does nothing if homedir does not exist 240 }
344 whitelist_path(&entry); 241
345 free(cmd); 242 int fd = whitelist_mkpath(link, 0755);
346} 243 if (fd == -1) {
244 if (arg_debug || arg_debug_whitelists)
245 printf("Debug %d: cannot create symbolic link %s\n", __LINE__, link);
246 if (userprivs)
247 EUID_ROOT();
248 return;
249 }
250
251 // get file name of symlink
252 const char *file = gnu_basename(link);
253
254 // create the link
255 if (symlinkat(target, fd, file) == -1) {
256 if (arg_debug || arg_debug_whitelists) {
257 perror("symlink");
258 printf("Debug %d: cannot create symbolic link %s\n", __LINE__, link);
259 }
260 }
261 else if (arg_debug || arg_debug_whitelists)
262 printf("Created symbolic link %s -> %s\n", link, target);
347 263
264 close(fd);
265 if (userprivs)
266 EUID_ROOT();
267}
348 268
349static void globbing(const char *pattern) { 269static void globbing(const char *pattern) {
350 assert(pattern); 270 assert(pattern);
@@ -363,6 +283,11 @@ static void globbing(const char *pattern) {
363 // testing for GLOB_NOCHECK - no pattern matched returns the original pattern 283 // testing for GLOB_NOCHECK - no pattern matched returns the original pattern
364 if (strcmp(globbuf.gl_pathv[i], pattern) == 0) 284 if (strcmp(globbuf.gl_pathv[i], pattern) == 0)
365 continue; 285 continue;
286 // foo/* expands to foo/. and foo/..
287 const char *base = gnu_basename(globbuf.gl_pathv[i]);
288 if (strcmp(base, ".") == 0 ||
289 strcmp(base, "..") == 0)
290 continue;
366 291
367 // build the new profile command 292 // build the new profile command
368 char *newcmd; 293 char *newcmd;
@@ -378,6 +303,230 @@ static void globbing(const char *pattern) {
378 globfree(&globbuf); 303 globfree(&globbuf);
379} 304}
380 305
306// mount tmpfs on all top level directories
307// home directories *inside* /run/user/$UID are not fully supported
308static void tmpfs_topdirs(const TopDir *topdirs) {
309 int tmpfs_home = 0;
310 int tmpfs_runuser = 0;
311
312 int i;
313 for (i = 0; i < TOP_MAX && topdirs[i].path; i++) {
314 // do nested top level directories last
315 // this way '--whitelist=nested_top_level_dir'
316 // yields the full, unmodified directory
317 // instead of the tmpfs
318 if (strcmp(topdirs[i].path, cfg.homedir) == 0) {
319 tmpfs_home = 1;
320 continue;
321 }
322 if (strcmp(topdirs[i].path, runuser) == 0) {
323 tmpfs_runuser = 1;
324 continue;
325 }
326
327 // special case /run
328 // open /run/firejail, so it can be restored right after mounting the tmpfs
329 int fd = -1;
330 if (strcmp(topdirs[i].path, "/run") == 0) {
331 fd = open(RUN_FIREJAIL_DIR, O_PATH|O_CLOEXEC);
332 if (fd == -1)
333 errExit("open");
334 }
335
336 // mount tmpfs
337 fs_tmpfs(topdirs[i].path, 0);
338
339 // init tmpfs
340 if (strcmp(topdirs[i].path, "/run") == 0) {
341 // restore /run/firejail directory
342 if (mkdir(RUN_FIREJAIL_DIR, 0755) == -1)
343 errExit("mkdir");
344 char *proc;
345 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
346 errExit("asprintf");
347 if (mount(proc, RUN_FIREJAIL_DIR, NULL, MS_BIND | MS_REC, NULL) < 0)
348 errExit("mount bind");
349 free(proc);
350 close(fd);
351 fs_logger2("whitelist", RUN_FIREJAIL_DIR);
352
353 // restore /run/user/$UID directory
354 // get path relative to /run
355 const char *rel = runuser + 5;
356 whitelist_file(topdirs[i].fd, rel, runuser);
357 }
358 else if (strcmp(topdirs[i].path, "/tmp") == 0) {
359 // fix pam-tmpdir (#2685)
360 const char *env = env_get("TMP");
361 if (env) {
362 char *pamtmpdir;
363 if (asprintf(&pamtmpdir, "/tmp/user/%u", getuid()) == -1)
364 errExit("asprintf");
365 if (strcmp(env, pamtmpdir) == 0) {
366 // create empty user-owned /tmp/user/$UID directory
367 mkdir_attr("/tmp/user", 0711, 0, 0);
368 selinux_relabel_path("/tmp/user", "/tmp/user");
369 fs_logger("mkdir /tmp/user");
370 mkdir_attr(pamtmpdir, 0700, getuid(), 0);
371 selinux_relabel_path(pamtmpdir, pamtmpdir);
372 fs_logger2("mkdir", pamtmpdir);
373 }
374 free(pamtmpdir);
375 }
376 }
377
378 // restore user home directory if it is masked by the tmpfs
379 // creates path owned by root
380 // does nothing if user home directory doesn't exist
381 size_t topdir_len = strlen(topdirs[i].path);
382 if (strncmp(topdirs[i].path, cfg.homedir, topdir_len) == 0 && cfg.homedir[topdir_len] == '/') {
383 // get path relative to top level directory
384 const char *rel = cfg.homedir + topdir_len + 1;
385 whitelist_file(topdirs[i].fd, rel, cfg.homedir);
386 }
387
388 selinux_relabel_path(topdirs[i].path, topdirs[i].path);
389 }
390
391 // user home directory
392 if (tmpfs_home)
393 // checks owner if outside /home
394 fs_private();
395
396 // /run/user/$UID directory
397 if (tmpfs_runuser) {
398 fs_tmpfs(runuser, 0);
399 selinux_relabel_path(runuser, runuser);
400 }
401}
402
403static int reject_topdir(const char *dir) {
404 if (!whitelist_reject_topdirs)
405 return 0;
406
407 size_t i;
408 for (i = 0; whitelist_reject_topdirs[i]; i++) {
409 if (strcmp(dir, whitelist_reject_topdirs[i]) == 0)
410 return 1;
411 }
412 return 0;
413}
414
415// keep track of whitelist top level directories by adding them to an array
416// open each directory
417static TopDir *add_topdir(const char *dir, TopDir *topdirs, const char *path) {
418 assert(dir && path);
419
420 // /proc and /sys are not allowed
421 if (strcmp(dir, "/") == 0 ||
422 strcmp(dir, "/proc") == 0 ||
423 strcmp(dir, "/sys") == 0)
424 whitelist_error(path);
425
426 // whitelisting home directory is disabled if --private option is present
427 if (arg_private && strcmp(dir, cfg.homedir) == 0) {
428 if (arg_debug || arg_debug_whitelists)
429 printf("Debug %d: skip %s - a private home dir is configured!\n", __LINE__, path);
430 return NULL;
431 }
432
433 // do nothing if directory doesn't exist
434 struct stat s;
435 if (lstat(dir, &s) != 0) {
436 if (arg_debug || arg_debug_whitelists)
437 printf("Cannot access whitelist top level directory %s: %s\n", dir, strerror(errno));
438 return NULL;
439 }
440 // do nothing if directory is a link
441 if (!S_ISDIR(s.st_mode)) {
442 if (S_ISLNK(s.st_mode)) {
443 fwarning("skipping whitelist %s because %s is a symbolic link\n", path, dir);
444 return NULL;
445 }
446 whitelist_error(path);
447 }
448 // do nothing if directory is disabled by administrator
449 if (reject_topdir(dir)) {
450 fmessage("Whitelist top level directory %s is disabled in Firejail configuration file\n", dir);
451 return NULL;
452 }
453
454 // add directory to array
455 if (arg_debug || arg_debug_whitelists)
456 printf("Adding whitelist top level directory %s\n", dir);
457 static int cnt = 0;
458 if (cnt >= TOP_MAX) {
459 fprintf(stderr, "Error: too many whitelist top level directories\n");
460 exit(1);
461 }
462 TopDir *rv = topdirs + cnt;
463 cnt++;
464
465 rv->path = strdup(dir);
466 if (!rv->path)
467 errExit("strdup");
468
469 // open the directory, don't follow symbolic links
470 rv->fd = safer_openat(-1, rv->path, O_PATH|O_NOFOLLOW|O_DIRECTORY|O_CLOEXEC);
471 if (rv->fd == -1) {
472 fprintf(stderr, "Error: cannot open %s\n", rv->path);
473 exit(1);
474 }
475
476 return rv;
477}
478
479static TopDir *have_topdir(const char *dir, TopDir *topdirs) {
480 assert(dir);
481
482 int i;
483 for (i = 0; i < TOP_MAX; i++) {
484 TopDir *rv = topdirs + i;
485 if (!rv->path)
486 break;
487 if (strcmp(dir, rv->path) == 0)
488 return rv;
489 }
490 return NULL;
491}
492
493static char *extract_topdir(const char *path) {
494 assert(path);
495
496 char *dup = strdup(path);
497 if (!dup)
498 errExit("strdup");
499
500 // user home directory can be anywhere; disconnect user home
501 // whitelisting from top level directory whitelisting
502 // by treating user home as separate whitelist top level directory
503 if (strncmp(dup, cfg.homedir, homedir_len) == 0 && dup[homedir_len] == '/')
504 dup[homedir_len] = '\0';
505 // /run/user/$UID is treated as top level directory
506 else if (strncmp(dup, runuser, runuser_len) == 0 && dup[runuser_len] == '/')
507 dup[runuser_len] = '\0';
508 // whitelisting in /sys is not allowed, but /sys/module is an exception
509 // and is treated as top level directory here
510 else if (strncmp(dup, "/sys/module", 11) == 0 && dup[11] == '/')
511 dup[11] = '\0';
512 // treat /usr subdirectories as top level directories
513 else if (strncmp(dup, "/usr/", 5) == 0) {
514 char *p = strchr(dup+5, '/');
515 if (!p)
516 whitelist_error(path);
517 *p = '\0';
518 }
519 // all other top level directories
520 else {
521 assert(dup[0] == '/');
522 char *p = strchr(dup+1, '/');
523 if (!p)
524 whitelist_error(path);
525 *p = '\0';
526 }
527
528 return dup;
529}
381 530
382void fs_whitelist(void) { 531void fs_whitelist(void) {
383 ProfileEntry *entry = cfg.profile; 532 ProfileEntry *entry = cfg.profile;
@@ -389,29 +538,18 @@ void fs_whitelist(void) {
389 runuser_len = strlen(runuser); 538 runuser_len = strlen(runuser);
390 homedir_len = strlen(cfg.homedir); 539 homedir_len = strlen(cfg.homedir);
391 540
392 char *new_name = NULL;
393 int home_dir = 0; // /home/user directory flag
394 int tmp_dir = 0; // /tmp directory flag
395 int media_dir = 0; // /media directory flag
396 int mnt_dir = 0; // /mnt directory flag
397 int var_dir = 0; // /var directory flag
398 int dev_dir = 0; // /dev directory flag
399 int opt_dir = 0; // /opt directory flag
400 int srv_dir = 0; // /srv directory flag
401 int etc_dir = 0; // /etc directory flag
402 int share_dir = 0; // /usr/share directory flag
403 int module_dir = 0; // /sys/module directory flag
404 int run_dir = 0; // /run/user/$uid directory flag
405
406 size_t nowhitelist_c = 0; 541 size_t nowhitelist_c = 0;
407 size_t nowhitelist_m = 32; 542 size_t nowhitelist_m = 32;
408 char **nowhitelist = calloc(nowhitelist_m, sizeof(*nowhitelist)); 543 char **nowhitelist = calloc(nowhitelist_m, sizeof(*nowhitelist));
409 if (nowhitelist == NULL) 544 if (nowhitelist == NULL)
410 errExit("failed allocating memory for nowhitelist entries"); 545 errExit("calloc");
546
547 TopDir *topdirs = calloc(TOP_MAX, sizeof(*topdirs));
548 if (topdirs == NULL)
549 errExit("calloc");
411 550
412 // verify whitelist files, extract symbolic links, etc. 551 // verify whitelist files, extract symbolic links, etc.
413 EUID_USER(); 552 EUID_USER();
414 struct stat s;
415 while (entry) { 553 while (entry) {
416 int nowhitelist_flag = 0; 554 int nowhitelist_flag = 0;
417 555
@@ -424,48 +562,73 @@ void fs_whitelist(void) {
424 entry = entry->next; 562 entry = entry->next;
425 continue; 563 continue;
426 } 564 }
427 char *dataptr = (nowhitelist_flag)? entry->data + 12: entry->data + 10; 565 if (arg_debug || arg_debug_whitelists)
428 566 printf("Debug %d: %s\n", __LINE__, entry->data);
429 // replace ~/ or ${HOME} into /home/username or resolve macro 567
430 new_name = expand_macros(dataptr); 568 const char *dataptr = (nowhitelist_flag)? entry->data + 12: entry->data + 10;
431 assert(new_name); 569
432 570 // replace ~ into /home/username or resolve macro
433 // mount empty home directory if resolving the macro was not successful 571 char *expanded = expand_macros(dataptr);
434 if (is_macro(new_name) && macro_id(new_name) > -1) { 572
435 // no warning if home does not exist (e.g. in a chroot) 573 // check if respolving the macro was successful
436 if (stat(cfg.homedir, &s) == 0 && !nowhitelist_flag && !arg_private) { 574 if (is_macro(expanded) && macro_id(expanded) > -1) {
437 home_dir = 1; 575 if (!nowhitelist_flag && (have_topdir(cfg.homedir, topdirs) || add_topdir(cfg.homedir, topdirs, expanded)) && !arg_quiet) {
438 if (!arg_quiet) { 576 fprintf(stderr, "***\n");
439 fprintf(stderr, "***\n"); 577 fprintf(stderr, "*** Warning: cannot whitelist %s directory\n", expanded);
440 fprintf(stderr, "*** Warning: cannot whitelist %s directory\n", new_name); 578 fprintf(stderr, "*** Any file saved in this directory will be lost when the sandbox is closed.\n");
441 fprintf(stderr, "*** Any file saved in this directory will be lost when the sandbox is closed.\n"); 579 fprintf(stderr, "***\n");
442 fprintf(stderr, "***\n");
443 }
444 } 580 }
445 entry->data = EMPTY_STRING;
446 entry = entry->next; 581 entry = entry->next;
447 free(new_name); 582 free(expanded);
448 continue; 583 continue;
449 } 584 }
450 585
451 // remove trailing slashes and single dots 586 if (arg_debug || arg_debug_whitelists)
452 if (!nowhitelist_flag) 587 printf("Debug %d: expanded: %s\n", __LINE__, expanded);
453 trim_trailing_slash_or_dot(new_name); 588
589 // path should be absolute at this point
590 if (expanded[0] != '/')
591 whitelist_error(expanded);
592
593 // sane pathname
594 char *new_name = clean_pathname(expanded);
595 free(expanded);
454 596
455 if (arg_debug || arg_debug_whitelists) 597 if (arg_debug || arg_debug_whitelists)
456 fprintf(stderr, "Debug %d: new_name #%s#, %s\n", __LINE__, new_name, (nowhitelist_flag)? "nowhitelist": "whitelist"); 598 printf("Debug %d: new_name: %s\n", __LINE__, new_name);
599
600 if (strstr(new_name, ".."))
601 whitelist_error(new_name);
457 602
458 // valid path referenced to filesystem root 603 // /run/firejail is not allowed
459 if (*new_name != '/') { 604 if (strncmp(new_name, RUN_FIREJAIL_DIR, strlen(RUN_FIREJAIL_DIR)) == 0)
605 whitelist_error(new_name);
606
607 TopDir *current_top = NULL;
608 if (!nowhitelist_flag) {
609 // extract whitelist top level directory
610 char *dir = extract_topdir(new_name);
460 if (arg_debug || arg_debug_whitelists) 611 if (arg_debug || arg_debug_whitelists)
461 fprintf(stderr, "Debug %d: \n", __LINE__); 612 printf("Debug %d: dir: %s\n", __LINE__, dir);
462 goto errexit; 613
614 // check if this top level directory has been processed already
615 current_top = have_topdir(dir, topdirs);
616 if (!current_top) { // got new top level directory
617 current_top = add_topdir(dir, topdirs, new_name);
618 if (!current_top) { // skip this command, top level directory not valid
619 entry = entry->next;
620 free(new_name);
621 free(dir);
622 continue;
623 }
624 }
625 free(dir);
463 } 626 }
464 627
465 // extract the absolute path of the file 628 // extract resolved path of the file
466 // realpath function will fail with ENOENT if the file is not found or with EACCES if user has no permission 629 // realpath function will fail with ENOENT if the file is not found or with EACCES if user has no permission
467 // special processing for /dev/fd, /dev/stdin, /dev/stdout and /dev/stderr 630 // special processing for /dev/fd, /dev/stdin, /dev/stdout and /dev/stderr
468 char *fname; 631 char *fname = NULL;
469 if (strcmp(new_name, "/dev/fd") == 0) 632 if (strcmp(new_name, "/dev/fd") == 0)
470 fname = strdup("/proc/self/fd"); 633 fname = strdup("/proc/self/fd");
471 else if (strcmp(new_name, "/dev/stdin") == 0) 634 else if (strcmp(new_name, "/dev/stdin") == 0)
@@ -477,60 +640,30 @@ void fs_whitelist(void) {
477 else 640 else
478 fname = realpath(new_name, NULL); 641 fname = realpath(new_name, NULL);
479 642
480 // if this is not a real path, let's try globbing
481 // mark this entry as EMPTY_STRING and push the new paths at the end of profile entry list
482 // the new profile entries will be processed in this loop
483 // currently there is no globbing support for nowhitelist
484 if (!fname && !nowhitelist_flag)
485 globbing(new_name);
486
487 if (!fname) { 643 if (!fname) {
488 // file not found, blank the entry in the list and continue
489 if (arg_debug || arg_debug_whitelists) { 644 if (arg_debug || arg_debug_whitelists) {
490 printf("Removed whitelist/nowhitelist path: %s\n", entry->data); 645 printf("Removed path: %s\n", entry->data);
491 printf("\texpanded: %s\n", new_name); 646 printf("\texpanded: %s\n", new_name);
492 printf("\treal path: (null)\n"); 647 printf("\trealpath: (null)\n");
493 printf("\t");fflush(0); 648 printf("\t%s\n", strerror(errno));
494 perror("realpath");
495 } 649 }
496 650
497 // if 1 the file was not found; mount an empty directory
498 if (!nowhitelist_flag) { 651 if (!nowhitelist_flag) {
499 if (strncmp(new_name, cfg.homedir, homedir_len) == 0 && new_name[homedir_len] == '/') { 652 // if this is not a real path, let's try globbing
500 if(!arg_private) 653 // push the new paths at the end of profile entry list
501 home_dir = 1; 654 // the new profile entries will be processed in this loop
502 } 655 // currently there is no globbing support for nowhitelist
503 else if (strncmp(new_name, "/tmp/", 5) == 0) 656 globbing(new_name);
504 tmp_dir = 1;
505 else if (strncmp(new_name, "/media/", 7) == 0)
506 media_dir = 1;
507 else if (strncmp(new_name, "/mnt/", 5) == 0)
508 mnt_dir = 1;
509 else if (strncmp(new_name, "/var/", 5) == 0)
510 var_dir = 1;
511 else if (strncmp(new_name, "/dev/", 5) == 0)
512 dev_dir = 1;
513 else if (strncmp(new_name, "/opt/", 5) == 0)
514 opt_dir = 1;
515 else if (strncmp(new_name, "/srv/", 5) == 0)
516 srv_dir = 1;
517 else if (strncmp(new_name, "/etc/", 5) == 0)
518 etc_dir = 1;
519 else if (strncmp(new_name, "/usr/share/", 11) == 0)
520 share_dir = 1;
521 else if (strncmp(new_name, "/sys/module/", 12) == 0)
522 module_dir = 1;
523 else if (strncmp(new_name, runuser, runuser_len) == 0 && new_name[runuser_len] == '/')
524 run_dir = 1;
525 } 657 }
526 658
527 entry->data = EMPTY_STRING;
528 entry = entry->next; 659 entry = entry->next;
529 free(new_name); 660 free(new_name);
530 continue; 661 continue;
531 } 662 }
532 else if (arg_debug_whitelists) 663
533 printf("real path %s\n", fname); 664 // /run/firejail is not allowed
665 if (strncmp(fname, RUN_FIREJAIL_DIR, strlen(RUN_FIREJAIL_DIR)) == 0)
666 whitelist_error(fname);
534 667
535 if (nowhitelist_flag) { 668 if (nowhitelist_flag) {
536 // store the path in nowhitelist array 669 // store the path in nowhitelist array
@@ -544,175 +677,12 @@ void fs_whitelist(void) {
544 errExit("failed increasing memory for nowhitelist entries"); 677 errExit("failed increasing memory for nowhitelist entries");
545 } 678 }
546 nowhitelist[nowhitelist_c++] = fname; 679 nowhitelist[nowhitelist_c++] = fname;
547 entry->data = EMPTY_STRING;
548 entry = entry->next; 680 entry = entry->next;
549 free(new_name); 681 free(new_name);
550 continue; 682 continue;
551 } 683 }
552
553 // check for supported directories
554 if (strncmp(new_name, cfg.homedir, homedir_len) == 0 && new_name[homedir_len] == '/') {
555 // whitelisting home directory is disabled if --private option is present
556 if (arg_private) {
557 if (arg_debug || arg_debug_whitelists)
558 printf("\"%s\" disabled by --private\n", entry->data);
559
560 entry->data = EMPTY_STRING;
561 entry = entry->next;
562 free(fname);
563 free(new_name);
564 continue;
565 }
566
567 entry->wldir = WLDIR_HOME;
568 home_dir = 1;
569 if (arg_debug || arg_debug_whitelists)
570 fprintf(stderr, "Debug %d: fname #%s#, cfg.homedir #%s#\n",
571 __LINE__, fname, cfg.homedir);
572
573 // both path and absolute path are in user home,
574 // if not check if the symlink destination is owned by the user
575 if (strncmp(fname, cfg.homedir, homedir_len) != 0 || fname[homedir_len] != '/') {
576 if (checkcfg(CFG_FOLLOW_SYMLINK_AS_USER)) {
577 if (stat(fname, &s) == 0 && s.st_uid != getuid()) {
578 free(fname);
579 goto errexit;
580 }
581 }
582 }
583 }
584 else if (strncmp(new_name, "/tmp/", 5) == 0) {
585 entry->wldir = WLDIR_TMP;
586 tmp_dir = 1;
587
588 // both path and absolute path are under /tmp
589 if (strncmp(fname, "/tmp/", 5) != 0) {
590 free(fname);
591 goto errexit;
592 }
593 }
594 else if (strncmp(new_name, "/media/", 7) == 0) {
595 entry->wldir = WLDIR_MEDIA;
596 media_dir = 1;
597 // both path and absolute path are under /media
598 if (strncmp(fname, "/media/", 7) != 0) {
599 free(fname);
600 goto errexit;
601 }
602 }
603 else if (strncmp(new_name, "/mnt/", 5) == 0) {
604 entry->wldir = WLDIR_MNT;
605 mnt_dir = 1;
606 // both path and absolute path are under /mnt
607 if (strncmp(fname, "/mnt/", 5) != 0) {
608 free(fname);
609 goto errexit;
610 }
611 }
612 else if (strncmp(new_name, "/var/", 5) == 0) {
613 entry->wldir = WLDIR_VAR;
614 var_dir = 1;
615 // both path and absolute path are under /var
616 // exceptions: /var/tmp, /var/run and /var/lock
617 if (strcmp(new_name, "/var/run")== 0 && strcmp(fname, "/run") == 0);
618 else if (strcmp(new_name, "/var/lock")== 0 && strcmp(fname, "/run/lock") == 0);
619 else if (strcmp(new_name, "/var/tmp")== 0 && strcmp(fname, "/tmp") == 0);
620 else {
621 // both path and absolute path are under /var
622 if (strncmp(fname, "/var/", 5) != 0) {
623 free(fname);
624 goto errexit;
625 }
626 }
627 }
628 else if (strncmp(new_name, "/dev/", 5) == 0) {
629 entry->wldir = WLDIR_DEV;
630 dev_dir = 1;
631 // special handling for /dev/shm
632 // on some platforms (Debian wheezy, Ubuntu 14.04), it is a symlink to /run/shm
633 if (strcmp(new_name, "/dev/shm") == 0 && strcmp(fname, "/run/shm") == 0);
634 // special handling for /dev/log, which can be a symlink to /run/systemd/journal/dev-log
635 else if (strcmp(new_name, "/dev/log") == 0 && strcmp(fname, "/run/systemd/journal/dev-log") == 0);
636 // special processing for /proc/self/fd files
637 else if (strcmp(new_name, "/dev/fd") == 0 && strcmp(fname, "/proc/self/fd") == 0);
638 else if (strcmp(new_name, "/dev/stdin") == 0 && strcmp(fname, "/proc/self/fd/0") == 0);
639 else if (strcmp(new_name, "/dev/stdout") == 0 && strcmp(fname, "/proc/self/fd/1") == 0);
640 else if (strcmp(new_name, "/dev/stderr") == 0 && strcmp(fname, "/proc/self/fd/2") == 0);
641 else {
642 // both path and absolute path are under /dev
643 if (strncmp(fname, "/dev/", 5) != 0) {
644 free(fname);
645 goto errexit;
646 }
647 }
648 }
649 else if (strncmp(new_name, "/opt/", 5) == 0) {
650 entry->wldir = WLDIR_OPT;
651 opt_dir = 1;
652 // both path and absolute path are under /dev
653 if (strncmp(fname, "/opt/", 5) != 0) {
654 free(fname);
655 goto errexit;
656 }
657 }
658 else if (strncmp(new_name, "/srv/", 5) == 0) {
659 entry->wldir = WLDIR_SRV;
660 srv_dir = 1;
661 // both path and absolute path are under /srv
662 if (strncmp(fname, "/srv/", 5) != 0) {
663 free(fname);
664 goto errexit;
665 }
666 }
667 else if (strncmp(new_name, "/etc/", 5) == 0) {
668 entry->wldir = WLDIR_ETC;
669 etc_dir = 1;
670 // special handling for some of the symlinks
671 if (strcmp(new_name, "/etc/localtime") == 0);
672 else if (strcmp(new_name, "/etc/mtab") == 0);
673 else if (strcmp(new_name, "/etc/os-release") == 0);
674 // both path and absolute path are under /etc
675 else {
676 if (strncmp(fname, "/etc/", 5) != 0) {
677 free(fname);
678 goto errexit;
679 }
680 }
681 }
682 else if (strncmp(new_name, "/usr/share/", 11) == 0) {
683 entry->wldir = WLDIR_SHARE;
684 share_dir = 1;
685 // both path and absolute path are under /etc
686 if (strncmp(fname, "/usr/share/", 11) != 0) {
687 free(fname);
688 goto errexit;
689 }
690 }
691 else if (strncmp(new_name, "/sys/module/", 12) == 0) {
692 entry->wldir = WLDIR_MODULE;
693 module_dir = 1;
694 // both path and absolute path are under /sys/module
695 if (strncmp(fname, "/sys/module/", 12) != 0) {
696 free(fname);
697 goto errexit;
698 }
699 }
700 else if (strncmp(new_name, runuser, runuser_len) == 0 && new_name[runuser_len] == '/') {
701 entry->wldir = WLDIR_RUN;
702 run_dir = 1;
703 // both path and absolute path are under /run/user/$uid
704 if (strncmp(fname, runuser, runuser_len) != 0 || fname[runuser_len] != '/') {
705 free(fname);
706 goto errexit;
707 }
708 }
709 else { 684 else {
710 free(fname); 685 // check if the path is in nowhitelist array
711 goto errexit;
712 }
713
714 // check if the path is in nowhitelist array
715 if (nowhitelist_flag == 0) {
716 size_t i; 686 size_t i;
717 int found = 0; 687 int found = 0;
718 for (i = 0; i < nowhitelist_c; i++) { 688 for (i = 0; i < nowhitelist_c; i++) {
@@ -726,494 +696,79 @@ void fs_whitelist(void) {
726 if (found) { 696 if (found) {
727 if (arg_debug || arg_debug_whitelists) 697 if (arg_debug || arg_debug_whitelists)
728 printf("Skip nowhitelisted path %s\n", fname); 698 printf("Skip nowhitelisted path %s\n", fname);
729 entry->data = EMPTY_STRING;
730 entry = entry->next; 699 entry = entry->next;
731 free(fname);
732 free(new_name); 700 free(new_name);
701 free(fname);
733 continue; 702 continue;
734 } 703 }
735 } 704 }
736 705
737 // mark symbolic links 706 // attach whitelist parameters to profile entry
707 entry->wparam = calloc(1, sizeof(struct wparam_t));
708 if (!entry->wparam)
709 errExit("calloc");
710
711 assert(current_top);
712 entry->wparam->top = current_top;
713 entry->wparam->file = fname;
714
715 // mark link
738 if (is_link(new_name)) 716 if (is_link(new_name))
739 entry->link = new_name; 717 entry->wparam->link = new_name;
740 else { 718 else
741 free(new_name); 719 free(new_name);
742 entry->link = NULL;
743 }
744 720
745 // change file name in entry->data
746 if (strcmp(fname, entry->data + 10) != 0) {
747 char *newdata;
748 if (asprintf(&newdata, "whitelist %s", fname) == -1)
749 errExit("asprintf");
750 entry->data = newdata;
751 if (arg_debug || arg_debug_whitelists)
752 printf("Replaced whitelist path: %s\n", entry->data);
753 }
754 free(fname);
755 entry = entry->next; 721 entry = entry->next;
756 } 722 }
757 723
758 // release nowhitelist memory 724 // release nowhitelist memory
759 assert(nowhitelist);
760 free(nowhitelist); 725 free(nowhitelist);
761 726
727 // mount tmpfs on all top level directories
762 EUID_ROOT(); 728 EUID_ROOT();
763 // /tmp mountpoint 729 tmpfs_topdirs(topdirs);
764 if (tmp_dir) {
765 // check if /tmp directory exists
766 if (stat("/tmp", &s) == 0) {
767 // keep a copy of real /tmp directory in RUN_WHITELIST_TMP_DIR
768 mkdir_attr(RUN_WHITELIST_TMP_DIR, 1777, 0, 0);
769 if (mount("/tmp", RUN_WHITELIST_TMP_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
770 errExit("mount bind");
771
772 // mount tmpfs on /tmp
773 if (arg_debug || arg_debug_whitelists)
774 printf("Mounting tmpfs on /tmp directory\n");
775 if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=1777,gid=0") < 0)
776 errExit("mounting tmpfs on /tmp");
777 selinux_relabel_path("/tmp", "/tmp");
778 fs_logger("tmpfs /tmp");
779
780 // pam-tmpdir - issue #2685
781 const char *env = env_get("TMP");
782 if (env) {
783 char *pamtmpdir;
784 if (asprintf(&pamtmpdir, "/tmp/user/%u", getuid()) == -1)
785 errExit("asprintf");
786 if (strcmp(env, pamtmpdir) == 0) {
787 // create empty user-owned /tmp/user/$uid directory
788 mkdir_attr("/tmp/user", 0711, 0, 0);
789 selinux_relabel_path("/tmp/user", "/tmp/user");
790 fs_logger("mkdir /tmp/user");
791 mkdir_attr(pamtmpdir, 0700, getuid(), 0);
792 selinux_relabel_path(pamtmpdir, pamtmpdir);
793 fs_logger2("mkdir", pamtmpdir);
794 }
795 free(pamtmpdir);
796 }
797
798 // autowhitelist home directory if it is masked by the tmpfs
799 if (strncmp(cfg.homedir, "/tmp/", 5) == 0)
800 whitelist_home(WLDIR_TMP);
801 }
802 else
803 tmp_dir = 0;
804 }
805
806 // /media mountpoint
807 if (media_dir) {
808 // some distros don't have a /media directory
809 if (stat("/media", &s) == 0) {
810 // keep a copy of real /media directory in RUN_WHITELIST_MEDIA_DIR
811 mkdir_attr(RUN_WHITELIST_MEDIA_DIR, 0755, 0, 0);
812 if (mount("/media", RUN_WHITELIST_MEDIA_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
813 errExit("mount bind");
814
815 // mount tmpfs on /media
816 if (arg_debug || arg_debug_whitelists)
817 printf("Mounting tmpfs on /media directory\n");
818 if (mount("tmpfs", "/media", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
819 errExit("mounting tmpfs on /media");
820 selinux_relabel_path("/media", "/media");
821 fs_logger("tmpfs /media");
822
823 // autowhitelist home directory if it is masked by the tmpfs
824 if (strncmp(cfg.homedir, "/media/", 7) == 0)
825 whitelist_home(WLDIR_MEDIA);
826 }
827 else
828 media_dir = 0;
829 }
830
831 // /mnt mountpoint
832 if (mnt_dir) {
833 // check if /mnt directory exists
834 if (stat("/mnt", &s) == 0) {
835 // keep a copy of real /mnt directory in RUN_WHITELIST_MNT_DIR
836 mkdir_attr(RUN_WHITELIST_MNT_DIR, 0755, 0, 0);
837 if (mount("/mnt", RUN_WHITELIST_MNT_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
838 errExit("mount bind");
839
840 // mount tmpfs on /mnt
841 if (arg_debug || arg_debug_whitelists)
842 printf("Mounting tmpfs on /mnt directory\n");
843 if (mount("tmpfs", "/mnt", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
844 errExit("mounting tmpfs on /mnt");
845 selinux_relabel_path("/mnt", "/mnt");
846 fs_logger("tmpfs /mnt");
847
848 // autowhitelist home directory if it is masked by the tmpfs
849 if (strncmp(cfg.homedir, "/mnt/", 5) == 0)
850 whitelist_home(WLDIR_MNT);
851 }
852 else
853 mnt_dir = 0;
854 }
855
856 // /var mountpoint
857 if (var_dir) {
858 // check if /var directory exists
859 if (stat("/var", &s) == 0) {
860 // keep a copy of real /var directory in RUN_WHITELIST_VAR_DIR
861 mkdir_attr(RUN_WHITELIST_VAR_DIR, 0755, 0, 0);
862 if (mount("/var", RUN_WHITELIST_VAR_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
863 errExit("mount bind");
864
865 // mount tmpfs on /var
866 if (arg_debug || arg_debug_whitelists)
867 printf("Mounting tmpfs on /var directory\n");
868 if (mount("tmpfs", "/var", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
869 errExit("mounting tmpfs on /var");
870 selinux_relabel_path("/var", "/var");
871 fs_logger("tmpfs /var");
872
873 // autowhitelist home directory if it is masked by the tmpfs
874 if (strncmp(cfg.homedir, "/var/", 5) == 0)
875 whitelist_home(WLDIR_VAR);
876 }
877 else
878 var_dir = 0;
879 }
880
881 // /dev mountpoint
882 if (dev_dir) {
883 // check if /dev directory exists
884 if (stat("/dev", &s) == 0) {
885 // keep a copy of real /dev directory in RUN_WHITELIST_DEV_DIR
886 mkdir_attr(RUN_WHITELIST_DEV_DIR, 0755, 0, 0);
887 if (mount("/dev", RUN_WHITELIST_DEV_DIR, NULL, MS_BIND|MS_REC, "mode=755,gid=0") < 0)
888 errExit("mount bind");
889
890 // mount tmpfs on /dev
891 if (arg_debug || arg_debug_whitelists)
892 printf("Mounting tmpfs on /dev directory\n");
893 if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
894 errExit("mounting tmpfs on /dev");
895 selinux_relabel_path("/dev", "/dev");
896 fs_logger("tmpfs /dev");
897
898 // autowhitelist home directory if it is masked by the tmpfs
899 if (strncmp(cfg.homedir, "/dev/", 5) == 0)
900 whitelist_home(WLDIR_DEV);
901 }
902 else
903 dev_dir = 0;
904 }
905
906 // /opt mountpoint
907 if (opt_dir) {
908 // check if /opt directory exists
909 if (stat("/opt", &s) == 0) {
910 // keep a copy of real /opt directory in RUN_WHITELIST_OPT_DIR
911 mkdir_attr(RUN_WHITELIST_OPT_DIR, 0755, 0, 0);
912 if (mount("/opt", RUN_WHITELIST_OPT_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
913 errExit("mount bind");
914
915 // mount tmpfs on /opt
916 if (arg_debug || arg_debug_whitelists)
917 printf("Mounting tmpfs on /opt directory\n");
918 if (mount("tmpfs", "/opt", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
919 errExit("mounting tmpfs on /opt");
920 selinux_relabel_path("/opt", "/opt");
921 fs_logger("tmpfs /opt");
922
923 // autowhitelist home directory if it is masked by the tmpfs
924 if (strncmp(cfg.homedir, "/opt/", 5) == 0)
925 whitelist_home(WLDIR_OPT);
926 }
927 else
928 opt_dir = 0;
929 }
930
931 // /srv mountpoint
932 if (srv_dir) {
933 // check if /srv directory exists
934 if (stat("/srv", &s) == 0) {
935 // keep a copy of real /srv directory in RUN_WHITELIST_SRV_DIR
936 mkdir_attr(RUN_WHITELIST_SRV_DIR, 0755, 0, 0);
937 if (mount("/srv", RUN_WHITELIST_SRV_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
938 errExit("mount bind");
939
940 // mount tmpfs on /srv
941 if (arg_debug || arg_debug_whitelists)
942 printf("Mounting tmpfs on /srv directory\n");
943 if (mount("tmpfs", "/srv", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
944 errExit("mounting tmpfs on /srv");
945 selinux_relabel_path("/srv", "/srv");
946 fs_logger("tmpfs /srv");
947
948 // autowhitelist home directory if it is masked by the tmpfs
949 if (strncmp(cfg.homedir, "/srv/", 5) == 0)
950 whitelist_home(WLDIR_SRV);
951 }
952 else
953 srv_dir = 0;
954 }
955
956 // /etc mountpoint
957 if (etc_dir) {
958 // check if /etc directory exists
959 if (stat("/etc", &s) == 0) {
960 // keep a copy of real /etc directory in RUN_WHITELIST_ETC_DIR
961 mkdir_attr(RUN_WHITELIST_ETC_DIR, 0755, 0, 0);
962 if (mount("/etc", RUN_WHITELIST_ETC_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
963 errExit("mount bind");
964
965 // mount tmpfs on /etc
966 if (arg_debug || arg_debug_whitelists)
967 printf("Mounting tmpfs on /etc directory\n");
968 if (mount("tmpfs", "/etc", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
969 errExit("mounting tmpfs on /etc");
970 selinux_relabel_path("/etc", "/etc");
971 fs_logger("tmpfs /etc");
972
973 // autowhitelist home directory if it is masked by the tmpfs
974 if (strncmp(cfg.homedir, "/etc/", 5) == 0)
975 whitelist_home(WLDIR_ETC);
976 }
977 else
978 etc_dir = 0;
979 }
980
981 // /usr/share mountpoint
982 if (share_dir) {
983 // check if /usr/share directory exists
984 if (stat("/usr/share", &s) == 0) {
985 // keep a copy of real /usr/share directory in RUN_WHITELIST_ETC_DIR
986 mkdir_attr(RUN_WHITELIST_SHARE_DIR, 0755, 0, 0);
987 if (mount("/usr/share", RUN_WHITELIST_SHARE_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
988 errExit("mount bind");
989
990 // mount tmpfs on /srv
991 if (arg_debug || arg_debug_whitelists)
992 printf("Mounting tmpfs on /usr/share directory\n");
993 if (mount("tmpfs", "/usr/share", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
994 errExit("mounting tmpfs on /usr/share");
995 selinux_relabel_path("/usr/share", "/usr/share");
996 fs_logger("tmpfs /usr/share");
997
998 // autowhitelist home directory if it is masked by the tmpfs
999 if (strncmp(cfg.homedir, "/usr/share/", 11) == 0)
1000 whitelist_home(WLDIR_SHARE);
1001 }
1002 else
1003 share_dir = 0;
1004 }
1005
1006 // /sys/module mountpoint
1007 if (module_dir) {
1008 // check if /sys/module directory exists
1009 if (stat("/sys/module", &s) == 0) {
1010 // keep a copy of real /sys/module directory in RUN_WHITELIST_MODULE_DIR
1011 mkdir_attr(RUN_WHITELIST_MODULE_DIR, 0755, 0, 0);
1012 if (mount("/sys/module", RUN_WHITELIST_MODULE_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
1013 errExit("mount bind");
1014
1015 // mount tmpfs on /sys/module
1016 if (arg_debug || arg_debug_whitelists)
1017 printf("Mounting tmpfs on /sys/module directory\n");
1018 if (mount("tmpfs", "/sys/module", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1019 errExit("mounting tmpfs on /sys/module");
1020 selinux_relabel_path("/sys/module", "/sys/module");
1021 fs_logger("tmpfs /sys/module");
1022 }
1023 else
1024 module_dir = 0;
1025 }
1026
1027 // /run/user/$uid mountpoint
1028 if (run_dir) {
1029 // check if /run/user/$uid directory exists
1030 if (stat(runuser, &s) == 0) {
1031 // keep a copy of real /run/user/$uid directory in RUN_WHITELIST_RUN_USER_DIR
1032 mkdir_attr(RUN_WHITELIST_RUN_USER_DIR, 0700, getuid(), getgid());
1033 if (mount(runuser, RUN_WHITELIST_RUN_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
1034 errExit("mount bind");
1035
1036 // mount tmpfs on /run/user/$uid
1037 if (arg_debug || arg_debug_whitelists)
1038 printf("Mounting tmpfs on %s directory\n", runuser);
1039 char *options;
1040 if (asprintf(&options, "mode=700,uid=%u,gid=%u", getuid(), getgid()) == -1)
1041 errExit("asprintf");
1042 if (mount("tmpfs", runuser, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME, options) < 0)
1043 errExit("mounting tmpfs on /run/user/<uid>");
1044 selinux_relabel_path(runuser, runuser);
1045 free(options);
1046 fs_logger2("tmpfs", runuser);
1047
1048 // autowhitelist home directory if it is masked by the tmpfs
1049 if (strncmp(cfg.homedir, runuser, runuser_len) == 0 && cfg.homedir[runuser_len] == '/')
1050 whitelist_home(WLDIR_RUN);
1051 }
1052 else
1053 run_dir = 0;
1054 }
1055
1056 // home mountpoint
1057 if (home_dir) {
1058 // check if home directory exists
1059 if (stat(cfg.homedir, &s) == 0) {
1060 // keep a copy of real home dir in RUN_WHITELIST_HOME_USER_DIR
1061 mkdir_attr(RUN_WHITELIST_HOME_USER_DIR, 0755, getuid(), getgid());
1062 int fd = safe_fd(cfg.homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
1063 if (fd == -1)
1064 errExit("safe_fd");
1065 char *proc;
1066 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
1067 errExit("asprintf");
1068 if (mount(proc, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
1069 errExit("mount bind");
1070 free(proc);
1071 close(fd);
1072
1073 // mount a tmpfs and initialize home directory
1074 fs_private();
1075 }
1076 else
1077 home_dir = 0;
1078 }
1079 730
1080 // go through profile rules again, and interpret whitelist commands 731 // go through profile rules again, and interpret whitelist commands
1081 entry = cfg.profile; 732 entry = cfg.profile;
1082 while (entry) { 733 while (entry) {
1083 // handle only whitelist commands 734 if (entry->wparam) {
1084 if (strncmp(entry->data, "whitelist ", 10)) { 735 char *file = entry->wparam->file;
1085 entry = entry->next; 736 char *link = entry->wparam->link;
1086 continue; 737 const char *topdir = entry->wparam->top->path;
1087 } 738 size_t topdir_len = strlen(topdir);
739 int dirfd = entry->wparam->top->fd;
740
741 // top level directories of link and file can differ
742 // whitelist the file only if it is in same top level directory
743 if (strncmp(file, topdir, topdir_len) == 0 && file[topdir_len] == '/') {
744 // get path relative to top level directory
745 const char *rel = file + topdir_len + 1;
1088 746
1089//printf("here %d#%s#\n", __LINE__, entry->data); 747 if (arg_debug || arg_debug_whitelists)
1090 // whitelist the real file 748 printf("Debug %d: file: %s; dirfd: %d; topdir: %s; rel: %s\n", __LINE__, file, dirfd, topdir, rel);
1091 whitelist_path(entry); 749 whitelist_file(dirfd, rel, file);
1092
1093 // create the link if any
1094 if (entry->link) {
1095 // if the link is already there, do not bother
1096 if (lstat(entry->link, &s) != 0) {
1097 // create the path if necessary
1098 // entry->link has no trailing slashes or single dots
1099 int fd = mkpath(entry->link, 0755);
1100 if (fd == -1) {
1101 if (arg_debug || arg_debug_whitelists)
1102 printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link);
1103 free(entry->link);
1104 entry->link = NULL;
1105 entry = entry->next;
1106 continue;
1107 }
1108 // get file name of symlink
1109 const char *file = gnu_basename(entry->link);
1110 // create the link
1111 int rv = symlinkat(entry->data + 10, fd, file);
1112 if (rv) {
1113 if (arg_debug || arg_debug_whitelists) {
1114 perror("symlink");
1115 printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link);
1116 }
1117 }
1118 else if (arg_debug || arg_debug_whitelists)
1119 printf("Created symbolic link %s -> %s\n", entry->link, entry->data + 10);
1120 close(fd);
1121 } 750 }
1122 free(entry->link);
1123 entry->link = NULL;
1124 }
1125 751
1126 entry = entry->next; 752 // create the link if any
1127 } 753 if (link)
754 whitelist_symlink(link, file);
1128 755
1129 // mask the real home directory, currently mounted on RUN_WHITELIST_HOME_DIR 756 free(link);
1130 if (home_dir) { 757 free(file);
1131 if (mount("tmpfs", RUN_WHITELIST_HOME_USER_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0) 758 free(entry->wparam);
1132 errExit("mount tmpfs"); 759 entry->wparam = NULL;
1133 fs_logger2("tmpfs", RUN_WHITELIST_HOME_USER_DIR); 760 }
1134 }
1135
1136 // mask the real /tmp directory, currently mounted on RUN_WHITELIST_TMP_DIR
1137 if (tmp_dir) {
1138 if (mount("tmpfs", RUN_WHITELIST_TMP_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1139 errExit("mount tmpfs");
1140 fs_logger2("tmpfs", RUN_WHITELIST_TMP_DIR);
1141 }
1142
1143 // mask the real /var directory, currently mounted on RUN_WHITELIST_VAR_DIR
1144 if (var_dir) {
1145 if (mount("tmpfs", RUN_WHITELIST_VAR_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1146 errExit("mount tmpfs");
1147 fs_logger2("tmpfs", RUN_WHITELIST_VAR_DIR);
1148 }
1149
1150 // mask the real /opt directory, currently mounted on RUN_WHITELIST_OPT_DIR
1151 if (opt_dir) {
1152 if (mount("tmpfs", RUN_WHITELIST_OPT_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1153 errExit("mount tmpfs");
1154 fs_logger2("tmpfs", RUN_WHITELIST_OPT_DIR);
1155 }
1156
1157 // mask the real /dev directory, currently mounted on RUN_WHITELIST_DEV_DIR
1158 if (dev_dir) {
1159 if (mount("tmpfs", RUN_WHITELIST_DEV_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1160 errExit("mount tmpfs");
1161 fs_logger2("tmpfs", RUN_WHITELIST_DEV_DIR);
1162 }
1163
1164 // mask the real /media directory, currently mounted on RUN_WHITELIST_MEDIA_DIR
1165 if (media_dir) {
1166 if (mount("tmpfs", RUN_WHITELIST_MEDIA_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1167 errExit("mount tmpfs");
1168 fs_logger2("tmpfs", RUN_WHITELIST_MEDIA_DIR);
1169 }
1170
1171 // mask the real /mnt directory, currently mounted on RUN_WHITELIST_MNT_DIR
1172 if (mnt_dir) {
1173 if (mount("tmpfs", RUN_WHITELIST_MNT_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1174 errExit("mount tmpfs");
1175 fs_logger2("tmpfs", RUN_WHITELIST_MNT_DIR);
1176 }
1177
1178 // mask the real /srv directory, currently mounted on RUN_WHITELIST_SRV_DIR
1179 if (srv_dir) {
1180 if (mount("tmpfs", RUN_WHITELIST_SRV_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1181 errExit("mount tmpfs");
1182 fs_logger2("tmpfs", RUN_WHITELIST_SRV_DIR);
1183 }
1184
1185 // mask the real /etc directory, currently mounted on RUN_WHITELIST_ETC_DIR
1186 if (etc_dir) {
1187 if (mount("tmpfs", RUN_WHITELIST_ETC_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1188 errExit("mount tmpfs");
1189 fs_logger2("tmpfs", RUN_WHITELIST_ETC_DIR);
1190 }
1191
1192 // mask the real /usr/share directory, currently mounted on RUN_WHITELIST_SHARE_DIR
1193 if (share_dir) {
1194 if (mount("tmpfs", RUN_WHITELIST_SHARE_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1195 errExit("mount tmpfs");
1196 fs_logger2("tmpfs", RUN_WHITELIST_SHARE_DIR);
1197 }
1198
1199 // mask the real /sys/module directory, currently mounted on RUN_WHITELIST_MODULE_DIR
1200 if (module_dir) {
1201 if (mount("tmpfs", RUN_WHITELIST_MODULE_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1202 errExit("mount tmpfs");
1203 fs_logger2("tmpfs", RUN_WHITELIST_MODULE_DIR);
1204 }
1205 761
1206 // mask the real /run/user/$uid directory, currently mounted on RUN_WHITELIST_RUN_USER_DIR 762 entry = entry->next;
1207 if (run_dir) {
1208 if (mount("tmpfs", RUN_WHITELIST_RUN_USER_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1209 errExit("mount tmpfs");
1210 fs_logger2("tmpfs", RUN_WHITELIST_RUN_USER_DIR);
1211 } 763 }
1212 764
765 // release resources
1213 free(runuser); 766 free(runuser);
1214 return;
1215 767
1216errexit: 768 size_t i;
1217 fprintf(stderr, "Error: invalid whitelist path %s\n", new_name); 769 for (i = 0; i < TOP_MAX && topdirs[i].path; i++) {
1218 exit(1); 770 free(topdirs[i].path);
771 close(topdirs[i].fd);
772 }
773 free(topdirs);
1219} 774}
diff --git a/src/firejail/main.c b/src/firejail/main.c
index f011c5799..bbabe533f 100644
--- a/src/firejail/main.c
+++ b/src/firejail/main.c
@@ -982,6 +982,14 @@ int main(int argc, char **argv, char **envp) {
982 int arg_caps_cmdline = 0; // caps requested on command line (used to break out of --chroot) 982 int arg_caps_cmdline = 0; // caps requested on command line (used to break out of --chroot)
983 char **ptr; 983 char **ptr;
984 984
985#ifndef HAVE_SUID
986 if (geteuid() != 0) {
987 fprintf(stderr, "Error: Firejail needs to be SUID.\n");
988 fprintf(stderr, "Assuming firejail is installed in /usr/bin, execute the following command as root:\n");
989 fprintf(stderr, " chmod u+s /usr/bin/firejail\n");
990 }
991#endif
992
985 // sanitize the umask 993 // sanitize the umask
986 orig_umask = umask(022); 994 orig_umask = umask(022);
987 995
@@ -1248,8 +1256,10 @@ int main(int argc, char **argv, char **envp) {
1248 for (i = 1; i < argc; i++) { 1256 for (i = 1; i < argc; i++) {
1249 run_cmd_and_exit(i, argc, argv); // will exit if the command is recognized 1257 run_cmd_and_exit(i, argc, argv); // will exit if the command is recognized
1250 1258
1251 if (strcmp(argv[i], "--debug") == 0 && !arg_quiet) 1259 if (strcmp(argv[i], "--debug") == 0) {
1252 arg_debug = 1; 1260 arg_debug = 1;
1261 arg_quiet = 0;
1262 }
1253 else if (strcmp(argv[i], "--debug-blacklists") == 0) 1263 else if (strcmp(argv[i], "--debug-blacklists") == 0)
1254 arg_debug_blacklists = 1; 1264 arg_debug_blacklists = 1;
1255 else if (strcmp(argv[i], "--debug-whitelists") == 0) 1265 else if (strcmp(argv[i], "--debug-whitelists") == 0)
@@ -1257,8 +1267,8 @@ int main(int argc, char **argv, char **envp) {
1257 else if (strcmp(argv[i], "--debug-private-lib") == 0) 1267 else if (strcmp(argv[i], "--debug-private-lib") == 0)
1258 arg_debug_private_lib = 1; 1268 arg_debug_private_lib = 1;
1259 else if (strcmp(argv[i], "--quiet") == 0) { 1269 else if (strcmp(argv[i], "--quiet") == 0) {
1260 arg_quiet = 1; 1270 if (!arg_debug)
1261 arg_debug = 0; 1271 arg_quiet = 1;
1262 } 1272 }
1263 else if (strcmp(argv[i], "--allow-debuggers") == 0) { 1273 else if (strcmp(argv[i], "--allow-debuggers") == 0) {
1264 // already handled 1274 // already handled
diff --git a/src/firejail/profile.c b/src/firejail/profile.c
index da28f0413..40e4f788e 100644
--- a/src/firejail/profile.c
+++ b/src/firejail/profile.c
@@ -1727,13 +1727,17 @@ void profile_read(const char *fname) {
1727 int lineno = 0; 1727 int lineno = 0;
1728 while (fgets(buf, MAX_READ, fp)) { 1728 while (fgets(buf, MAX_READ, fp)) {
1729 ++lineno; 1729 ++lineno;
1730
1731 // remove comments
1732 char *ptr = strchr(buf, '#');
1733 if (ptr)
1734 *ptr = '\0';
1735
1730 // remove empty space - ptr in allocated memory 1736 // remove empty space - ptr in allocated memory
1731 char *ptr = line_remove_spaces(buf); 1737 ptr = line_remove_spaces(buf);
1732 if (ptr == NULL) 1738 if (ptr == NULL)
1733 continue; 1739 continue;
1734 1740 if (*ptr == '\0') {
1735 // comments
1736 if (*ptr == '#' || *ptr == '\0') {
1737 free(ptr); 1741 free(ptr);
1738 continue; 1742 continue;
1739 } 1743 }
@@ -1743,7 +1747,7 @@ void profile_read(const char *fname) {
1743 if (strcmp(ptr, "quiet") == 0) { 1747 if (strcmp(ptr, "quiet") == 0) {
1744 if (is_in_ignore_list(ptr)) 1748 if (is_in_ignore_list(ptr))
1745 arg_quiet = 0; 1749 arg_quiet = 0;
1746 else 1750 else if (!arg_debug)
1747 arg_quiet = 1; 1751 arg_quiet = 1;
1748 free(ptr); 1752 free(ptr);
1749 continue; 1753 continue;
diff --git a/src/firejail/pulseaudio.c b/src/firejail/pulseaudio.c
index 97c022bad..1b01a71c6 100644
--- a/src/firejail/pulseaudio.c
+++ b/src/firejail/pulseaudio.c
@@ -131,7 +131,7 @@ void pulseaudio_init(void) {
131 131
132 // if ~/.config/pulse exists and there are no symbolic links, mount the new directory 132 // if ~/.config/pulse exists and there are no symbolic links, mount the new directory
133 // else set environment variable 133 // else set environment variable
134 int fd = safe_fd(homeusercfg, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 134 int fd = safer_openat(-1, homeusercfg, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
135 if (fd == -1) { 135 if (fd == -1) {
136 pulseaudio_fallback(pulsecfg); 136 pulseaudio_fallback(pulsecfg);
137 goto out; 137 goto out;
diff --git a/src/firejail/restrict_users.c b/src/firejail/restrict_users.c
index e173c9888..53e395b89 100644
--- a/src/firejail/restrict_users.c
+++ b/src/firejail/restrict_users.c
@@ -73,7 +73,7 @@ static void sanitize_home(void) {
73 if (arg_debug) 73 if (arg_debug)
74 printf("Cleaning /home directory\n"); 74 printf("Cleaning /home directory\n");
75 // open user home directory in order to keep it around 75 // open user home directory in order to keep it around
76 int fd = safe_fd(cfg.homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 76 int fd = safer_openat(-1, cfg.homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
77 if (fd == -1) 77 if (fd == -1)
78 goto errout; 78 goto errout;
79 if (fstat(fd, &s) == -1) { // FUSE 79 if (fstat(fd, &s) == -1) { // FUSE
diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c
index 08f0f32c9..a6bcec02c 100644
--- a/src/firejail/sandbox.c
+++ b/src/firejail/sandbox.c
@@ -227,7 +227,7 @@ static void sandbox_if_up(Bridge *br) {
227 if (br->arg_ip_none == 1); // do nothing 227 if (br->arg_ip_none == 1); // do nothing
228 else if (br->arg_ip_none == 0 && br->macvlan == 0) { 228 else if (br->arg_ip_none == 0 && br->macvlan == 0) {
229 if (br->ipsandbox == br->ip) { 229 if (br->ipsandbox == br->ip) {
230 fprintf(stderr, "Error: %d.%d.%d.%d is interface %s address.\n", PRINT_IP(br->ipsandbox), br->dev); 230 fprintf(stderr, "Error: %d.%d.%d.%d is interface %s address, exiting...\n", PRINT_IP(br->ipsandbox), br->dev);
231 exit(1); 231 exit(1);
232 } 232 }
233 233
@@ -245,13 +245,17 @@ static void sandbox_if_up(Bridge *br) {
245 br->ipsandbox = arp_assign(dev, br); //br->ip, br->mask); 245 br->ipsandbox = arp_assign(dev, br); //br->ip, br->mask);
246 else { 246 else {
247 if (br->ipsandbox == br->ip) { 247 if (br->ipsandbox == br->ip) {
248 fprintf(stderr, "Error: %d.%d.%d.%d is interface %s address.\n", PRINT_IP(br->ipsandbox), br->dev); 248 fprintf(stderr, "Error: %d.%d.%d.%d is interface %s address, exiting...\n", PRINT_IP(br->ipsandbox), br->dev);
249 exit(1);
250 }
251 if (br->ipsandbox == cfg.defaultgw) {
252 fprintf(stderr, "Error: %d.%d.%d.%d is the default gateway, exiting...\n", PRINT_IP(br->ipsandbox));
249 exit(1); 253 exit(1);
250 } 254 }
251 255
252 uint32_t rv = arp_check(dev, br->ipsandbox); 256 uint32_t rv = arp_check(dev, br->ipsandbox);
253 if (rv) { 257 if (rv) {
254 fprintf(stderr, "Error: the address %d.%d.%d.%d is already in use.\n", PRINT_IP(br->ipsandbox)); 258 fprintf(stderr, "Error: the address %d.%d.%d.%d is already in use, exiting...\n", PRINT_IP(br->ipsandbox));
255 exit(1); 259 exit(1);
256 } 260 }
257 } 261 }
diff --git a/src/firejail/sbox.c b/src/firejail/sbox.c
index 4a8dd1bf7..d2c0bcc19 100644
--- a/src/firejail/sbox.c
+++ b/src/firejail/sbox.c
@@ -293,7 +293,7 @@ int sbox_run_v(unsigned filtermask, char * const arg[]) {
293 errExit("waitpid"); 293 errExit("waitpid");
294 } 294 }
295 if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { 295 if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
296 fprintf(stderr, "Error: failed to run %s\n", arg[0]); 296 fprintf(stderr, "Error: failed to run %s, exiting...\n", arg[0]);
297 exit(1); 297 exit(1);
298 } 298 }
299 299
diff --git a/src/firejail/shutdown.c b/src/firejail/shutdown.c
index fbfe1765b..d1be6eed4 100644
--- a/src/firejail/shutdown.c
+++ b/src/firejail/shutdown.c
@@ -36,8 +36,10 @@ void shut(pid_t pid) {
36 } 36 }
37 free(comm); 37 free(comm);
38 } 38 }
39 else 39 else {
40 errExit("/proc/PID/comm"); 40 fprintf(stderr, "Error: cannot find process %d\n", pid);
41 exit(1);
42 }
41 43
42 // check privileges for non-root users 44 // check privileges for non-root users
43 uid_t uid = getuid(); 45 uid_t uid = getuid();
diff --git a/src/firejail/util.c b/src/firejail/util.c
index 2c985c0d6..6a7318c4b 100644
--- a/src/firejail/util.c
+++ b/src/firejail/util.c
@@ -544,11 +544,13 @@ char *split_comma(char *str) {
544} 544}
545 545
546 546
547// remove consecutive and trailing slashes 547// simplify absolute path by removing
548// and return allocated memory 548// 1) consecutive and trailing slashes, and
549// e.g. /home//user/ -> /home/user 549// 2) segments with a single dot
550// for example /foo//./bar/ -> /foo/bar
550char *clean_pathname(const char *path) { 551char *clean_pathname(const char *path) {
551 assert(path); 552 assert(path && path[0] == '/');
553
552 size_t len = strlen(path); 554 size_t len = strlen(path);
553 char *rv = malloc(len + 1); 555 char *rv = malloc(len + 1);
554 if (!rv) 556 if (!rv)
@@ -557,15 +559,23 @@ char *clean_pathname(const char *path) {
557 size_t i = 0; 559 size_t i = 0;
558 size_t j = 0; 560 size_t j = 0;
559 while (path[i]) { 561 while (path[i]) {
560 while (path[i] == '/' && path[i+1] == '/') 562 if (path[i] == '/') {
561 i++; 563 while (path[i+1] == '/' ||
564 (path[i+1] == '.' && path[i+2] == '/'))
565 i++;
566 }
567
562 rv[j++] = path[i++]; 568 rv[j++] = path[i++];
563 } 569 }
564 rv[j] = '\0'; 570 rv[j] = '\0';
565 571
572 // remove a trailing dot
573 if (j > 1 && rv[j - 1] == '.' && rv[j - 2] == '/')
574 rv[--j] = '\0';
575
566 // remove a trailing slash 576 // remove a trailing slash
567 if (j > 1 && rv[j - 1] == '/') 577 if (j > 1 && rv[j - 1] == '/')
568 rv[j - 1] = '\0'; 578 rv[--j] = '\0';
569 579
570 return rv; 580 return rv;
571} 581}
@@ -637,9 +647,11 @@ int find_child(pid_t parent, pid_t *child) {
637 if (parent == atoi(ptr)) { 647 if (parent == atoi(ptr)) {
638 // we don't want /usr/bin/xdg-dbus-proxy! 648 // we don't want /usr/bin/xdg-dbus-proxy!
639 char *cmdline = pid_proc_cmdline(pid); 649 char *cmdline = pid_proc_cmdline(pid);
640 if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0) 650 if (cmdline) {
641 *child = pid; 651 if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0)
642 free(cmdline); 652 *child = pid;
653 free(cmdline);
654 }
643 } 655 }
644 break; // stop reading the file 656 break; // stop reading the file
645 } 657 }
@@ -905,9 +917,9 @@ int remove_overlay_directory(void) {
905 errExit("fork"); 917 errExit("fork");
906 if (child == 0) { 918 if (child == 0) {
907 // open ~/.firejail, fails if there is any symlink 919 // open ~/.firejail, fails if there is any symlink
908 int fd = safe_fd(path, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 920 int fd = safer_openat(-1, path, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
909 if (fd == -1) 921 if (fd == -1)
910 errExit("safe_fd"); 922 errExit("safer_openat");
911 // chdir to ~/.firejail 923 // chdir to ~/.firejail
912 if (fchdir(fd) == -1) 924 if (fchdir(fd) == -1)
913 errExit("fchdir"); 925 errExit("fchdir");
@@ -1020,8 +1032,10 @@ void create_empty_file_as_root(const char *fname, mode_t mode) {
1020 if (stat(fname, &s)) { 1032 if (stat(fname, &s)) {
1021 if (arg_debug) 1033 if (arg_debug)
1022 printf("Creating empty %s file\n", fname); 1034 printf("Creating empty %s file\n", fname);
1023 1035 /* coverity[toctou] */
1024 FILE *fp = fopen(fname, "wxe"); 1036 // don't fail if file already exists. This can be the case in a race
1037 // condition, when two jails launch at the same time. Compare to #1013
1038 FILE *fp = fopen(fname, "we");
1025 if (!fp) 1039 if (!fp)
1026 errExit("fopen"); 1040 errExit("fopen");
1027 SET_PERMS_STREAM(fp, 0, 0, mode); 1041 SET_PERMS_STREAM(fp, 0, 0, mode);
@@ -1125,13 +1139,13 @@ void disable_file_path(const char *path, const char *file) {
1125} 1139}
1126 1140
1127// open an existing file without following any symbolic link 1141// open an existing file without following any symbolic link
1128int safe_fd(const char *path, int flags) { 1142// relative paths are interpreted relative to dirfd
1143// ignore dirfd if path is absolute
1144// https://web.archive.org/web/20180419120236/https://blogs.gnome.org/jamesh/2018/04/19/secure-mounts
1145int safer_openat(int dirfd, const char *path, int flags) {
1146 assert(path && path[0]);
1129 flags |= O_NOFOLLOW; 1147 flags |= O_NOFOLLOW;
1130 assert(path); 1148
1131 if (*path != '/' || strstr(path, "..")) {
1132 fprintf(stderr, "Error: invalid path %s\n", path);
1133 exit(1);
1134 }
1135 int fd = -1; 1149 int fd = -1;
1136 1150
1137#ifdef __NR_openat2 // kernel 5.6 or better 1151#ifdef __NR_openat2 // kernel 5.6 or better
@@ -1139,7 +1153,7 @@ int safe_fd(const char *path, int flags) {
1139 memset(&oh, 0, sizeof(oh)); 1153 memset(&oh, 0, sizeof(oh));
1140 oh.flags = flags; 1154 oh.flags = flags;
1141 oh.resolve = RESOLVE_NO_SYMLINKS; 1155 oh.resolve = RESOLVE_NO_SYMLINKS;
1142 fd = syscall(__NR_openat2, -1, path, &oh, sizeof(struct open_how)); 1156 fd = syscall(__NR_openat2, dirfd, path, &oh, sizeof(struct open_how));
1143 if (fd != -1 || errno != ENOSYS) 1157 if (fd != -1 || errno != ENOSYS)
1144 return fd; 1158 return fd;
1145#endif 1159#endif
@@ -1150,18 +1164,23 @@ int safe_fd(const char *path, int flags) {
1150 if (!dup) 1164 if (!dup)
1151 errExit("strdup"); 1165 errExit("strdup");
1152 char *tok = strtok(dup, "/"); 1166 char *tok = strtok(dup, "/");
1153 if (!tok) { // root directory 1167 if (!tok) { // nothing to do, path is the root directory
1154 free(dup); 1168 free(dup);
1155 return open("/", flags); 1169 return openat(dirfd, path, flags);
1156 } 1170 }
1157 char *last_tok = EMPTY_STRING; 1171 char *last_tok = EMPTY_STRING;
1158 int parentfd = open("/", O_PATH|O_CLOEXEC); 1172
1173 int parentfd;
1174 if (path[0] == '/')
1175 parentfd = open("/", O_PATH|O_CLOEXEC);
1176 else
1177 parentfd = fcntl(dirfd, F_DUPFD_CLOEXEC, 0);
1159 if (parentfd == -1) 1178 if (parentfd == -1)
1160 errExit("open"); 1179 errExit("open/fcntl");
1161 1180
1162 while(1) { 1181 while (1) {
1163 // open path component, assuming it is a directory; this fails with ENOTDIR if it is a symbolic link 1182 // open path component, assuming it is a directory; this fails with ENOTDIR if it is a symbolic link
1164 // if token is a single dot, the previous directory is reopened 1183 // if token is a single dot, the directory referred to by parentfd is reopened
1165 fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 1184 fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
1166 if (fd == -1) { 1185 if (fd == -1) {
1167 // if the following token is NULL, the current token is the final path component 1186 // if the following token is NULL, the current token is the final path component
@@ -1292,13 +1311,11 @@ pid_t require_pid(const char *name) {
1292// return 1 if there is a link somewhere in path of directory 1311// return 1 if there is a link somewhere in path of directory
1293static int has_link(const char *dir) { 1312static int has_link(const char *dir) {
1294 assert(dir); 1313 assert(dir);
1295 int fd = safe_fd(dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 1314 int fd = safer_openat(-1, dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
1296 if (fd == -1) { 1315 if (fd != -1)
1297 if ((errno == ELOOP || errno == ENOTDIR) && is_dir(dir))
1298 return 1;
1299 }
1300 else
1301 close(fd); 1316 close(fd);
1317 else if (errno == ELOOP || (errno == ENOTDIR && is_dir(dir)))
1318 return 1;
1302 return 0; 1319 return 0;
1303} 1320}
1304 1321
diff --git a/src/firejail/x11.c b/src/firejail/x11.c
index c0587ffc1..f4f093138 100644
--- a/src/firejail/x11.c
+++ b/src/firejail/x11.c
@@ -1239,9 +1239,9 @@ void x11_xorg(void) {
1239 } 1239 }
1240 } 1240 }
1241 // get a file descriptor for ~/.Xauthority 1241 // get a file descriptor for ~/.Xauthority
1242 int dst = safe_fd(dest, O_PATH|O_NOFOLLOW|O_CLOEXEC); 1242 int dst = safer_openat(-1, dest, O_PATH|O_NOFOLLOW|O_CLOEXEC);
1243 if (dst == -1) 1243 if (dst == -1)
1244 errExit("safe_fd"); 1244 errExit("safer_openat");
1245 // check if the actual mount destination is a user owned regular file 1245 // check if the actual mount destination is a user owned regular file
1246 if (fstat(dst, &s) == -1) 1246 if (fstat(dst, &s) == -1)
1247 errExit("fstat"); 1247 errExit("fstat");
@@ -1263,9 +1263,9 @@ void x11_xorg(void) {
1263 fs_remount(RUN_XAUTHORITY_SEC_DIR, MOUNT_NOEXEC, 0); 1263 fs_remount(RUN_XAUTHORITY_SEC_DIR, MOUNT_NOEXEC, 0);
1264 1264
1265 // get a file descriptor for the new Xauthority file 1265 // get a file descriptor for the new Xauthority file
1266 int src = safe_fd(tmpfname, O_PATH|O_NOFOLLOW|O_CLOEXEC); 1266 int src = safer_openat(-1, tmpfname, O_PATH|O_NOFOLLOW|O_CLOEXEC);
1267 if (src == -1) 1267 if (src == -1)
1268 errExit("safe_fd"); 1268 errExit("safer_openat");
1269 if (fstat(src, &s) == -1) 1269 if (fstat(src, &s) == -1)
1270 errExit("fstat"); 1270 errExit("fstat");
1271 if (!S_ISREG(s.st_mode)) { 1271 if (!S_ISREG(s.st_mode)) {
@@ -1327,7 +1327,7 @@ void fs_x11(void) {
1327 struct stat s1, s2; 1327 struct stat s1, s2;
1328 if (stat("/tmp", &s1) != 0 || lstat("/tmp/.X11-unix", &s2) != 0) 1328 if (stat("/tmp", &s1) != 0 || lstat("/tmp/.X11-unix", &s2) != 0)
1329 return; 1329 return;
1330 if ((s1.st_mode & S_ISVTX) == 0) { 1330 if ((s1.st_mode & S_ISVTX) != S_ISVTX) {
1331 fwarning("cannot mask X11 sockets: sticky bit not set on /tmp directory\n"); 1331 fwarning("cannot mask X11 sockets: sticky bit not set on /tmp directory\n");
1332 return; 1332 return;
1333 } 1333 }
@@ -1335,26 +1335,26 @@ void fs_x11(void) {
1335 fwarning("cannot mask X11 sockets: /tmp/.X11-unix not owned by root user\n"); 1335 fwarning("cannot mask X11 sockets: /tmp/.X11-unix not owned by root user\n");
1336 return; 1336 return;
1337 } 1337 }
1338
1338 char *x11file; 1339 char *x11file;
1339 if (asprintf(&x11file, "/tmp/.X11-unix/X%d", display) == -1) 1340 if (asprintf(&x11file, "/tmp/.X11-unix/X%d", display) == -1)
1340 errExit("asprintf"); 1341 errExit("asprintf");
1342 int src = open(x11file, O_PATH|O_NOFOLLOW|O_CLOEXEC);
1343 if (src < 0) {
1344 free(x11file);
1345 return;
1346 }
1341 struct stat x11stat; 1347 struct stat x11stat;
1342 if (lstat(x11file, &x11stat) != 0 || !S_ISSOCK(x11stat.st_mode)) { 1348 if (fstat(src, &x11stat) < 0)
1349 errExit("fstat");
1350 if (!S_ISSOCK(x11stat.st_mode)) {
1351 close(src);
1343 free(x11file); 1352 free(x11file);
1344 return; 1353 return;
1345 } 1354 }
1346 1355
1347 if (arg_debug || arg_debug_whitelists) 1356 if (arg_debug || arg_debug_whitelists)
1348 fprintf(stderr, "Masking all X11 sockets except %s\n", x11file); 1357 fprintf(stderr, "Masking all X11 sockets except %s\n", x11file);
1349
1350 // Move the real /tmp/.X11-unix to a scratch location
1351 // so we can still access x11file after we mount a
1352 // tmpfs over /tmp/.X11-unix.
1353 if (mkdir(RUN_WHITELIST_X11_DIR, 0700) == -1)
1354 errExit("mkdir");
1355 if (mount("/tmp/.X11-unix", RUN_WHITELIST_X11_DIR, 0, MS_BIND|MS_REC, 0) < 0)
1356 errExit("mount bind");
1357
1358 // This directory must be mode 1777 1358 // This directory must be mode 1777
1359 if (mount("tmpfs", "/tmp/.X11-unix", "tmpfs", 1359 if (mount("tmpfs", "/tmp/.X11-unix", "tmpfs",
1360 MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_STRICTATIME, 1360 MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_STRICTATIME,
@@ -1363,40 +1363,21 @@ void fs_x11(void) {
1363 fs_logger("tmpfs /tmp/.X11-unix"); 1363 fs_logger("tmpfs /tmp/.X11-unix");
1364 1364
1365 // create an empty root-owned file which will have the desired socket bind-mounted over it 1365 // create an empty root-owned file which will have the desired socket bind-mounted over it
1366 int fd = open(x11file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR | S_IWUSR); 1366 int dst = open(x11file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR | S_IWUSR);
1367 if (fd < 0) 1367 if (dst < 0)
1368 errExit(x11file); 1368 errExit("open");
1369 close(fd);
1370 1369
1371 // the mount source is under control of the user, so be careful and 1370 char *proc_src, *proc_dst;
1372 // mount without following symbolic links, using a file descriptor 1371 if (asprintf(&proc_src, "/proc/self/fd/%d", src) == -1 ||
1373 char *wx11file; 1372 asprintf(&proc_dst, "/proc/self/fd/%d", dst) == -1)
1374 if (asprintf(&wx11file, "%s/X%d", RUN_WHITELIST_X11_DIR, display) == -1)
1375 errExit("asprintf");
1376 fd = safe_fd(wx11file, O_PATH|O_NOFOLLOW|O_CLOEXEC);
1377 if (fd == -1)
1378 errExit("opening X11 socket");
1379 // confirm once more we are mounting a socket
1380 if (fstat(fd, &x11stat) == -1)
1381 errExit("fstat");
1382 if (!S_ISSOCK(x11stat.st_mode)) {
1383 errno = ENOTSOCK;
1384 errExit("mounting X11 socket");
1385 }
1386 char *proc;
1387 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
1388 errExit("asprintf"); 1373 errExit("asprintf");
1389 if (mount(proc, x11file, NULL, MS_BIND|MS_REC, NULL) < 0) 1374 if (mount(proc_src, proc_dst, NULL, MS_BIND | MS_REC, NULL) < 0)
1390 errExit("mount bind"); 1375 errExit("mount bind");
1376 free(proc_src);
1377 free(proc_dst);
1378 close(src);
1379 close(dst);
1391 fs_logger2("whitelist", x11file); 1380 fs_logger2("whitelist", x11file);
1392 close(fd);
1393 free(proc);
1394
1395 // block access to RUN_WHITELIST_X11_DIR
1396 if (mount(RUN_RO_DIR, RUN_WHITELIST_X11_DIR, 0, MS_BIND, 0) < 0)
1397 errExit("mount");
1398 fs_logger2("blacklist", RUN_WHITELIST_X11_DIR);
1399 free(wx11file);
1400 free(x11file); 1381 free(x11file);
1401#endif 1382#endif
1402} 1383}
diff --git a/src/include/rundefs.h b/src/include/rundefs.h
index d14f6782f..3db750da3 100644
--- a/src/include/rundefs.h
+++ b/src/include/rundefs.h
@@ -79,24 +79,8 @@
79#define PATH_SECCOMP_MDWX_32 LIBDIR "/firejail/seccomp.mdwx.32" 79#define PATH_SECCOMP_MDWX_32 LIBDIR "/firejail/seccomp.mdwx.32"
80#define PATH_SECCOMP_BLOCK_SECONDARY LIBDIR "/firejail/seccomp.block_secondary" // secondary arch blocking filter built during make 80#define PATH_SECCOMP_BLOCK_SECONDARY LIBDIR "/firejail/seccomp.block_secondary" // secondary arch blocking filter built during make
81 81
82
83#define RUN_DEV_DIR RUN_MNT_DIR "/dev" 82#define RUN_DEV_DIR RUN_MNT_DIR "/dev"
84#define RUN_DEVLOG_FILE RUN_MNT_DIR "/devlog" 83#define RUN_DEVLOG_FILE RUN_MNT_DIR "/devlog"
85
86#define RUN_WHITELIST_X11_DIR RUN_MNT_DIR "/orig-x11"
87#define RUN_WHITELIST_HOME_USER_DIR RUN_MNT_DIR "/orig-home-user" // home directory whitelisting
88#define RUN_WHITELIST_RUN_USER_DIR RUN_MNT_DIR "/orig-run-user" // run directory whitelisting
89#define RUN_WHITELIST_TMP_DIR RUN_MNT_DIR "/orig-tmp"
90#define RUN_WHITELIST_MEDIA_DIR RUN_MNT_DIR "/orig-media"
91#define RUN_WHITELIST_MNT_DIR RUN_MNT_DIR "/orig-mnt"
92#define RUN_WHITELIST_VAR_DIR RUN_MNT_DIR "/orig-var"
93#define RUN_WHITELIST_DEV_DIR RUN_MNT_DIR "/orig-dev"
94#define RUN_WHITELIST_OPT_DIR RUN_MNT_DIR "/orig-opt"
95#define RUN_WHITELIST_SRV_DIR RUN_MNT_DIR "/orig-srv"
96#define RUN_WHITELIST_ETC_DIR RUN_MNT_DIR "/orig-etc"
97#define RUN_WHITELIST_SHARE_DIR RUN_MNT_DIR "/orig-share"
98#define RUN_WHITELIST_MODULE_DIR RUN_MNT_DIR "/orig-module"
99
100#define RUN_XAUTHORITY_FILE RUN_MNT_DIR "/.Xauthority" // private options 84#define RUN_XAUTHORITY_FILE RUN_MNT_DIR "/.Xauthority" // private options
101#define RUN_XAUTH_FILE RUN_MNT_DIR "/xauth" // x11=xorg 85#define RUN_XAUTH_FILE RUN_MNT_DIR "/xauth" // x11=xorg
102#define RUN_XAUTHORITY_SEC_DIR RUN_MNT_DIR "/.sec.Xauthority" // x11=xorg 86#define RUN_XAUTHORITY_SEC_DIR RUN_MNT_DIR "/.sec.Xauthority" // x11=xorg
diff --git a/src/jailtest/Makefile.in b/src/jailcheck/Makefile.in
index 6306d24ec..d218c1f90 100644
--- a/src/jailtest/Makefile.in
+++ b/src/jailcheck/Makefile.in
@@ -1,16 +1,16 @@
1.PHONY: all 1.PHONY: all
2all: jailtest 2all: jailcheck
3 3
4include ../common.mk 4include ../common.mk
5 5
6%.o : %.c $(H_FILE_LIST) ../include/common.h ../include/pid.h 6%.o : %.c $(H_FILE_LIST) ../include/common.h ../include/pid.h
7 $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@ 7 $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@
8 8
9jailtest: $(OBJS) 9jailcheck: $(OBJS)
10 $(CC) $(LDFLAGS) -o $@ $(OBJS) ../lib/common.o ../lib/pid.o $(LIBS) $(EXTRA_LDFLAGS) 10 $(CC) $(LDFLAGS) -o $@ $(OBJS) ../lib/common.o ../lib/pid.o $(LIBS) $(EXTRA_LDFLAGS)
11 11
12.PHONY: clean 12.PHONY: clean
13clean:; rm -fr *.o jailtest *.gcov *.gcda *.gcno *.plist 13clean:; rm -fr *.o jailcheck *.gcov *.gcda *.gcno *.plist
14 14
15.PHONY: distclean 15.PHONY: distclean
16distclean: clean 16distclean: clean
diff --git a/src/jailtest/access.c b/src/jailcheck/access.c
index 4e737dc7a..c18d64a82 100644
--- a/src/jailtest/access.c
+++ b/src/jailcheck/access.c
@@ -17,7 +17,7 @@
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19*/
20#include "jailtest.h" 20#include "jailcheck.h"
21#include <dirent.h> 21#include <dirent.h>
22#include <sys/wait.h> 22#include <sys/wait.h>
23 23
@@ -74,7 +74,7 @@ void access_setup(const char *directory) {
74 74
75 // create a test file 75 // create a test file
76 char *test_file; 76 char *test_file;
77 if (asprintf(&test_file, "%s/jailtest-access-%d", path, getpid()) == -1) 77 if (asprintf(&test_file, "%s/jailcheck-access-%d", path, getpid()) == -1)
78 errExit("asprintf"); 78 errExit("asprintf");
79 79
80 FILE *fp = fopen(test_file, "w"); 80 FILE *fp = fopen(test_file, "w");
diff --git a/src/jailtest/apparmor.c b/src/jailcheck/apparmor.c
index 9ddfea3de..64f278046 100644
--- a/src/jailtest/apparmor.c
+++ b/src/jailcheck/apparmor.c
@@ -17,7 +17,7 @@
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19*/
20#include "jailtest.h" 20#include "jailcheck.h"
21 21
22#ifdef HAVE_APPARMOR 22#ifdef HAVE_APPARMOR
23#include <sys/apparmor.h> 23#include <sys/apparmor.h>
diff --git a/src/jailtest/jailtest.h b/src/jailcheck/jailcheck.h
index 0c4883061..32be1c978 100644
--- a/src/jailtest/jailtest.h
+++ b/src/jailcheck/jailcheck.h
@@ -17,8 +17,8 @@
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19*/
20#ifndef JAILTEST_H 20#ifndef JAILCHECK_H
21#define JAILTEST_H 21#define JAILCHECK_H
22 22
23#include "../include/common.h" 23#include "../include/common.h"
24 24
diff --git a/src/jailtest/main.c b/src/jailcheck/main.c
index 3369dca39..4d642bf96 100644
--- a/src/jailtest/main.c
+++ b/src/jailcheck/main.c
@@ -17,7 +17,7 @@
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19*/
20#include "jailtest.h" 20#include "jailcheck.h"
21#include "../include/firejail_user.h" 21#include "../include/firejail_user.h"
22#include "../include/pid.h" 22#include "../include/pid.h"
23#include <sys/wait.h> 23#include <sys/wait.h>
@@ -30,7 +30,7 @@ char *user_run_dir = NULL;
30int arg_debug = 0; 30int arg_debug = 0;
31 31
32static char *usage_str = 32static char *usage_str =
33 "Usage: jailtest [options] directory [directory]\n\n" 33 "Usage: jailcheck [options] directory [directory]\n\n"
34 "Options:\n" 34 "Options:\n"
35 " --debug - print debug messages.\n" 35 " --debug - print debug messages.\n"
36 " --help, -? - this help screen.\n" 36 " --help, -? - this help screen.\n"
diff --git a/src/jailtest/noexec.c b/src/jailcheck/noexec.c
index 4347b7eef..7f994d6a1 100644
--- a/src/jailtest/noexec.c
+++ b/src/jailcheck/noexec.c
@@ -17,7 +17,7 @@
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19*/
20#include "jailtest.h" 20#include "jailcheck.h"
21#include <sys/wait.h> 21#include <sys/wait.h>
22#include <sys/stat.h> 22#include <sys/stat.h>
23#include <fcntl.h> 23#include <fcntl.h>
@@ -67,7 +67,7 @@ void noexec_test(const char *path) {
67 return; 67 return;
68 68
69 char *fname; 69 char *fname;
70 if (asprintf(&fname, "%s/jailtest-noexec-%d", path, getpid()) == -1) 70 if (asprintf(&fname, "%s/jailcheck-noexec-%d", path, getpid()) == -1)
71 errExit("asprintf"); 71 errExit("asprintf");
72 72
73 pid_t child = fork(); 73 pid_t child = fork();
diff --git a/src/jailtest/seccomp.c b/src/jailcheck/seccomp.c
index 2cecb4b4d..9345eb970 100644
--- a/src/jailtest/seccomp.c
+++ b/src/jailcheck/seccomp.c
@@ -17,7 +17,7 @@
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19*/
20#include "jailtest.h" 20#include "jailcheck.h"
21#define MAXBUF 4096 21#define MAXBUF 4096
22 22
23void seccomp_test(pid_t pid) { 23void seccomp_test(pid_t pid) {
diff --git a/src/jailtest/sysfiles.c b/src/jailcheck/sysfiles.c
index 7e4709453..caeb580af 100644
--- a/src/jailtest/sysfiles.c
+++ b/src/jailcheck/sysfiles.c
@@ -17,7 +17,7 @@
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19*/
20#include "jailtest.h" 20#include "jailcheck.h"
21#include <dirent.h> 21#include <dirent.h>
22#include <sys/wait.h> 22#include <sys/wait.h>
23 23
diff --git a/src/jailtest/utils.c b/src/jailcheck/utils.c
index 41c21b753..c3aaae298 100644
--- a/src/jailtest/utils.c
+++ b/src/jailcheck/utils.c
@@ -17,7 +17,7 @@
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19*/
20#include "jailtest.h" 20#include "jailcheck.h"
21#include "../include/pid.h" 21#include "../include/pid.h"
22#include <errno.h> 22#include <errno.h>
23#include <pwd.h> 23#include <pwd.h>
diff --git a/src/jailtest/virtual.c b/src/jailcheck/virtual.c
index fcdcf9720..09092f9ce 100644
--- a/src/jailtest/virtual.c
+++ b/src/jailcheck/virtual.c
@@ -17,7 +17,7 @@
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19*/
20#include "jailtest.h" 20#include "jailcheck.h"
21#include <dirent.h> 21#include <dirent.h>
22#include <sys/wait.h> 22#include <sys/wait.h>
23 23
@@ -43,7 +43,7 @@ void virtual_setup(const char *directory) {
43 43
44 // create a test file 44 // create a test file
45 char *test_file; 45 char *test_file;
46 if (asprintf(&test_file, "%s/jailtest-private-%d", directory, getpid()) == -1) 46 if (asprintf(&test_file, "%s/jailcheck-private-%d", directory, getpid()) == -1)
47 errExit("asprintf"); 47 errExit("asprintf");
48 48
49 FILE *fp = fopen(test_file, "w"); 49 FILE *fp = fopen(test_file, "w");
diff --git a/src/man/Makefile.in b/src/man/Makefile.in
index 3711d5cec..fbd2d795e 100644
--- a/src/man/Makefile.in
+++ b/src/man/Makefile.in
@@ -1,5 +1,5 @@
1.PHONY: all 1.PHONY: all
2all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man jailtest.man 2all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man jailcheck.man
3 3
4include ../common.mk 4include ../common.mk
5 5
diff --git a/src/man/firecfg.txt b/src/man/firecfg.txt
index dbb9397c6..7e0a57f92 100644
--- a/src/man/firecfg.txt
+++ b/src/man/firecfg.txt
@@ -135,4 +135,4 @@ Homepage: https://firejail.wordpress.com
135.BR firejail-profile (5), 135.BR firejail-profile (5),
136.BR firejail-login (5), 136.BR firejail-login (5),
137.BR firejail-users (5), 137.BR firejail-users (5),
138.BR jailtest (1) 138.BR jailcheck (1)
diff --git a/src/man/firejail-login.txt b/src/man/firejail-login.txt
index 1b8a4931c..05afd55b5 100644
--- a/src/man/firejail-login.txt
+++ b/src/man/firejail-login.txt
@@ -39,4 +39,4 @@ Homepage: https://firejail.wordpress.com
39.BR firecfg (1), 39.BR firecfg (1),
40.BR firejail-profile (5), 40.BR firejail-profile (5),
41.BR firejail-users (5), 41.BR firejail-users (5),
42.BR jailtest (1) 42.BR jailcheck (1)
diff --git a/src/man/firejail-profile.txt b/src/man/firejail-profile.txt
index 49be8d0b0..6f3bef7f2 100644
--- a/src/man/firejail-profile.txt
+++ b/src/man/firejail-profile.txt
@@ -160,6 +160,11 @@ Example: "blacklist ~/My Virtual Machines"
160 160
161.TP 161.TP
162\fB# this is a comment 162\fB# this is a comment
163Example:
164
165# disable networking
166.br
167net none # this command creates an empty network namespace
163 168
164.TP 169.TP
165\fB?CONDITIONAL: profile line 170\fB?CONDITIONAL: profile line
@@ -423,8 +428,9 @@ Blacklist violations logged to syslog.
423\fBwhitelist file_or_directory 428\fBwhitelist file_or_directory
424Whitelist directory or file. A temporary file system is mounted on the top directory, and the 429Whitelist directory or file. A temporary file system is mounted on the top directory, and the
425whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, 430whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent,
426everything else is discarded when the sandbox is closed. The top directory could be 431everything else is discarded when the sandbox is closed. The top directory can be
427user home, /dev, /etc, /media, /mnt, /opt, /srv, /sys/module, /usr/share, /var, and /tmp. 432all directories in / (except /proc and /sys), /sys/module, /run/user/$UID, $HOME and
433all directories in /usr.
428.br 434.br
429 435
430.br 436.br
@@ -731,6 +737,9 @@ Disable DVD and audio CD devices.
731\fBnogroups 737\fBnogroups
732Disable supplementary user groups 738Disable supplementary user groups
733.TP 739.TP
740\fBnoinput
741Disable input devices.
742.TP
734\fBnosound 743\fBnosound
735Disable sound system. 744Disable sound system.
736.TP 745.TP
@@ -743,9 +752,6 @@ Disable U2F devices.
743\fBnovideo 752\fBnovideo
744Disable video capture devices. 753Disable video capture devices.
745.TP 754.TP
746\fBnoinput
747Disable input devices.
748.TP
749\fBshell none 755\fBshell none
750Run the program directly, without a shell. 756Run the program directly, without a shell.
751 757
@@ -986,7 +992,7 @@ Homepage: https://firejail.wordpress.com
986.BR firecfg (1), 992.BR firecfg (1),
987.BR firejail-login (5), 993.BR firejail-login (5),
988.BR firejail-users (5), 994.BR firejail-users (5),
989.BR jailtest (1) 995.BR jailcheck (1)
990 996
991.UR https://github.com/netblue30/firejail/wiki/Creating-Profiles 997.UR https://github.com/netblue30/firejail/wiki/Creating-Profiles
992.UE 998.UE
diff --git a/src/man/firejail-users.txt b/src/man/firejail-users.txt
index c5a9c1848..e3cce7ed5 100644
--- a/src/man/firejail-users.txt
+++ b/src/man/firejail-users.txt
@@ -59,4 +59,4 @@ Homepage: https://firejail.wordpress.com
59.BR firecfg (1), 59.BR firecfg (1),
60.BR firejail-profile (5), 60.BR firejail-profile (5),
61.BR firejail-login (5), 61.BR firejail-login (5),
62.BR jailtest (1) 62.BR jailcheck (1)
diff --git a/src/man/firejail.txt b/src/man/firejail.txt
index 68aea5857..3212a88e4 100644
--- a/src/man/firejail.txt
+++ b/src/man/firejail.txt
@@ -147,12 +147,12 @@ private-bin and private-lib are disabled by default when running appimages.
147.br 147.br
148Example: 148Example:
149.br 149.br
150$ firejail --appimage krita-3.0-x86_64.appimage 150$ firejail --appimage --profile=krita krita-3.0-x86_64.appimage
151.br 151.br
152$ firejail --appimage --private krita-3.0-x86_64.appimage 152$ firejail --appimage --private --profile=krita krita-3.0-x86_64.appimage
153.br 153.br
154#ifdef HAVE_X11 154#ifdef HAVE_X11
155$ firejail --appimage --net=none --x11 krita-3.0-x86_64.appimage 155$ firejail --appimage --net=none --x11 --profile=krita krita-3.0-x86_64.appimage
156#endif 156#endif
157.TP 157.TP
158#ifdef HAVE_NETWORK 158#ifdef HAVE_NETWORK
@@ -2723,8 +2723,9 @@ $ firejail \-\-net=br0 --veth-name=if0
2723\fB\-\-whitelist=dirname_or_filename 2723\fB\-\-whitelist=dirname_or_filename
2724Whitelist directory or file. A temporary file system is mounted on the top directory, and the 2724Whitelist directory or file. A temporary file system is mounted on the top directory, and the
2725whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, 2725whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent,
2726everything else is discarded when the sandbox is closed. The top directory could be 2726everything else is discarded when the sandbox is closed. The top directory can be
2727user home, /dev, /etc, /media, /mnt, /opt, /run/user/$UID, /srv, /sys/module, /tmp, /usr/share and /var. 2727all directories in / (except /proc and /sys), /sys/module, /run/user/$UID, $HOME and
2728all directories in /usr.
2728.br 2729.br
2729 2730
2730.br 2731.br
@@ -3367,7 +3368,7 @@ Homepage: https://firejail.wordpress.com
3367.BR firejail-profile (5), 3368.BR firejail-profile (5),
3368.BR firejail-login (5), 3369.BR firejail-login (5),
3369.BR firejail-users (5), 3370.BR firejail-users (5),
3370.BR jailtest (1) 3371.BR jailcheck (1)
3371 3372
3372.UR https://github.com/netblue30/firejail/wiki 3373.UR https://github.com/netblue30/firejail/wiki
3373.UE , 3374.UE ,
diff --git a/src/man/firemon.txt b/src/man/firemon.txt
index 64f15a1f0..76b2f7be2 100644
--- a/src/man/firemon.txt
+++ b/src/man/firemon.txt
@@ -120,4 +120,4 @@ Homepage: https://firejail.wordpress.com
120.BR firejail-profile (5), 120.BR firejail-profile (5),
121.BR firejail-login (5), 121.BR firejail-login (5),
122.BR firejail-users (5), 122.BR firejail-users (5),
123.BR jailtest (1) 123.BR jailcheck (1)
diff --git a/src/man/jailtest.txt b/src/man/jailcheck.txt
index b52fc5eed..c80e305cc 100644
--- a/src/man/jailtest.txt
+++ b/src/man/jailcheck.txt
@@ -1,23 +1,22 @@
1.TH JAILTEST 1 "MONTH YEAR" "VERSION" "JAILTEST man page" 1.TH JAILCHECK 1 "MONTH YEAR" "VERSION" "JAILCHECK man page"
2.SH NAME 2.SH NAME
3jailtest \- Simple utility program to test running sandboxes 3jailcheck \- Simple utility program to test running sandboxes
4.SH SYNOPSIS 4.SH SYNOPSIS
5sudo jailtest [OPTIONS] [directory] 5sudo jailcheck [OPTIONS] [directory]
6.SH DESCRIPTION 6.SH DESCRIPTION
7WORK IN PROGRESS! 7jailcheck attaches itself to all sandboxes started by the user and performs some basic tests
8jailtest attaches itself to all sandboxes started by the user and performs some basic tests
9on the sandbox filesystem: 8on the sandbox filesystem:
10.TP 9.TP
11\fB1. Virtual directories 10\fB1. Virtual directories
12jailtest extracts a list with the main virtual directories installed by the sandbox. 11jailcheck 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. 12These directories are build by firejail at startup using --private* and --whitelist commands.
14.TP 13.TP
15\fB2. Noexec test 14\fB2. Noexec test
16jailtest inserts executable programs in /home/username, /tmp, and /var/tmp directories 15jailcheck inserts executable programs in /home/username, /tmp, and /var/tmp directories
17and tries to run them from inside the sandbox, thus testing if the directory is executable or not. 16and tries to run them from inside the sandbox, thus testing if the directory is executable or not.
18.TP 17.TP
19\fB3. Read access test 18\fB3. Read access test
20jailtest creates test files in the directories specified by the user and tries to read 19jailcheck creates test files in the directories specified by the user and tries to read
21them from inside the sandbox. 20them from inside the sandbox.
22.TP 21.TP
23\fB4. AppArmor test 22\fB4. AppArmor test
@@ -49,7 +48,7 @@ It is followed by relevant sandbox information, such as the virtual directories
49 48
50.SH EXAMPLE 49.SH EXAMPLE
51 50
52$ sudo jailtest 51$ sudo jailcheck
53.br 52.br
542014:netblue::firejail /usr/bin/gimp 532014:netblue::firejail /usr/bin/gimp
55.br 54.br