diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/common.mk.in | 7 | ||||
-rw-r--r-- | src/fbuilder/build_profile.c | 54 | ||||
-rw-r--r-- | src/fbuilder/build_seccomp.c | 29 | ||||
-rw-r--r-- | src/fbuilder/main.c | 10 | ||||
-rw-r--r-- | src/fcopy/main.c | 2 | ||||
-rw-r--r-- | src/firecfg/firecfg.config | 7 | ||||
-rw-r--r-- | src/firejail/arp.c | 4 | ||||
-rw-r--r-- | src/firejail/checkcfg.c | 29 | ||||
-rw-r--r-- | src/firejail/chroot.c | 8 | ||||
-rw-r--r-- | src/firejail/dbus.c | 2 | ||||
-rw-r--r-- | src/firejail/firejail.h | 30 | ||||
-rw-r--r-- | src/firejail/fs.c | 10 | ||||
-rw-r--r-- | src/firejail/fs_home.c | 6 | ||||
-rw-r--r-- | src/firejail/fs_whitelist.c | 1363 | ||||
-rw-r--r-- | src/firejail/main.c | 16 | ||||
-rw-r--r-- | src/firejail/profile.c | 14 | ||||
-rw-r--r-- | src/firejail/pulseaudio.c | 2 | ||||
-rw-r--r-- | src/firejail/restrict_users.c | 2 | ||||
-rw-r--r-- | src/firejail/sandbox.c | 10 | ||||
-rw-r--r-- | src/firejail/sbox.c | 2 | ||||
-rw-r--r-- | src/firejail/shutdown.c | 6 | ||||
-rw-r--r-- | src/firejail/util.c | 83 | ||||
-rw-r--r-- | src/firejail/x11.c | 71 | ||||
-rw-r--r-- | src/include/rundefs.h | 16 | ||||
-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.in | 2 | ||||
-rw-r--r-- | src/man/firecfg.txt | 2 | ||||
-rw-r--r-- | src/man/firejail-login.txt | 2 | ||||
-rw-r--r-- | src/man/firejail-profile.txt | 18 | ||||
-rw-r--r-- | src/man/firejail-users.txt | 2 | ||||
-rw-r--r-- | src/man/firejail.txt | 13 | ||||
-rw-r--r-- | src/man/firemon.txt | 2 | ||||
-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@ | |||
23 | HAVE_PRIVATE_HOME=@HAVE_PRIVATE_HOME@ | 23 | HAVE_PRIVATE_HOME=@HAVE_PRIVATE_HOME@ |
24 | HAVE_GCOV=@HAVE_GCOV@ | 24 | HAVE_GCOV=@HAVE_GCOV@ |
25 | HAVE_SELINUX=@HAVE_SELINUX@ | 25 | HAVE_SELINUX=@HAVE_SELINUX@ |
26 | ifeq (@HAVE_SUID@, yes) | ||
27 | HAVE_SUID=-DHAVE_SUID | ||
28 | else | ||
29 | HAVE_SUID= | ||
30 | endif | ||
26 | HAVE_DBUSPROXY=@HAVE_DBUSPROXY@ | 31 | HAVE_DBUSPROXY=@HAVE_DBUSPROXY@ |
27 | HAVE_USERTMPFS=@HAVE_USERTMPFS@ | 32 | HAVE_USERTMPFS=@HAVE_USERTMPFS@ |
28 | HAVE_OUTPUT=@HAVE_OUTPUT@ | 33 | HAVE_OUTPUT=@HAVE_OUTPUT@ |
@@ -37,7 +42,7 @@ BINOBJS = $(foreach file, $(OBJS), $file) | |||
37 | CFLAGS = @CFLAGS@ | 42 | CFLAGS = @CFLAGS@ |
38 | CFLAGS += -ggdb $(HAVE_FATAL_WARNINGS) -O2 -DVERSION='"$(VERSION)"' $(HAVE_GCOV) | 43 | CFLAGS += -ggdb $(HAVE_FATAL_WARNINGS) -O2 -DVERSION='"$(VERSION)"' $(HAVE_GCOV) |
39 | CFLAGS += -DPREFIX='"$(prefix)"' -DSYSCONFDIR='"$(sysconfdir)/firejail"' -DLIBDIR='"$(libdir)"' -DBINDIR='"$(bindir)"' | 44 | CFLAGS += -DPREFIX='"$(prefix)"' -DSYSCONFDIR='"$(sysconfdir)/firejail"' -DLIBDIR='"$(libdir)"' -DBINDIR='"$(bindir)"' |
40 | MANFLAGS = $(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) | 45 | MANFLAGS = $(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) |
41 | CFLAGS += $(MANFLAGS) | 46 | CFLAGS += $(MANFLAGS) |
42 | CFLAGS += -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security | 47 | CFLAGS += -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security |
43 | LDFLAGS += -pie -fPIE -Wl,-z,relro -Wl,-z,now -lpthread | 48 | LDFLAGS += -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 | |||
42 | void build_profile(int argc, char **argv, int index, FILE *fp) { | 27 | void 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 | //*************************************** |
85 | int unix_s = 0; | 85 | static int unix_s = 0; |
86 | int inet = 0; | 86 | static int inet = 0; |
87 | int inet6 = 0; | 87 | static int inet6 = 0; |
88 | int netlink = 0; | 88 | static int netlink = 0; |
89 | int packet = 0; | 89 | static int packet = 0; |
90 | static int bluetooth = 0; | ||
90 | static void process_protocol(const char *fname) { | 91 | static 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 | ||
341 | errexit: | 341 | errexit: |
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 | |||
186 | dnox | 186 | dnox |
187 | dnscrypt-proxy | 187 | dnscrypt-proxy |
188 | dnsmasq | 188 | dnsmasq |
189 | dolphin | ||
190 | dolphin-emu | 189 | dolphin-emu |
191 | dooble | 190 | dooble |
192 | dooble-qt4 | 191 | dooble-qt4 |
@@ -271,6 +270,7 @@ freetube | |||
271 | freshclam | 270 | freshclam |
272 | frogatto | 271 | frogatto |
273 | frozen-bubble | 272 | frozen-bubble |
273 | funnyboat | ||
274 | gajim | 274 | gajim |
275 | gajim-history-manager | 275 | gajim-history-manager |
276 | galculator | 276 | galculator |
@@ -357,6 +357,7 @@ gradio | |||
357 | gramps | 357 | gramps |
358 | gravity-beams-and-evaporating-stars | 358 | gravity-beams-and-evaporating-stars |
359 | gthumb | 359 | gthumb |
360 | gtk-pipe-viewer | ||
360 | gtk-straw-viewer | 361 | gtk-straw-viewer |
361 | gtk-youtube-viewer | 362 | gtk-youtube-viewer |
362 | gtk2-youtube-viewer | 363 | gtk2-youtube-viewer |
@@ -443,6 +444,7 @@ kube | |||
443 | kwrite | 444 | kwrite |
444 | leafpad | 445 | leafpad |
445 | # less - breaks man | 446 | # less - breaks man |
447 | librecad | ||
446 | libreoffice | 448 | libreoffice |
447 | librewolf | 449 | librewolf |
448 | librewolf-nightly | 450 | librewolf-nightly |
@@ -450,6 +452,7 @@ liferea | |||
450 | lightsoff | 452 | lightsoff |
451 | lincity-ng | 453 | lincity-ng |
452 | links | 454 | links |
455 | links2 | ||
453 | linphone | 456 | linphone |
454 | lmms | 457 | lmms |
455 | lobase | 458 | lobase |
@@ -626,6 +629,7 @@ pinball | |||
626 | pingus | 629 | pingus |
627 | pinta | 630 | pinta |
628 | pioneer | 631 | pioneer |
632 | pipe-viewer | ||
629 | pithos | 633 | pithos |
630 | pitivi | 634 | pitivi |
631 | pix | 635 | pix |
@@ -868,6 +872,7 @@ xfce4-notes | |||
868 | xfce4-screenshooter | 872 | xfce4-screenshooter |
869 | xiphos | 873 | xiphos |
870 | xlinks | 874 | xlinks |
875 | xlinks2 | ||
871 | xmms | 876 | xmms |
872 | xmr-stak | 877 | xmr-stak |
873 | xonotic | 878 | xonotic |
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 = ""; | |||
35 | char *netfilter_default = NULL; | 35 | char *netfilter_default = NULL; |
36 | unsigned long join_timeout = 5000000; // microseconds | 36 | unsigned long join_timeout = 5000000; // microseconds |
37 | char *config_seccomp_error_action_str = "EPERM"; | 37 | char *config_seccomp_error_action_str = "EPERM"; |
38 | char **whitelist_reject_topdirs = NULL; | ||
38 | 39 | ||
39 | int checkcfg(int val) { | 40 | int 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 | ||
274 | void print_compiletime_support(void) { | 299 | void 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 | ||
418 | static void socket_overlay(char *socket_path, char *proxy_path) { | 418 | static 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 | ||
125 | typedef struct topdir_t { | ||
126 | char *path; | ||
127 | int fd; | ||
128 | } TopDir; | ||
129 | |||
125 | typedef struct profile_entry_t { | 130 | typedef 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 | ||
147 | typedef struct config_t { | 143 | typedef struct config_t { |
@@ -529,7 +525,7 @@ void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid); | |||
529 | unsigned extract_timeout(const char *str); | 525 | unsigned extract_timeout(const char *str); |
530 | void disable_file_or_dir(const char *fname); | 526 | void disable_file_or_dir(const char *fname); |
531 | void disable_file_path(const char *path, const char *file); | 527 | void disable_file_path(const char *path, const char *file); |
532 | int safe_fd(const char *path, int flags); | 528 | int safer_openat(int dirfd, const char *path, int flags); |
533 | int has_handler(pid_t pid, int signal); | 529 | int has_handler(pid_t pid, int signal); |
534 | void enter_network_namespace(pid_t pid); | 530 | void enter_network_namespace(pid_t pid); |
535 | int read_pid(const char *name, pid_t *pid); | 531 | int 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; | |||
798 | extern char *netfilter_default; | 793 | extern char *netfilter_default; |
799 | extern unsigned long join_timeout; | 794 | extern unsigned long join_timeout; |
800 | extern char *config_seccomp_error_action_str; | 795 | extern char *config_seccomp_error_action_str; |
796 | extern char **whitelist_reject_topdirs; | ||
801 | 797 | ||
802 | int checkcfg(int val); | 798 | int checkcfg(int val); |
803 | void print_compiletime_support(void); | 799 | void 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 ("") | 39 | static size_t homedir_len = 0; // cache length of homedir string |
40 | static size_t homedir_len; // cache length of homedir string | 40 | static size_t runuser_len = 0; // cache length of runuser string |
41 | static size_t runuser_len; // cache length of runuser string | 41 | static char *runuser = NULL; |
42 | static char *runuser; | ||
43 | 42 | ||
44 | 43 | ||
45 | static 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 | 45 | static 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 | |||
52 | static 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 | ||
120 | static void whitelist_path(ProfileEntry *entry) { | 107 | static 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 | ||
334 | static void whitelist_home(int topdir) { | 231 | static 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 | ||
349 | static void globbing(const char *pattern) { | 269 | static 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 | ||
308 | static 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 | |||
403 | static 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 | ||
417 | static 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 | |||
479 | static 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 | |||
493 | static 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 | ||
382 | void fs_whitelist(void) { | 531 | void 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 | ||
1216 | errexit: | 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 | ||
550 | char *clean_pathname(const char *path) { | 551 | char *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 |
1128 | int 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 | ||
1145 | int 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 |
1293 | static int has_link(const char *dir) { | 1312 | static 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 |
2 | all: jailtest | 2 | all: jailcheck |
3 | 3 | ||
4 | include ../common.mk | 4 | include ../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 | ||
9 | jailtest: $(OBJS) | 9 | jailcheck: $(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 |
13 | clean:; rm -fr *.o jailtest *.gcov *.gcda *.gcno *.plist | 13 | clean:; rm -fr *.o jailcheck *.gcov *.gcda *.gcno *.plist |
14 | 14 | ||
15 | .PHONY: distclean | 15 | .PHONY: distclean |
16 | distclean: clean | 16 | distclean: 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; | |||
30 | int arg_debug = 0; | 30 | int arg_debug = 0; |
31 | 31 | ||
32 | static char *usage_str = | 32 | static 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 | ||
23 | void seccomp_test(pid_t pid) { | 23 | void 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 |
2 | all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man jailtest.man | 2 | all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man jailcheck.man |
3 | 3 | ||
4 | include ../common.mk | 4 | include ../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 |
163 | Example: | ||
164 | |||
165 | # disable networking | ||
166 | .br | ||
167 | net 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 |
424 | Whitelist directory or file. A temporary file system is mounted on the top directory, and the | 429 | Whitelist directory or file. A temporary file system is mounted on the top directory, and the |
425 | whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, | 430 | whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, |
426 | everything else is discarded when the sandbox is closed. The top directory could be | 431 | everything else is discarded when the sandbox is closed. The top directory can be |
427 | user home, /dev, /etc, /media, /mnt, /opt, /srv, /sys/module, /usr/share, /var, and /tmp. | 432 | all directories in / (except /proc and /sys), /sys/module, /run/user/$UID, $HOME and |
433 | all 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 |
732 | Disable supplementary user groups | 738 | Disable supplementary user groups |
733 | .TP | 739 | .TP |
740 | \fBnoinput | ||
741 | Disable input devices. | ||
742 | .TP | ||
734 | \fBnosound | 743 | \fBnosound |
735 | Disable sound system. | 744 | Disable sound system. |
736 | .TP | 745 | .TP |
@@ -743,9 +752,6 @@ Disable U2F devices. | |||
743 | \fBnovideo | 752 | \fBnovideo |
744 | Disable video capture devices. | 753 | Disable video capture devices. |
745 | .TP | 754 | .TP |
746 | \fBnoinput | ||
747 | Disable input devices. | ||
748 | .TP | ||
749 | \fBshell none | 755 | \fBshell none |
750 | Run the program directly, without a shell. | 756 | Run 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 |
148 | Example: | 148 | Example: |
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 |
2724 | Whitelist directory or file. A temporary file system is mounted on the top directory, and the | 2724 | Whitelist directory or file. A temporary file system is mounted on the top directory, and the |
2725 | whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, | 2725 | whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, |
2726 | everything else is discarded when the sandbox is closed. The top directory could be | 2726 | everything else is discarded when the sandbox is closed. The top directory can be |
2727 | user home, /dev, /etc, /media, /mnt, /opt, /run/user/$UID, /srv, /sys/module, /tmp, /usr/share and /var. | 2727 | all directories in / (except /proc and /sys), /sys/module, /run/user/$UID, $HOME and |
2728 | all 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 |
3 | jailtest \- Simple utility program to test running sandboxes | 3 | jailcheck \- Simple utility program to test running sandboxes |
4 | .SH SYNOPSIS | 4 | .SH SYNOPSIS |
5 | sudo jailtest [OPTIONS] [directory] | 5 | sudo jailcheck [OPTIONS] [directory] |
6 | .SH DESCRIPTION | 6 | .SH DESCRIPTION |
7 | WORK IN PROGRESS! | 7 | jailcheck attaches itself to all sandboxes started by the user and performs some basic tests |
8 | jailtest attaches itself to all sandboxes started by the user and performs some basic tests | ||
9 | on the sandbox filesystem: | 8 | on the sandbox filesystem: |
10 | .TP | 9 | .TP |
11 | \fB1. Virtual directories | 10 | \fB1. Virtual directories |
12 | jailtest extracts a list with the main virtual directories installed by the sandbox. | 11 | jailcheck extracts a list with the main virtual directories installed by the sandbox. |
13 | These directories are build by firejail at startup using --private* and --whitelist commands. | 12 | These directories are build by firejail at startup using --private* and --whitelist commands. |
14 | .TP | 13 | .TP |
15 | \fB2. Noexec test | 14 | \fB2. Noexec test |
16 | jailtest inserts executable programs in /home/username, /tmp, and /var/tmp directories | 15 | jailcheck inserts executable programs in /home/username, /tmp, and /var/tmp directories |
17 | and tries to run them from inside the sandbox, thus testing if the directory is executable or not. | 16 | and 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 |
20 | jailtest creates test files in the directories specified by the user and tries to read | 19 | jailcheck creates test files in the directories specified by the user and tries to read |
21 | them from inside the sandbox. | 20 | them 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 |
54 | 2014:netblue::firejail /usr/bin/gimp | 53 | 2014:netblue::firejail /usr/bin/gimp |
55 | .br | 54 | .br |