aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Reiner Herrmann <reiner@reiner-h.de>2021-06-21 23:10:09 +0200
committerLibravatar Reiner Herrmann <reiner@reiner-h.de>2021-06-21 23:10:09 +0200
commit0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2 (patch)
tree0875693a6ceef54818511972601d587a09a1aab4 /src
parentstyle: grammer and codestyle improvements (diff)
parentcreating alpine.profile (#4350) (diff)
downloadfirejail-0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2.tar.gz
firejail-0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2.tar.zst
firejail-0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2.zip
Merge branch 'master' into kuesji/master
Diffstat (limited to 'src')
-rw-r--r--src/fcopy/main.c19
-rw-r--r--src/firecfg/firecfg.config8
-rw-r--r--src/firejail/appimage.c35
-rw-r--r--src/firejail/checkcfg.c13
-rw-r--r--src/firejail/chroot.c43
-rw-r--r--src/firejail/cmdline.c32
-rw-r--r--src/firejail/dbus.c18
-rw-r--r--src/firejail/dhcp.c12
-rw-r--r--src/firejail/firejail.h35
-rw-r--r--src/firejail/fs.c253
-rw-r--r--src/firejail/fs_dev.c2
-rw-r--r--src/firejail/fs_home.c63
-rw-r--r--src/firejail/fs_lib.c13
-rw-r--r--src/firejail/fs_mkdir.c3
-rw-r--r--src/firejail/fs_trace.c6
-rw-r--r--src/firejail/fs_var.c4
-rw-r--r--src/firejail/fs_whitelist.c39
-rw-r--r--src/firejail/join.c2
-rw-r--r--src/firejail/ls.c4
-rw-r--r--src/firejail/macros.c1
-rw-r--r--src/firejail/main.c152
-rw-r--r--src/firejail/mountinfo.c5
-rw-r--r--src/firejail/no_sandbox.c2
-rw-r--r--src/firejail/paths.c2
-rw-r--r--src/firejail/profile.c82
-rw-r--r--src/firejail/pulseaudio.c68
-rw-r--r--src/firejail/restrict_users.c22
-rw-r--r--src/firejail/rlimit.c4
-rw-r--r--src/firejail/sandbox.c3
-rw-r--r--src/firejail/sbox.c4
-rw-r--r--src/firejail/selinux.c10
-rw-r--r--src/firejail/shutdown.c6
-rw-r--r--src/firejail/util.c258
-rw-r--r--src/firejail/x11.c98
-rw-r--r--src/firemon/interface.c4
-rw-r--r--src/firemon/netstats.c4
-rw-r--r--src/firemon/procevent.c4
-rw-r--r--src/firemon/top.c4
-rw-r--r--src/include/rundefs.h4
-rw-r--r--src/jailcheck/access.c2
-rw-r--r--src/jailcheck/jailcheck.h2
-rw-r--r--src/jailcheck/main.c23
-rw-r--r--src/jailcheck/network.c56
-rw-r--r--src/jailcheck/sysfiles.c2
-rw-r--r--src/lib/ldd_utils.c2
-rw-r--r--src/man/firejail-profile.txt7
-rw-r--r--src/man/firejail.txt10
-rw-r--r--src/man/jailcheck.txt12
48 files changed, 902 insertions, 555 deletions
diff --git a/src/fcopy/main.c b/src/fcopy/main.c
index 572e9f601..31810de9a 100644
--- a/src/fcopy/main.c
+++ b/src/fcopy/main.c
@@ -19,11 +19,15 @@
19 */ 19 */
20 20
21#include "../include/common.h" 21#include "../include/common.h"
22#include <fcntl.h>
23#include <ftw.h> 22#include <ftw.h>
24#include <errno.h> 23#include <errno.h>
25#include <pwd.h> 24#include <pwd.h>
26 25
26#include <fcntl.h>
27#ifndef O_PATH
28#define O_PATH 010000000
29#endif
30
27#if HAVE_SELINUX 31#if HAVE_SELINUX
28#include <sys/stat.h> 32#include <sys/stat.h>
29#include <sys/types.h> 33#include <sys/types.h>
@@ -55,7 +59,7 @@ static void selinux_relabel_path(const char *path, const char *inside_path) {
55 assert(path); 59 assert(path);
56 assert(inside_path); 60 assert(inside_path);
57#if HAVE_SELINUX 61#if HAVE_SELINUX
58 char procfs_path[64]; 62 char procfs_path[64];
59 char *fcon = NULL; 63 char *fcon = NULL;
60 int fd; 64 int fd;
61 struct stat st; 65 struct stat st;
@@ -69,20 +73,23 @@ static void selinux_relabel_path(const char *path, const char *inside_path) {
69 if (!label_hnd) 73 if (!label_hnd)
70 label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0); 74 label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
71 75
76 if (!label_hnd)
77 errExit("selabel_open");
78
72 /* Open the file as O_PATH, to pin it while we determine and adjust the label */ 79 /* Open the file as O_PATH, to pin it while we determine and adjust the label */
73 fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH); 80 fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
74 if (fd < 0) 81 if (fd < 0)
75 return; 82 return;
76 if (fstat(fd, &st) < 0) 83 if (fstat(fd, &st) < 0)
77 goto close; 84 goto close;
78 85
79 if (selabel_lookup_raw(label_hnd, &fcon, inside_path, st.st_mode) == 0) { 86 if (selabel_lookup_raw(label_hnd, &fcon, inside_path, st.st_mode) == 0) {
80 sprintf(procfs_path, "/proc/self/fd/%i", fd); 87 sprintf(procfs_path, "/proc/self/fd/%i", fd);
81 if (arg_debug) 88 if (arg_debug)
82 printf("Relabeling %s as %s (%s)\n", path, inside_path, fcon); 89 printf("Relabeling %s as %s (%s)\n", path, inside_path, fcon);
83 90
84 setfilecon_raw(procfs_path, fcon); 91 setfilecon_raw(procfs_path, fcon);
85 } 92 }
86 freecon(fcon); 93 freecon(fcon);
87 close: 94 close:
88 close(fd); 95 close(fd);
@@ -340,7 +347,7 @@ static char *check(const char *src) {
340 347
341errexit: 348errexit:
342 free(rsrc); 349 free(rsrc);
343 fprintf(stderr, "Error fcopy: invalid file %s\n", src); 350 fprintf(stderr, "Error fcopy: invalid ownership for file %s\n", src);
344 exit(1); 351 exit(1);
345} 352}
346 353
diff --git a/src/firecfg/firecfg.config b/src/firecfg/firecfg.config
index f0e3a887f..e58fe39ec 100644
--- a/src/firecfg/firecfg.config
+++ b/src/firecfg/firecfg.config
@@ -38,6 +38,8 @@ abrowser
38akonadi_control 38akonadi_control
39akregator 39akregator
40alacarte 40alacarte
41alpine
42alpinef
41amarok 43amarok
42amule 44amule
43amuled 45amuled
@@ -167,6 +169,7 @@ cvlc
167cyberfox 169cyberfox
168darktable 170darktable
169dconf-editor 171dconf-editor
172ddgr
170ddgtk 173ddgtk
171deadbeef 174deadbeef
172deluge 175deluge
@@ -350,6 +353,7 @@ google-chrome-unstable
350google-earth 353google-earth
351google-earth-pro 354google-earth-pro
352google-play-music-desktop-player 355google-play-music-desktop-player
356googler
353gpa 357gpa
354gpicview 358gpicview
355gpredict 359gpredict
@@ -452,6 +456,7 @@ liferea
452lightsoff 456lightsoff
453lincity-ng 457lincity-ng
454links 458links
459links2
455linphone 460linphone
456lmms 461lmms
457lobase 462lobase
@@ -491,6 +496,7 @@ mathematica
491matrix-mirage 496matrix-mirage
492mattermost-desktop 497mattermost-desktop
493mcabber 498mcabber
499mcomix
494mediainfo 500mediainfo
495mediathekview 501mediathekview
496megaglest 502megaglest
@@ -651,6 +657,7 @@ pybitmessage
651# pycharm-professional 657# pycharm-professional
652# pzstd - disable until we fix CLI archivers for makepkg on Arch (see discussion in #3095) 658# pzstd - disable until we fix CLI archivers for makepkg on Arch (see discussion in #3095)
653qbittorrent 659qbittorrent
660qcomicbook
654qemu-launcher 661qemu-launcher
655qgis 662qgis
656qlipper 663qlipper
@@ -871,6 +878,7 @@ xfce4-notes
871xfce4-screenshooter 878xfce4-screenshooter
872xiphos 879xiphos
873xlinks 880xlinks
881xlinks2
874xmms 882xmms
875xmr-stak 883xmr-stak
876xonotic 884xonotic
diff --git a/src/firejail/appimage.c b/src/firejail/appimage.c
index 6b9fed765..a96415985 100644
--- a/src/firejail/appimage.c
+++ b/src/firejail/appimage.c
@@ -28,8 +28,13 @@
28#include <linux/loop.h> 28#include <linux/loop.h>
29#include <errno.h> 29#include <errno.h>
30 30
31#ifdef HAVE_GCOV
32#include <gcov.h>
33#endif
34
31static char *devloop = NULL; // device file 35static char *devloop = NULL; // device file
32static long unsigned size = 0; // offset into appimage file 36static long unsigned size = 0; // offset into appimage file
37#define MAXBUF 4096
33 38
34#ifdef LOOP_CTL_GET_FREE // test for older kernels; this definition is found in /usr/include/linux/loop.h 39#ifdef LOOP_CTL_GET_FREE // test for older kernels; this definition is found in /usr/include/linux/loop.h
35static void err_loop(void) { 40static void err_loop(void) {
@@ -38,6 +43,36 @@ static void err_loop(void) {
38} 43}
39#endif 44#endif
40 45
46// return 1 if found
47int appimage_find_profile(const char *archive) {
48 assert(archive);
49 assert(strlen(archive));
50
51 // try to match the name of the archive with the list of programs in /usr/lib/firejail/firecfg.config
52 FILE *fp = fopen(LIBDIR "/firejail/firecfg.config", "r");
53 if (!fp) {
54 fprintf(stderr, "Error: cannot find %s, firejail is not correctly installed\n", LIBDIR "/firejail/firecfg.config");
55 exit(1);
56 }
57 char buf[MAXBUF];
58 while (fgets(buf, MAXBUF, fp)) {
59 if (*buf == '#')
60 continue;
61 char *ptr = strchr(buf, '\n');
62 if (ptr)
63 *ptr = '\0';
64 if (strcasestr(archive, buf)) {
65 fclose(fp);
66 return profile_find_firejail(buf, 1);
67 }
68 }
69
70 fclose(fp);
71 return 0;
72
73}
74
75
41void appimage_set(const char *appimage) { 76void appimage_set(const char *appimage) {
42 assert(appimage); 77 assert(appimage);
43 assert(devloop == NULL); // don't call this twice! 78 assert(devloop == NULL); // don't call this twice!
diff --git a/src/firejail/checkcfg.c b/src/firejail/checkcfg.c
index cb087d395..f3ab0a6d8 100644
--- a/src/firejail/checkcfg.c
+++ b/src/firejail/checkcfg.c
@@ -110,10 +110,14 @@ int checkcfg(int val) {
110 PARSE_YESNO(CFG_RESTRICTED_NETWORK, "restricted-network") 110 PARSE_YESNO(CFG_RESTRICTED_NETWORK, "restricted-network")
111 PARSE_YESNO(CFG_XEPHYR_WINDOW_TITLE, "xephyr-window-title") 111 PARSE_YESNO(CFG_XEPHYR_WINDOW_TITLE, "xephyr-window-title")
112 PARSE_YESNO(CFG_OVERLAYFS, "overlayfs") 112 PARSE_YESNO(CFG_OVERLAYFS, "overlayfs")
113 PARSE_YESNO(CFG_PRIVATE_HOME, "private-home") 113 PARSE_YESNO(CFG_PRIVATE_BIN, "private-bin")
114 PARSE_YESNO(CFG_PRIVATE_BIN_NO_LOCAL, "private-bin-no-local")
114 PARSE_YESNO(CFG_PRIVATE_CACHE, "private-cache") 115 PARSE_YESNO(CFG_PRIVATE_CACHE, "private-cache")
116 PARSE_YESNO(CFG_PRIVATE_ETC, "private-etc")
117 PARSE_YESNO(CFG_PRIVATE_HOME, "private-home")
115 PARSE_YESNO(CFG_PRIVATE_LIB, "private-lib") 118 PARSE_YESNO(CFG_PRIVATE_LIB, "private-lib")
116 PARSE_YESNO(CFG_PRIVATE_BIN_NO_LOCAL, "private-bin-no-local") 119 PARSE_YESNO(CFG_PRIVATE_OPT, "private-opt")
120 PARSE_YESNO(CFG_PRIVATE_SRV, "private-srv")
117 PARSE_YESNO(CFG_DISABLE_MNT, "disable-mnt") 121 PARSE_YESNO(CFG_DISABLE_MNT, "disable-mnt")
118 PARSE_YESNO(CFG_XPRA_ATTACH, "xpra-attach") 122 PARSE_YESNO(CFG_XPRA_ATTACH, "xpra-attach")
119 PARSE_YESNO(CFG_BROWSER_DISABLE_U2F, "browser-disable-u2f") 123 PARSE_YESNO(CFG_BROWSER_DISABLE_U2F, "browser-disable-u2f")
@@ -130,8 +134,7 @@ int checkcfg(int val) {
130 *end = '\0'; 134 *end = '\0';
131 135
132 // is the file present? 136 // is the file present?
133 struct stat s; 137 if (access(fname, F_OK) == -1) {
134 if (stat(fname, &s) == -1) {
135 fprintf(stderr, "Error: netfilter-default file %s not available\n", fname); 138 fprintf(stderr, "Error: netfilter-default file %s not available\n", fname);
136 exit(1); 139 exit(1);
137 } 140 }
@@ -294,7 +297,7 @@ errout:
294 297
295void print_compiletime_support(void) { 298void print_compiletime_support(void) {
296 printf("Compile time support:\n"); 299 printf("Compile time support:\n");
297 printf("\t- Always force nonewprivs support is %s\n", 300 printf("\t- always force nonewprivs support is %s\n",
298#ifdef HAVE_FORCE_NONEWPRIVS 301#ifdef HAVE_FORCE_NONEWPRIVS
299 "enabled" 302 "enabled"
300#else 303#else
diff --git a/src/firejail/chroot.c b/src/firejail/chroot.c
index 757ffb1f7..edc31cdea 100644
--- a/src/firejail/chroot.c
+++ b/src/firejail/chroot.c
@@ -29,6 +29,9 @@
29#define O_PATH 010000000 29#define O_PATH 010000000
30#endif 30#endif
31 31
32#ifdef HAVE_GCOV
33#include <gcov.h>
34#endif
32 35
33// exit if error 36// exit if error
34void fs_check_chroot_dir(void) { 37void fs_check_chroot_dir(void) {
@@ -163,12 +166,8 @@ void fs_chroot(const char *rootdir) {
163 int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 166 int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
164 if (fd == -1) 167 if (fd == -1)
165 errExit("open"); 168 errExit("open");
166 char *proc; 169 if (bind_mount_path_to_fd("/dev", fd))
167 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
168 errExit("asprintf");
169 if (mount("/dev", proc, NULL, MS_BIND|MS_REC, NULL) < 0)
170 errExit("mounting /dev"); 170 errExit("mounting /dev");
171 free(proc);
172 close(fd); 171 close(fd);
173 172
174#ifdef HAVE_X11 173#ifdef HAVE_X11
@@ -192,11 +191,8 @@ void fs_chroot(const char *rootdir) {
192 fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 191 fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
193 if (fd == -1) 192 if (fd == -1)
194 errExit("open"); 193 errExit("open");
195 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 194 if (bind_mount_path_to_fd("/tmp/.X11-unix", fd))
196 errExit("asprintf");
197 if (mount("/tmp/.X11-unix", proc, NULL, MS_BIND|MS_REC, NULL) < 0)
198 errExit("mounting /tmp/.X11-unix"); 195 errExit("mounting /tmp/.X11-unix");
199 free(proc);
200 close(fd); 196 close(fd);
201 } 197 }
202#endif // HAVE_X11 198#endif // HAVE_X11
@@ -225,19 +221,11 @@ void fs_chroot(const char *rootdir) {
225 fprintf(stderr, "Error: cannot open %s\n", pulse); 221 fprintf(stderr, "Error: cannot open %s\n", pulse);
226 exit(1); 222 exit(1);
227 } 223 }
228 free(pulse); 224 if (bind_mount_by_fd(src, dst))
229 225 errExit("mounting pulseaudio");
230 char *proc_src, *proc_dst;
231 if (asprintf(&proc_src, "/proc/self/fd/%d", src) == -1)
232 errExit("asprintf");
233 if (asprintf(&proc_dst, "/proc/self/fd/%d", dst) == -1)
234 errExit("asprintf");
235 if (mount(proc_src, proc_dst, NULL, MS_BIND|MS_REC, NULL) < 0)
236 errExit("mount bind");
237 free(proc_src);
238 free(proc_dst);
239 close(src); 226 close(src);
240 close(dst); 227 close(dst);
228 free(pulse);
241 229
242 // update /etc/machine-id in chroot 230 // update /etc/machine-id in chroot
243 update_file(parentfd, "etc/machine-id"); 231 update_file(parentfd, "etc/machine-id");
@@ -256,11 +244,8 @@ void fs_chroot(const char *rootdir) {
256 fd = openat(parentfd, &RUN_FIREJAIL_LIB_DIR[1], O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 244 fd = openat(parentfd, &RUN_FIREJAIL_LIB_DIR[1], O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
257 if (fd == -1) 245 if (fd == -1)
258 errExit("open"); 246 errExit("open");
259 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 247 if (bind_mount_path_to_fd(RUN_FIREJAIL_LIB_DIR, fd))
260 errExit("asprintf");
261 if (mount(RUN_FIREJAIL_LIB_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0)
262 errExit("mount bind"); 248 errExit("mount bind");
263 free(proc);
264 close(fd); 249 close(fd);
265 250
266 // create /run/firejail/mnt directory in chroot 251 // create /run/firejail/mnt directory in chroot
@@ -271,11 +256,8 @@ void fs_chroot(const char *rootdir) {
271 fd = openat(parentfd, &RUN_MNT_DIR[1], O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 256 fd = openat(parentfd, &RUN_MNT_DIR[1], O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
272 if (fd == -1) 257 if (fd == -1)
273 errExit("open"); 258 errExit("open");
274 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 259 if (bind_mount_path_to_fd(RUN_MNT_DIR, fd))
275 errExit("asprintf");
276 if (mount(RUN_MNT_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0)
277 errExit("mount bind"); 260 errExit("mount bind");
278 free(proc);
279 close(fd); 261 close(fd);
280 262
281 // update chroot resolv.conf 263 // update chroot resolv.conf
@@ -289,11 +271,8 @@ void fs_chroot(const char *rootdir) {
289 if (mkdir(oroot, 0755) == -1) 271 if (mkdir(oroot, 0755) == -1)
290 errExit("mkdir"); 272 errExit("mkdir");
291 // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay 273 // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay
292 if (asprintf(&proc, "/proc/self/fd/%d", parentfd) == -1) 274 if (bind_mount_fd_to_path(parentfd, oroot))
293 errExit("asprintf");
294 if (mount(proc, oroot, NULL, MS_BIND|MS_REC, NULL) < 0)
295 errExit("mounting rootdir oroot"); 275 errExit("mounting rootdir oroot");
296 free(proc);
297 close(parentfd); 276 close(parentfd);
298 // chroot into the new directory 277 // chroot into the new directory
299 if (arg_debug) 278 if (arg_debug)
diff --git a/src/firejail/cmdline.c b/src/firejail/cmdline.c
index f902c4e1c..2fa68a55d 100644
--- a/src/firejail/cmdline.c
+++ b/src/firejail/cmdline.c
@@ -26,7 +26,7 @@
26#include <assert.h> 26#include <assert.h>
27#include <errno.h> 27#include <errno.h>
28 28
29static int cmdline_length(int argc, char **argv, int index) { 29static int cmdline_length(int argc, char **argv, int index, bool want_extra_quotes) {
30 assert(index != -1); 30 assert(index != -1);
31 31
32 unsigned i,j; 32 unsigned i,j;
@@ -46,10 +46,11 @@ static int cmdline_length(int argc, char **argv, int index) {
46 len += 3; 46 len += 3;
47 in_quotes = false; 47 in_quotes = false;
48 } else { 48 } else {
49 if (!in_quotes) 49 if (!in_quotes && want_extra_quotes)
50 len++; 50 len++;
51 len++; 51 len++;
52 in_quotes = true; 52 if (want_extra_quotes)
53 in_quotes = true;
53 } 54 }
54 } 55 }
55 if (in_quotes) { 56 if (in_quotes) {
@@ -64,7 +65,7 @@ static int cmdline_length(int argc, char **argv, int index) {
64 return len; 65 return len;
65} 66}
66 67
67static void quote_cmdline(char *command_line, char *window_title, int len, int argc, char **argv, int index) { 68static void quote_cmdline(char *command_line, char *window_title, int len, int argc, char **argv, int index, bool want_extra_quotes) {
68 assert(index != -1); 69 assert(index != -1);
69 70
70 unsigned i,j; 71 unsigned i,j;
@@ -103,14 +104,15 @@ static void quote_cmdline(char *command_line, char *window_title, int len, int a
103 // anything other 104 // anything other
104 else 105 else
105 { 106 {
106 if (!in_quotes) { 107 if (!in_quotes && want_extra_quotes) {
107 // open quotes 108 // open quotes
108 ptr1[0] = '\''; 109 ptr1[0] = '\'';
109 ptr1++; 110 ptr1++;
110 } 111 }
111 ptr1[0] = argv[i + index][j]; 112 ptr1[0] = argv[i + index][j];
112 ptr1++; 113 ptr1++;
113 in_quotes = true; 114 if (want_extra_quotes)
115 in_quotes = true;
114 } 116 }
115 } 117 }
116 // close quotes 118 // close quotes
@@ -134,12 +136,12 @@ static void quote_cmdline(char *command_line, char *window_title, int len, int a
134 assert((unsigned) len == strlen(command_line)); 136 assert((unsigned) len == strlen(command_line));
135} 137}
136 138
137void build_cmdline(char **command_line, char **window_title, int argc, char **argv, int index) { 139void build_cmdline(char **command_line, char **window_title, int argc, char **argv, int index, bool want_extra_quotes) {
138 // index == -1 could happen if we have --shell=none and no program was specified 140 // index == -1 could happen if we have --shell=none and no program was specified
139 // the program should exit with an error before entering this function 141 // the program should exit with an error before entering this function
140 assert(index != -1); 142 assert(index != -1);
141 143
142 int len = cmdline_length(argc, argv, index); 144 int len = cmdline_length(argc, argv, index, want_extra_quotes);
143 if (len > ARG_MAX) { 145 if (len > ARG_MAX) {
144 errno = E2BIG; 146 errno = E2BIG;
145 errExit("cmdline_length"); 147 errExit("cmdline_length");
@@ -152,7 +154,7 @@ void build_cmdline(char **command_line, char **window_title, int argc, char **ar
152 if (!*window_title) 154 if (!*window_title)
153 errExit("malloc"); 155 errExit("malloc");
154 156
155 quote_cmdline(*command_line, *window_title, len, argc, argv, index); 157 quote_cmdline(*command_line, *window_title, len, argc, argv, index, want_extra_quotes);
156 158
157 if (arg_debug) 159 if (arg_debug)
158 printf("Building quoted command line: %s\n", *command_line); 160 printf("Building quoted command line: %s\n", *command_line);
@@ -161,17 +163,17 @@ void build_cmdline(char **command_line, char **window_title, int argc, char **ar
161 assert(*window_title); 163 assert(*window_title);
162} 164}
163 165
164void build_appimage_cmdline(char **command_line, char **window_title, int argc, char **argv, int index) { 166void build_appimage_cmdline(char **command_line, char **window_title, int argc, char **argv, int index, bool want_extra_quotes) {
165 // index == -1 could happen if we have --shell=none and no program was specified 167 // index == -1 could happen if we have --shell=none and no program was specified
166 // the program should exit with an error before entering this function 168 // the program should exit with an error before entering this function
167 assert(index != -1); 169 assert(index != -1);
168 170
169 char *apprun_path = RUN_FIREJAIL_APPIMAGE_DIR "/AppRun"; 171 char *apprun_path = RUN_FIREJAIL_APPIMAGE_DIR "/AppRun";
170 172
171 int len1 = cmdline_length(argc, argv, index); // length of argv w/o changes 173 int len1 = cmdline_length(argc, argv, index, want_extra_quotes); // length of argv w/o changes
172 int len2 = cmdline_length(1, &argv[index], 0); // apptest.AppImage 174 int len2 = cmdline_length(1, &argv[index], 0, want_extra_quotes); // apptest.AppImage
173 int len3 = cmdline_length(1, &apprun_path, 0); // /run/firejail/appimage/AppRun 175 int len3 = cmdline_length(1, &apprun_path, 0, want_extra_quotes); // /run/firejail/appimage/AppRun
174 int len4 = (len1 - len2 + len3) + 1; // apptest.AppImage is replaced by /path/to/AppRun 176 int len4 = (len1 - len2 + len3) + 1; // apptest.AppImage is replaced by /path/to/AppRun
175 177
176 if (len4 > ARG_MAX) { 178 if (len4 > ARG_MAX) {
177 errno = E2BIG; 179 errno = E2BIG;
@@ -187,7 +189,7 @@ void build_appimage_cmdline(char **command_line, char **window_title, int argc,
187 errExit("malloc"); 189 errExit("malloc");
188 190
189 // run default quote_cmdline 191 // run default quote_cmdline
190 quote_cmdline(command_line_tmp, *window_title, len1, argc, argv, index); 192 quote_cmdline(command_line_tmp, *window_title, len1, argc, argv, index, want_extra_quotes);
191 193
192 assert(command_line_tmp); 194 assert(command_line_tmp);
193 assert(*window_title); 195 assert(*window_title);
diff --git a/src/firejail/dbus.c b/src/firejail/dbus.c
index b8aa2c974..9a4cb2e6b 100644
--- a/src/firejail/dbus.c
+++ b/src/firejail/dbus.c
@@ -258,12 +258,8 @@ static char *find_user_socket_by_format(char *format) {
258 if (asprintf(&dbus_user_socket, format, (int) getuid()) == -1) 258 if (asprintf(&dbus_user_socket, format, (int) getuid()) == -1)
259 errExit("asprintf"); 259 errExit("asprintf");
260 struct stat s; 260 struct stat s;
261 if (stat(dbus_user_socket, &s) == -1) { 261 if (lstat(dbus_user_socket, &s) == -1)
262 if (errno == ENOENT) 262 goto fail;
263 goto fail;
264 return NULL;
265 errExit("stat");
266 }
267 if (!S_ISSOCK(s.st_mode)) 263 if (!S_ISSOCK(s.st_mode))
268 goto fail; 264 goto fail;
269 return dbus_user_socket; 265 return dbus_user_socket;
@@ -426,12 +422,8 @@ static void socket_overlay(char *socket_path, char *proxy_path) {
426 errno = ENOTSOCK; 422 errno = ENOTSOCK;
427 errExit("mounting DBus proxy socket"); 423 errExit("mounting DBus proxy socket");
428 } 424 }
429 char *proxy_fd_path; 425 if (bind_mount_fd_to_path(fd, socket_path))
430 if (asprintf(&proxy_fd_path, "/proc/self/fd/%d", fd) == -1)
431 errExit("asprintf");
432 if (mount(proxy_path, socket_path, NULL, MS_BIND | MS_REC, NULL) == -1)
433 errExit("mount bind"); 426 errExit("mount bind");
434 free(proxy_fd_path);
435 close(fd); 427 close(fd);
436} 428}
437 429
@@ -478,7 +470,7 @@ void dbus_apply_policy(void) {
478 create_empty_dir_as_root(RUN_DBUS_DIR, 0755); 470 create_empty_dir_as_root(RUN_DBUS_DIR, 0755);
479 471
480 if (arg_dbus_user != DBUS_POLICY_ALLOW) { 472 if (arg_dbus_user != DBUS_POLICY_ALLOW) {
481 create_empty_file_as_root(RUN_DBUS_USER_SOCKET, 0700); 473 create_empty_file_as_root(RUN_DBUS_USER_SOCKET, 0600);
482 474
483 if (arg_dbus_user == DBUS_POLICY_FILTER) { 475 if (arg_dbus_user == DBUS_POLICY_FILTER) {
484 assert(dbus_user_proxy_socket != NULL); 476 assert(dbus_user_proxy_socket != NULL);
@@ -517,7 +509,7 @@ void dbus_apply_policy(void) {
517 } 509 }
518 510
519 if (arg_dbus_system != DBUS_POLICY_ALLOW) { 511 if (arg_dbus_system != DBUS_POLICY_ALLOW) {
520 create_empty_file_as_root(RUN_DBUS_SYSTEM_SOCKET, 0700); 512 create_empty_file_as_root(RUN_DBUS_SYSTEM_SOCKET, 0600);
521 513
522 if (arg_dbus_system == DBUS_POLICY_FILTER) { 514 if (arg_dbus_system == DBUS_POLICY_FILTER) {
523 assert(dbus_system_proxy_socket != NULL); 515 assert(dbus_system_proxy_socket != NULL);
diff --git a/src/firejail/dhcp.c b/src/firejail/dhcp.c
index 5bcdcad37..ec482e2ea 100644
--- a/src/firejail/dhcp.c
+++ b/src/firejail/dhcp.c
@@ -153,19 +153,13 @@ void dhcp_start(void) {
153 if (!any_dhcp()) 153 if (!any_dhcp())
154 return; 154 return;
155 155
156 char *dhclient_path = RUN_MNT_DIR "/dhclient";; 156 char *dhclient_path = RUN_MNT_DIR "/dhclient";
157 struct stat s; 157 struct stat s;
158 if (stat(dhclient_path, &s) == -1) { 158 if (stat(dhclient_path, &s) == -1) {
159 dhclient_path = "/usr/sbin/dhclient"; 159 fprintf(stderr, "Error: %s was not found.\n", dhclient_path);
160 if (stat(dhclient_path, &s) == -1) { 160 exit(1);
161 fprintf(stderr, "Error: dhclient was not found.\n");
162 exit(1);
163 }
164 } 161 }
165 162
166 sbox_run(SBOX_ROOT| SBOX_SECCOMP, 4, PATH_FCOPY, "--follow-link", dhclient_path, RUN_MNT_DIR);
167 dhclient_path = RUN_MNT_DIR "/dhclient";
168
169 EUID_ROOT(); 163 EUID_ROOT();
170 if (mkdir(RUN_DHCLIENT_DIR, 0700)) 164 if (mkdir(RUN_DHCLIENT_DIR, 0700))
171 errExit("mkdir"); 165 errExit("mkdir");
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h
index 756a5d095..c84965074 100644
--- a/src/firejail/firejail.h
+++ b/src/firejail/firejail.h
@@ -5,7 +5,7 @@
5 * 5 *
6 * This program is free software; you can redistribute it and/or modify 6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by 7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or 8 * the Free Software Foundation; eithe r version 2 of the License, or
9 * (at your option) any later version. 9 * (at your option) any later version.
10 * 10 *
11 * This program is distributed in the hope that it will be useful, 11 * This program is distributed in the hope that it will be useful,
@@ -45,6 +45,15 @@
45 assert(s.st_gid == gid);\ 45 assert(s.st_gid == gid);\
46 assert((s.st_mode & 07777) == (mode));\ 46 assert((s.st_mode & 07777) == (mode));\
47 } while (0) 47 } while (0)
48#define ASSERT_PERMS_AS_USER(file, uid, gid, mode) \
49 do { \
50 assert(file);\
51 struct stat s;\
52 if (stat_as_user(file, &s) == -1) errExit("stat");\
53 assert(s.st_uid == uid);\
54 assert(s.st_gid == gid);\
55 assert((s.st_mode & 07777) == (mode));\
56 } while (0)
48#define ASSERT_PERMS_FD(fd, uid, gid, mode) \ 57#define ASSERT_PERMS_FD(fd, uid, gid, mode) \
49 do { \ 58 do { \
50 struct stat s;\ 59 struct stat s;\
@@ -504,6 +513,9 @@ void copy_file_from_user_to_root(const char *srcname, const char *destname, uid_
504void touch_file_as_user(const char *fname, mode_t mode); 513void touch_file_as_user(const char *fname, mode_t mode);
505int is_dir(const char *fname); 514int is_dir(const char *fname);
506int is_link(const char *fname); 515int is_link(const char *fname);
516char *realpath_as_user(const char *fname);
517int stat_as_user(const char *fname, struct stat *s);
518int lstat_as_user(const char *fname, struct stat *s);
507void trim_trailing_slash_or_dot(char *path); 519void trim_trailing_slash_or_dot(char *path);
508char *line_remove_spaces(const char *buf); 520char *line_remove_spaces(const char *buf);
509char *split_comma(char *str); 521char *split_comma(char *str);
@@ -527,11 +539,15 @@ unsigned extract_timeout(const char *str);
527void disable_file_or_dir(const char *fname); 539void disable_file_or_dir(const char *fname);
528void disable_file_path(const char *path, const char *file); 540void disable_file_path(const char *path, const char *file);
529int safer_openat(int dirfd, const char *path, int flags); 541int safer_openat(int dirfd, const char *path, int flags);
542int remount_by_fd(int dst, unsigned long mountflags);
543int bind_mount_by_fd(int src, int dst);
544int bind_mount_path_to_fd(const char *srcname, int dst);
545int bind_mount_fd_to_path(int src, const char *destname);
530int has_handler(pid_t pid, int signal); 546int has_handler(pid_t pid, int signal);
531void enter_network_namespace(pid_t pid); 547void enter_network_namespace(pid_t pid);
532int read_pid(const char *name, pid_t *pid); 548int read_pid(const char *name, pid_t *pid);
533pid_t require_pid(const char *name); 549pid_t require_pid(const char *name);
534void check_homedir(void); 550void check_homedir(const char *dir);
535 551
536// Get info regarding the last kernel mount operation from /proc/self/mountinfo 552// Get info regarding the last kernel mount operation from /proc/self/mountinfo
537// The return value points to a static area, and will be overwritten by subsequent calls. 553// The return value points to a static area, and will be overwritten by subsequent calls.
@@ -763,8 +779,14 @@ enum {
763 CFG_WHITELIST, 779 CFG_WHITELIST,
764 CFG_XEPHYR_WINDOW_TITLE, 780 CFG_XEPHYR_WINDOW_TITLE,
765 CFG_OVERLAYFS, 781 CFG_OVERLAYFS,
766 CFG_PRIVATE_HOME, 782 CFG_PRIVATE_BIN,
767 CFG_PRIVATE_BIN_NO_LOCAL, 783 CFG_PRIVATE_BIN_NO_LOCAL,
784 CFG_PRIVATE_CACHE,
785 CFG_PRIVATE_ETC,
786 CFG_PRIVATE_HOME,
787 CFG_PRIVATE_LIB,
788 CFG_PRIVATE_OPT,
789 CFG_PRIVATE_SRV,
768 CFG_FIREJAIL_PROMPT, 790 CFG_FIREJAIL_PROMPT,
769 CFG_DISABLE_MNT, 791 CFG_DISABLE_MNT,
770 CFG_JOIN, 792 CFG_JOIN,
@@ -772,10 +794,8 @@ enum {
772 CFG_XPRA_ATTACH, 794 CFG_XPRA_ATTACH,
773 CFG_BROWSER_DISABLE_U2F, 795 CFG_BROWSER_DISABLE_U2F,
774 CFG_BROWSER_ALLOW_DRM, 796 CFG_BROWSER_ALLOW_DRM,
775 CFG_PRIVATE_LIB,
776 CFG_APPARMOR, 797 CFG_APPARMOR,
777 CFG_DBUS, 798 CFG_DBUS,
778 CFG_PRIVATE_CACHE,
779 CFG_CGROUP, 799 CFG_CGROUP,
780 CFG_NAME_CHANGE, 800 CFG_NAME_CHANGE,
781 CFG_SECCOMP_ERROR_ACTION, 801 CFG_SECCOMP_ERROR_ACTION,
@@ -796,6 +816,7 @@ int checkcfg(int val);
796void print_compiletime_support(void); 816void print_compiletime_support(void);
797 817
798// appimage.c 818// appimage.c
819int appimage_find_profile(const char *archive);
799void appimage_set(const char *appimage_path); 820void appimage_set(const char *appimage_path);
800void appimage_mount(void); 821void appimage_mount(void);
801void appimage_clear(void); 822void appimage_clear(void);
@@ -804,8 +825,8 @@ void appimage_clear(void);
804long unsigned int appimage2_size(int fd); 825long unsigned int appimage2_size(int fd);
805 826
806// cmdline.c 827// cmdline.c
807void build_cmdline(char **command_line, char **window_title, int argc, char **argv, int index); 828void build_cmdline(char **command_line, char **window_title, int argc, char **argv, int index, bool want_extra_quotes);
808void build_appimage_cmdline(char **command_line, char **window_title, int argc, char **argv, int index); 829void build_appimage_cmdline(char **command_line, char **window_title, int argc, char **argv, int index, bool want_extra_quotes);
809 830
810// sbox.c 831// sbox.c
811// programs 832// programs
diff --git a/src/firejail/fs.c b/src/firejail/fs.c
index 09de11de9..4ae7dbfa4 100644
--- a/src/firejail/fs.c
+++ b/src/firejail/fs.c
@@ -33,6 +33,10 @@
33#define O_PATH 010000000 33#define O_PATH 010000000
34#endif 34#endif
35 35
36#ifdef HAVE_GCOV
37#include <gcov.h>
38#endif
39
36#define MAX_BUF 4096 40#define MAX_BUF 4096
37#define EMPTY_STRING ("") 41#define EMPTY_STRING ("")
38// check noblacklist statements not matched by a proper blacklist in disable-*.inc files 42// check noblacklist statements not matched by a proper blacklist in disable-*.inc files
@@ -54,16 +58,10 @@ static char *opstr[] = {
54 [MOUNT_RDWR_NOCHECK] = "read-write", 58 [MOUNT_RDWR_NOCHECK] = "read-write",
55}; 59};
56 60
57typedef enum {
58 UNSUCCESSFUL,
59 SUCCESSFUL
60} LAST_DISABLE_OPERATION;
61LAST_DISABLE_OPERATION last_disable = UNSUCCESSFUL;
62
63static void disable_file(OPERATION op, const char *filename) { 61static void disable_file(OPERATION op, const char *filename) {
64 assert(filename); 62 assert(filename);
65 assert(op <OPERATION_MAX); 63 assert(op <OPERATION_MAX);
66 last_disable = UNSUCCESSFUL; 64 EUID_ASSERT();
67 65
68 // Resolve all symlinks 66 // Resolve all symlinks
69 char* fname = realpath(filename, NULL); 67 char* fname = realpath(filename, NULL);
@@ -71,20 +69,24 @@ static void disable_file(OPERATION op, const char *filename) {
71 return; 69 return;
72 } 70 }
73 if (fname == NULL && errno == EACCES) { 71 if (fname == NULL && errno == EACCES) {
74 if (arg_debug)
75 printf("Debug: no access to file %s, forcing mount\n", filename);
76 // realpath and stat functions will fail on FUSE filesystems 72 // realpath and stat functions will fail on FUSE filesystems
77 // they don't seem to like a uid of 0 73 // they don't seem to like a uid of 0
78 // force mounting 74 // force mounting
79 int rv = mount(RUN_RO_DIR, filename, "none", MS_BIND, "mode=400,gid=0"); 75 int fd = open(filename, O_PATH|O_CLOEXEC);
80 if (rv == 0) 76 if (fd < 0) {
81 last_disable = SUCCESSFUL; 77 if (arg_debug)
82 else { 78 printf("Warning (blacklisting): cannot open %s: %s\n", filename, strerror(errno));
83 rv = mount(RUN_RO_FILE, filename, "none", MS_BIND, "mode=400,gid=0"); 79 return;
84 if (rv == 0)
85 last_disable = SUCCESSFUL;
86 } 80 }
87 if (last_disable == SUCCESSFUL) { 81
82 EUID_ROOT();
83 int err = bind_mount_path_to_fd(RUN_RO_DIR, fd);
84 if (err != 0)
85 err = bind_mount_path_to_fd(RUN_RO_FILE, fd);
86 EUID_USER();
87 close(fd);
88
89 if (err == 0) {
88 if (arg_debug) 90 if (arg_debug)
89 printf("Disable %s\n", filename); 91 printf("Disable %s\n", filename);
90 if (op == BLACKLIST_FILE) 92 if (op == BLACKLIST_FILE)
@@ -92,21 +94,18 @@ static void disable_file(OPERATION op, const char *filename) {
92 else 94 else
93 fs_logger2("blacklist-nolog", filename); 95 fs_logger2("blacklist-nolog", filename);
94 } 96 }
95 else { 97 else if (arg_debug)
96 if (arg_debug) 98 printf("Warning (blacklisting): cannot mount on %s\n", filename);
97 printf("Warning (blacklisting): %s is an invalid file, skipping...\n", filename);
98 }
99 99
100 return; 100 return;
101 } 101 }
102 102
103 // if the file is not present, do nothing 103 // if the file is not present, do nothing
104 assert(fname);
104 struct stat s; 105 struct stat s;
105 if (fname == NULL) 106 if (stat(fname, &s) < 0) {
106 return;
107 if (stat(fname, &s) == -1) {
108 if (arg_debug) 107 if (arg_debug)
109 fwarning("%s does not exist, skipping...\n", fname); 108 printf("Warning (blacklisting): cannot access %s: %s\n", fname, strerror(errno));
110 free(fname); 109 free(fname);
111 return; 110 return;
112 } 111 }
@@ -115,8 +114,10 @@ static void disable_file(OPERATION op, const char *filename) {
115 // we migth have a file found in ${PATH} pointing to /usr/bin/firejail 114 // we migth have a file found in ${PATH} pointing to /usr/bin/firejail
116 // blacklisting it here will end up breaking situations like user clicks on a link in Thunderbird 115 // blacklisting it here will end up breaking situations like user clicks on a link in Thunderbird
117 // and expects Firefox to open in the same sandbox 116 // and expects Firefox to open in the same sandbox
118 if (strcmp(BINDIR "/firejail", fname) == 0) 117 if (strcmp(BINDIR "/firejail", fname) == 0) {
118 free(fname);
119 return; 119 return;
120 }
120 121
121 // modify the file 122 // modify the file
122 if (op == BLACKLIST_FILE || op == BLACKLIST_NOLOG) { 123 if (op == BLACKLIST_FILE || op == BLACKLIST_NOLOG) {
@@ -141,15 +142,25 @@ static void disable_file(OPERATION op, const char *filename) {
141 printf(" - no logging\n"); 142 printf(" - no logging\n");
142 } 143 }
143 144
145 int fd = open(fname, O_PATH|O_CLOEXEC);
146 if (fd < 0) {
147 if (arg_debug)
148 printf("Warning (blacklisting): cannot open %s: %s\n", fname, strerror(errno));
149 free(fname);
150 return;
151 }
152 EUID_ROOT();
144 if (S_ISDIR(s.st_mode)) { 153 if (S_ISDIR(s.st_mode)) {
145 if (mount(RUN_RO_DIR, fname, "none", MS_BIND, "mode=400,gid=0") < 0) 154 if (bind_mount_path_to_fd(RUN_RO_DIR, fd) < 0)
146 errExit("disable file"); 155 errExit("disable file");
147 } 156 }
148 else { 157 else {
149 if (mount(RUN_RO_FILE, fname, "none", MS_BIND, "mode=400,gid=0") < 0) 158 if (bind_mount_path_to_fd(RUN_RO_FILE, fd) < 0)
150 errExit("disable file"); 159 errExit("disable file");
151 } 160 }
152 last_disable = SUCCESSFUL; 161 EUID_USER();
162 close(fd);
163
153 if (op == BLACKLIST_FILE) 164 if (op == BLACKLIST_FILE)
154 fs_logger2("blacklist", fname); 165 fs_logger2("blacklist", fname);
155 else 166 else
@@ -158,23 +169,30 @@ static void disable_file(OPERATION op, const char *filename) {
158 } 169 }
159 else if (op == MOUNT_READONLY || op == MOUNT_RDWR || op == MOUNT_NOEXEC) { 170 else if (op == MOUNT_READONLY || op == MOUNT_RDWR || op == MOUNT_NOEXEC) {
160 fs_remount_rec(fname, op); 171 fs_remount_rec(fname, op);
161 // todo: last_disable = SUCCESSFUL;
162 } 172 }
163 else if (op == MOUNT_TMPFS) { 173 else if (op == MOUNT_TMPFS) {
164 if (S_ISDIR(s.st_mode)) { 174 if (!S_ISDIR(s.st_mode)) {
165 if (getuid()) { 175 fwarning("%s is not a directory; cannot mount a tmpfs on top of it.\n", fname);
166 if (strncmp(cfg.homedir, fname, strlen(cfg.homedir)) != 0 || 176 free(fname);
167 fname[strlen(cfg.homedir)] != '/') { 177 return;
168 fprintf(stderr, "Error: tmpfs outside $HOME is only available for root\n"); 178 }
169 exit(1); 179
170 } 180 uid_t uid = getuid();
181 if (uid != 0) {
182 // only user owned directories in user home
183 if (s.st_uid != uid ||
184 strncmp(cfg.homedir, fname, strlen(cfg.homedir)) != 0 ||
185 fname[strlen(cfg.homedir)] != '/') {
186 fwarning("you are not allowed to mount a tmpfs on %s\n", fname);
187 free(fname);
188 return;
171 } 189 }
172 fs_tmpfs(fname, getuid());
173 selinux_relabel_path(fname, fname);
174 last_disable = SUCCESSFUL;
175 } 190 }
176 else 191
177 fwarning("%s is not a directory; cannot mount a tmpfs on top of it.\n", fname); 192 fs_tmpfs(fname, uid);
193 EUID_USER(); // fs_tmpfs returns with EUID 0
194
195 selinux_relabel_path(fname, fname);
178 } 196 }
179 else 197 else
180 assert(0); 198 assert(0);
@@ -191,6 +209,7 @@ static int *nbcheck = NULL;
191// Treat pattern as a shell glob pattern and blacklist matching files 209// Treat pattern as a shell glob pattern and blacklist matching files
192static void globbing(OPERATION op, const char *pattern, const char *noblacklist[], size_t noblacklist_len) { 210static void globbing(OPERATION op, const char *pattern, const char *noblacklist[], size_t noblacklist_len) {
193 assert(pattern); 211 assert(pattern);
212 EUID_ASSERT();
194 213
195#ifdef TEST_NO_BLACKLIST_MATCHING 214#ifdef TEST_NO_BLACKLIST_MATCHING
196 if (nbcheck_start == 0) { 215 if (nbcheck_start == 0) {
@@ -264,6 +283,7 @@ void fs_blacklist(void) {
264 if (noblacklist == NULL) 283 if (noblacklist == NULL)
265 errExit("failed allocating memory for noblacklist entries"); 284 errExit("failed allocating memory for noblacklist entries");
266 285
286 EUID_USER();
267 while (entry) { 287 while (entry) {
268 OPERATION op = OPERATION_MAX; 288 OPERATION op = OPERATION_MAX;
269 char *ptr; 289 char *ptr;
@@ -294,11 +314,13 @@ void fs_blacklist(void) {
294 if (arg_debug) 314 if (arg_debug)
295 printf("Mount-bind %s on top of %s\n", dname1, dname2); 315 printf("Mount-bind %s on top of %s\n", dname1, dname2);
296 // preserve dname2 mode and ownership 316 // preserve dname2 mode and ownership
317 // EUID_ROOT(); - option not accessible to non-root users
297 if (mount(dname1, dname2, NULL, MS_BIND|MS_REC, NULL) < 0) 318 if (mount(dname1, dname2, NULL, MS_BIND|MS_REC, NULL) < 0)
298 errExit("mount bind"); 319 errExit("mount bind");
299 /* coverity[toctou] */ 320 /* coverity[toctou] */
300 if (set_perms(dname2, s.st_uid, s.st_gid,s.st_mode)) 321 if (set_perms(dname2, s.st_uid, s.st_gid,s.st_mode))
301 errExit("set_perms"); 322 errExit("set_perms");
323 // EUID_USER();
302 324
303 entry = entry->next; 325 entry = entry->next;
304 continue; 326 continue;
@@ -376,16 +398,12 @@ void fs_blacklist(void) {
376 op = MOUNT_TMPFS; 398 op = MOUNT_TMPFS;
377 } 399 }
378 else if (strncmp(entry->data, "mkdir ", 6) == 0) { 400 else if (strncmp(entry->data, "mkdir ", 6) == 0) {
379 EUID_USER();
380 fs_mkdir(entry->data + 6); 401 fs_mkdir(entry->data + 6);
381 EUID_ROOT();
382 entry = entry->next; 402 entry = entry->next;
383 continue; 403 continue;
384 } 404 }
385 else if (strncmp(entry->data, "mkfile ", 7) == 0) { 405 else if (strncmp(entry->data, "mkfile ", 7) == 0) {
386 EUID_USER();
387 fs_mkfile(entry->data + 7); 406 fs_mkfile(entry->data + 7);
388 EUID_ROOT();
389 entry = entry->next; 407 entry = entry->next;
390 continue; 408 continue;
391 } 409 }
@@ -441,6 +459,8 @@ void fs_blacklist(void) {
441 for (i = 0; i < noblacklist_c; i++) 459 for (i = 0; i < noblacklist_c; i++)
442 free(noblacklist[i]); 460 free(noblacklist[i]);
443 free(noblacklist); 461 free(noblacklist);
462
463 EUID_ROOT();
444} 464}
445 465
446//*********************************************** 466//***********************************************
@@ -449,6 +469,7 @@ void fs_blacklist(void) {
449 469
450// mount a writable tmpfs on directory; requires a resolved path 470// mount a writable tmpfs on directory; requires a resolved path
451void fs_tmpfs(const char *dir, unsigned check_owner) { 471void fs_tmpfs(const char *dir, unsigned check_owner) {
472 EUID_USER();
452 assert(dir); 473 assert(dir);
453 if (arg_debug) 474 if (arg_debug)
454 printf("Mounting tmpfs on %s, check owner: %s\n", dir, (check_owner)? "yes": "no"); 475 printf("Mounting tmpfs on %s, check owner: %s\n", dir, (check_owner)? "yes": "no");
@@ -473,6 +494,7 @@ void fs_tmpfs(const char *dir, unsigned check_owner) {
473 errExit("fstatvfs"); 494 errExit("fstatvfs");
474 unsigned long flags = buf.f_flag & ~(MS_RDONLY|MS_BIND); 495 unsigned long flags = buf.f_flag & ~(MS_RDONLY|MS_BIND);
475 // mount via the symbolic link in /proc/self/fd 496 // mount via the symbolic link in /proc/self/fd
497 EUID_ROOT();
476 char *proc; 498 char *proc;
477 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 499 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
478 errExit("asprintf"); 500 errExit("asprintf");
@@ -490,38 +512,42 @@ void fs_tmpfs(const char *dir, unsigned check_owner) {
490 512
491// remount path, preserving other mount flags; requires a resolved path 513// remount path, preserving other mount flags; requires a resolved path
492static void fs_remount_simple(const char *path, OPERATION op) { 514static void fs_remount_simple(const char *path, OPERATION op) {
515 EUID_ASSERT();
493 assert(path); 516 assert(path);
494 517
495 // open path without following symbolic links 518 // open path without following symbolic links
496 int fd1 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); 519 int fd = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
497 if (fd1 == -1) 520 if (fd < 0)
498 goto out; 521 goto out;
499 struct stat s1; 522
500 if (fstat(fd1, &s1) == -1) { 523 struct stat s;
524 if (fstat(fd, &s) < 0) {
501 // fstat can fail with EACCES if path is a FUSE mount, 525 // fstat can fail with EACCES if path is a FUSE mount,
502 // mounted without 'allow_root' or 'allow_other' 526 // mounted without 'allow_root' or 'allow_other'
503 if (errno != EACCES) 527 if (errno != EACCES)
504 errExit("fstat"); 528 errExit("fstat");
505 close(fd1); 529 close(fd);
506 goto out; 530 goto out;
507 } 531 }
508 // get mount flags 532 // get mount flags
509 struct statvfs buf; 533 struct statvfs buf;
510 if (fstatvfs(fd1, &buf) == -1) 534 if (fstatvfs(fd, &buf) < 0) {
511 errExit("fstatvfs"); 535 close(fd);
536 goto out;
537 }
512 unsigned long flags = buf.f_flag; 538 unsigned long flags = buf.f_flag;
513 539
514 // read-write option 540 // read-write option
515 if (op == MOUNT_RDWR || op == MOUNT_RDWR_NOCHECK) { 541 if (op == MOUNT_RDWR || op == MOUNT_RDWR_NOCHECK) {
516 // nothing to do if there is no read-only flag 542 // nothing to do if there is no read-only flag
517 if ((flags & MS_RDONLY) == 0) { 543 if ((flags & MS_RDONLY) == 0) {
518 close(fd1); 544 close(fd);
519 return; 545 return;
520 } 546 }
521 // allow only user owned directories, except the user is root 547 // allow only user owned directories, except the user is root
522 if (op != MOUNT_RDWR_NOCHECK && getuid() != 0 && s1.st_uid != getuid()) { 548 if (op != MOUNT_RDWR_NOCHECK && getuid() != 0 && s.st_uid != getuid()) {
523 fwarning("you are not allowed to change %s to read-write\n", path); 549 fwarning("you are not allowed to change %s to read-write\n", path);
524 close(fd1); 550 close(fd);
525 return; 551 return;
526 } 552 }
527 flags &= ~MS_RDONLY; 553 flags &= ~MS_RDONLY;
@@ -530,7 +556,7 @@ static void fs_remount_simple(const char *path, OPERATION op) {
530 else if (op == MOUNT_NOEXEC) { 556 else if (op == MOUNT_NOEXEC) {
531 // nothing to do if path is mounted noexec already 557 // nothing to do if path is mounted noexec already
532 if ((flags & (MS_NOEXEC|MS_NODEV|MS_NOSUID)) == (MS_NOEXEC|MS_NODEV|MS_NOSUID)) { 558 if ((flags & (MS_NOEXEC|MS_NODEV|MS_NOSUID)) == (MS_NOEXEC|MS_NODEV|MS_NOSUID)) {
533 close(fd1); 559 close(fd);
534 return; 560 return;
535 } 561 }
536 flags |= MS_NOEXEC|MS_NODEV|MS_NOSUID; 562 flags |= MS_NOEXEC|MS_NODEV|MS_NOSUID;
@@ -539,7 +565,7 @@ static void fs_remount_simple(const char *path, OPERATION op) {
539 else if (op == MOUNT_READONLY) { 565 else if (op == MOUNT_READONLY) {
540 // nothing to do if path is mounted read-only already 566 // nothing to do if path is mounted read-only already
541 if ((flags & MS_RDONLY) == MS_RDONLY) { 567 if ((flags & MS_RDONLY) == MS_RDONLY) {
542 close(fd1); 568 close(fd);
543 return; 569 return;
544 } 570 }
545 flags |= MS_RDONLY; 571 flags |= MS_RDONLY;
@@ -549,29 +575,37 @@ static void fs_remount_simple(const char *path, OPERATION op) {
549 575
550 if (arg_debug) 576 if (arg_debug)
551 printf("Mounting %s %s\n", opstr[op], path); 577 printf("Mounting %s %s\n", opstr[op], path);
578
579 // make path a mount point:
552 // mount --bind path path 580 // mount --bind path path
553 char *proc; 581 EUID_ROOT();
554 if (asprintf(&proc, "/proc/self/fd/%d", fd1) == -1) 582 int err = bind_mount_by_fd(fd, fd);
555 errExit("asprintf"); 583 EUID_USER();
556 if (mount(proc, proc, NULL, MS_BIND|MS_REC, NULL) < 0) 584 if (err) {
557 errExit("mount"); 585 close(fd);
558 free(proc); 586 goto out;
587 }
559 588
560 // mount --bind -o remount,ro path 589 // remount the mount point
561 // need to open path again without following symbolic links 590 // need to open path again
562 int fd2 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); 591 int fd2 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
563 if (fd2 == -1) 592 close(fd); // earliest timepoint to close fd
564 errExit("open"); 593 if (fd2 < 0)
594 goto out;
595
596 // device and inode number should be the same
565 struct stat s2; 597 struct stat s2;
566 if (fstat(fd2, &s2) == -1) 598 if (fstat(fd2, &s2) < 0)
567 errExit("fstat"); 599 errExit("fstat");
568 // device and inode number should be the same 600 if (s.st_dev != s2.st_dev || s.st_ino != s2.st_ino)
569 if (s1.st_dev != s2.st_dev || s1.st_ino != s2.st_ino)
570 errLogExit("invalid %s mount", opstr[op]); 601 errLogExit("invalid %s mount", opstr[op]);
571 if (asprintf(&proc, "/proc/self/fd/%d", fd2) == -1) 602
572 errExit("asprintf"); 603 EUID_ROOT();
573 if (mount(NULL, proc, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0) 604 err = remount_by_fd(fd2, flags);
574 errExit("mount"); 605 EUID_USER();
606 close(fd2);
607 if (err)
608 goto out;
575 609
576 // run a sanity check on /proc/self/mountinfo and confirm that target of the last 610 // run a sanity check on /proc/self/mountinfo and confirm that target of the last
577 // mount operation was path; if there are other mount points contained inside path, 611 // mount operation was path; if there are other mount points contained inside path,
@@ -582,10 +616,8 @@ static void fs_remount_simple(const char *path, OPERATION op) {
582 (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/')) 616 (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/'))
583 && strcmp(path, "/") != 0) // support read-only=/ 617 && strcmp(path, "/") != 0) // support read-only=/
584 errLogExit("invalid %s mount", opstr[op]); 618 errLogExit("invalid %s mount", opstr[op]);
619
585 fs_logger2(opstr[op], path); 620 fs_logger2(opstr[op], path);
586 free(proc);
587 close(fd1);
588 close(fd2);
589 return; 621 return;
590 622
591out: 623out:
@@ -594,7 +626,9 @@ out:
594 626
595// remount recursively; requires a resolved path 627// remount recursively; requires a resolved path
596static void fs_remount_rec(const char *dir, OPERATION op) { 628static void fs_remount_rec(const char *dir, OPERATION op) {
629 EUID_ASSERT();
597 assert(dir); 630 assert(dir);
631
598 struct stat s; 632 struct stat s;
599 if (stat(dir, &s) != 0) 633 if (stat(dir, &s) != 0)
600 return; 634 return;
@@ -632,6 +666,14 @@ static void fs_remount_rec(const char *dir, OPERATION op) {
632// resolve a path and remount it 666// resolve a path and remount it
633void fs_remount(const char *path, OPERATION op, int rec) { 667void fs_remount(const char *path, OPERATION op, int rec) {
634 assert(path); 668 assert(path);
669
670 int called_as_root = 0;
671 if (geteuid() == 0)
672 called_as_root = 1;
673
674 if (called_as_root)
675 EUID_USER();
676
635 char *rpath = realpath(path, NULL); 677 char *rpath = realpath(path, NULL);
636 if (rpath) { 678 if (rpath) {
637 if (rec) 679 if (rec)
@@ -640,10 +682,14 @@ void fs_remount(const char *path, OPERATION op, int rec) {
640 fs_remount_simple(rpath, op); 682 fs_remount_simple(rpath, op);
641 free(rpath); 683 free(rpath);
642 } 684 }
685
686 if (called_as_root)
687 EUID_ROOT();
643} 688}
644 689
645// Disable /mnt, /media, /run/mount and /run/media access 690// Disable /mnt, /media, /run/mount and /run/media access
646void fs_mnt(const int enforce) { 691void fs_mnt(const int enforce) {
692 EUID_USER();
647 if (enforce) { 693 if (enforce) {
648 // disable-mnt set in firejail.config 694 // disable-mnt set in firejail.config
649 // overriding with noblacklist is not possible in this case 695 // overriding with noblacklist is not possible in this case
@@ -653,13 +699,12 @@ void fs_mnt(const int enforce) {
653 disable_file(BLACKLIST_FILE, "/run/media"); 699 disable_file(BLACKLIST_FILE, "/run/media");
654 } 700 }
655 else { 701 else {
656 EUID_USER();
657 profile_add("blacklist /mnt"); 702 profile_add("blacklist /mnt");
658 profile_add("blacklist /media"); 703 profile_add("blacklist /media");
659 profile_add("blacklist /run/mount"); 704 profile_add("blacklist /run/mount");
660 profile_add("blacklist /run/media"); 705 profile_add("blacklist /run/media");
661 EUID_ROOT();
662 } 706 }
707 EUID_ROOT();
663} 708}
664 709
665 710
@@ -674,7 +719,6 @@ void fs_proc_sys_dev_boot(void) {
674 errExit("mounting /proc/sys"); 719 errExit("mounting /proc/sys");
675 fs_logger("read-only /proc/sys"); 720 fs_logger("read-only /proc/sys");
676 721
677
678 /* Mount a version of /sys that describes the network namespace */ 722 /* Mount a version of /sys that describes the network namespace */
679 if (arg_debug) 723 if (arg_debug)
680 printf("Remounting /sys directory\n"); 724 printf("Remounting /sys directory\n");
@@ -689,13 +733,13 @@ void fs_proc_sys_dev_boot(void) {
689 else 733 else
690 fs_logger("remount /sys"); 734 fs_logger("remount /sys");
691 735
736 EUID_USER();
737
692 disable_file(BLACKLIST_FILE, "/sys/firmware"); 738 disable_file(BLACKLIST_FILE, "/sys/firmware");
693 disable_file(BLACKLIST_FILE, "/sys/hypervisor"); 739 disable_file(BLACKLIST_FILE, "/sys/hypervisor");
694 { // allow user access to some directories in /sys/ by specifying 'noblacklist' option 740 { // allow user access to some directories in /sys/ by specifying 'noblacklist' option
695 EUID_USER();
696 profile_add("blacklist /sys/fs"); 741 profile_add("blacklist /sys/fs");
697 profile_add("blacklist /sys/module"); 742 profile_add("blacklist /sys/module");
698 EUID_ROOT();
699 } 743 }
700 disable_file(BLACKLIST_FILE, "/sys/power"); 744 disable_file(BLACKLIST_FILE, "/sys/power");
701 disable_file(BLACKLIST_FILE, "/sys/kernel/debug"); 745 disable_file(BLACKLIST_FILE, "/sys/kernel/debug");
@@ -739,12 +783,8 @@ void fs_proc_sys_dev_boot(void) {
739 // disable /dev/port 783 // disable /dev/port
740 disable_file(BLACKLIST_FILE, "/dev/port"); 784 disable_file(BLACKLIST_FILE, "/dev/port");
741 785
742
743
744 // disable various ipc sockets in /run/user 786 // disable various ipc sockets in /run/user
745 if (!arg_writable_run_user) { 787 if (!arg_writable_run_user) {
746 struct stat s;
747
748 char *fname; 788 char *fname;
749 if (asprintf(&fname, "/run/user/%d", getuid()) == -1) 789 if (asprintf(&fname, "/run/user/%d", getuid()) == -1)
750 errExit("asprintf"); 790 errExit("asprintf");
@@ -755,8 +795,7 @@ void fs_proc_sys_dev_boot(void) {
755 errExit("asprintf"); 795 errExit("asprintf");
756 if (create_empty_dir_as_user(fnamegpg, 0700)) 796 if (create_empty_dir_as_user(fnamegpg, 0700))
757 fs_logger2("create", fnamegpg); 797 fs_logger2("create", fnamegpg);
758 if (stat(fnamegpg, &s) == 0) 798 disable_file(BLACKLIST_FILE, fnamegpg);
759 disable_file(BLACKLIST_FILE, fnamegpg);
760 free(fnamegpg); 799 free(fnamegpg);
761 800
762 // disable /run/user/{uid}/systemd 801 // disable /run/user/{uid}/systemd
@@ -765,8 +804,7 @@ void fs_proc_sys_dev_boot(void) {
765 errExit("asprintf"); 804 errExit("asprintf");
766 if (create_empty_dir_as_user(fnamesysd, 0755)) 805 if (create_empty_dir_as_user(fnamesysd, 0755))
767 fs_logger2("create", fnamesysd); 806 fs_logger2("create", fnamesysd);
768 if (stat(fnamesysd, &s) == 0) 807 disable_file(BLACKLIST_FILE, fnamesysd);
769 disable_file(BLACKLIST_FILE, fnamesysd);
770 free(fnamesysd); 808 free(fnamesysd);
771 } 809 }
772 free(fname); 810 free(fname);
@@ -777,35 +815,30 @@ void fs_proc_sys_dev_boot(void) {
777 disable_file(BLACKLIST_FILE, "/dev/kmsg"); 815 disable_file(BLACKLIST_FILE, "/dev/kmsg");
778 disable_file(BLACKLIST_FILE, "/proc/kmsg"); 816 disable_file(BLACKLIST_FILE, "/proc/kmsg");
779 } 817 }
818
819 EUID_ROOT();
780} 820}
781 821
782// disable firejail configuration in ~/.config/firejail 822// disable firejail configuration in ~/.config/firejail
783void disable_config(void) { 823void disable_config(void) {
784 struct stat s; 824 EUID_USER();
785
786 char *fname; 825 char *fname;
787 if (asprintf(&fname, "%s/.config/firejail", cfg.homedir) == -1) 826 if (asprintf(&fname, "%s/.config/firejail", cfg.homedir) == -1)
788 errExit("asprintf"); 827 errExit("asprintf");
789 if (stat(fname, &s) == 0) 828 disable_file(BLACKLIST_FILE, fname);
790 disable_file(BLACKLIST_FILE, fname);
791 free(fname); 829 free(fname);
792 830
793 // disable run time information 831 // disable run time information
794 if (stat(RUN_FIREJAIL_NETWORK_DIR, &s) == 0) 832 disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NETWORK_DIR);
795 disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NETWORK_DIR); 833 disable_file(BLACKLIST_FILE, RUN_FIREJAIL_BANDWIDTH_DIR);
796 if (stat(RUN_FIREJAIL_BANDWIDTH_DIR, &s) == 0) 834 disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NAME_DIR);
797 disable_file(BLACKLIST_FILE, RUN_FIREJAIL_BANDWIDTH_DIR); 835 disable_file(BLACKLIST_FILE, RUN_FIREJAIL_PROFILE_DIR);
798 if (stat(RUN_FIREJAIL_NAME_DIR, &s) == 0) 836 disable_file(BLACKLIST_FILE, RUN_FIREJAIL_X11_DIR);
799 disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NAME_DIR); 837 EUID_ROOT();
800 if (stat(RUN_FIREJAIL_PROFILE_DIR, &s) == 0)
801 disable_file(BLACKLIST_FILE, RUN_FIREJAIL_PROFILE_DIR);
802 if (stat(RUN_FIREJAIL_X11_DIR, &s) == 0)
803 disable_file(BLACKLIST_FILE, RUN_FIREJAIL_X11_DIR);
804} 838}
805 839
806 840
807// build a basic read-only filesystem 841// build a basic read-only filesystem
808// top level directories could be links, run no after-mount checks
809void fs_basic_fs(void) { 842void fs_basic_fs(void) {
810 uid_t uid = getuid(); 843 uid_t uid = getuid();
811 844
@@ -815,6 +848,7 @@ void fs_basic_fs(void) {
815 if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) 848 if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0)
816 errExit("mounting /proc"); 849 errExit("mounting /proc");
817 850
851 EUID_USER();
818 if (arg_debug) 852 if (arg_debug)
819 printf("Basic read-only filesystem:\n"); 853 printf("Basic read-only filesystem:\n");
820 if (!arg_writable_etc) { 854 if (!arg_writable_etc) {
@@ -834,6 +868,7 @@ void fs_basic_fs(void) {
834 fs_remount("/lib64", MOUNT_READONLY, 1); 868 fs_remount("/lib64", MOUNT_READONLY, 1);
835 fs_remount("/lib32", MOUNT_READONLY, 1); 869 fs_remount("/lib32", MOUNT_READONLY, 1);
836 fs_remount("/libx32", MOUNT_READONLY, 1); 870 fs_remount("/libx32", MOUNT_READONLY, 1);
871 EUID_ROOT();
837 872
838 // update /var directory in order to support multiple sandboxes running on the same root directory 873 // update /var directory in order to support multiple sandboxes running on the same root directory
839 fs_var_lock(); 874 fs_var_lock();
@@ -862,6 +897,7 @@ void fs_basic_fs(void) {
862#ifdef HAVE_OVERLAYFS 897#ifdef HAVE_OVERLAYFS
863char *fs_check_overlay_dir(const char *subdirname, int allow_reuse) { 898char *fs_check_overlay_dir(const char *subdirname, int allow_reuse) {
864 assert(subdirname); 899 assert(subdirname);
900 EUID_ASSERT();
865 struct stat s; 901 struct stat s;
866 char *dirname; 902 char *dirname;
867 903
@@ -1221,6 +1257,7 @@ void fs_overlayfs(void) {
1221 1257
1222// this function is called from sandbox.c before blacklist/whitelist functions 1258// this function is called from sandbox.c before blacklist/whitelist functions
1223void fs_private_tmp(void) { 1259void fs_private_tmp(void) {
1260 EUID_ASSERT();
1224 if (arg_debug) 1261 if (arg_debug)
1225 printf("Generate private-tmp whitelist commands\n"); 1262 printf("Generate private-tmp whitelist commands\n");
1226 1263
@@ -1241,8 +1278,8 @@ void fs_private_tmp(void) {
1241 1278
1242 // whitelist x11 directory 1279 // whitelist x11 directory
1243 profile_add("whitelist /tmp/.X11-unix"); 1280 profile_add("whitelist /tmp/.X11-unix");
1244 // read-only x11 directory 1281 // read-only x11 directory
1245 profile_add("read-only /tmp/.X11-unix"); 1282 profile_add("read-only /tmp/.X11-unix");
1246 1283
1247 // whitelist any pulse* file in /tmp directory 1284 // whitelist any pulse* file in /tmp directory
1248 // some distros use PulseAudio sockets under /tmp instead of the socket in /urn/user 1285 // some distros use PulseAudio sockets under /tmp instead of the socket in /urn/user
diff --git a/src/firejail/fs_dev.c b/src/firejail/fs_dev.c
index 8c2870a4d..8cc3ecc62 100644
--- a/src/firejail/fs_dev.c
+++ b/src/firejail/fs_dev.c
@@ -187,8 +187,10 @@ static void mount_dev_shm(void) {
187static void process_dev_shm(void) { 187static void process_dev_shm(void) {
188 // Jack audio keeps an Unix socket under (/dev/shm/jack_default_1000_0 or /dev/shm/jack/...) 188 // Jack audio keeps an Unix socket under (/dev/shm/jack_default_1000_0 or /dev/shm/jack/...)
189 // looking for jack socket 189 // looking for jack socket
190 EUID_USER();
190 glob_t globbuf; 191 glob_t globbuf;
191 int globerr = glob(RUN_DEV_DIR "/shm/jack*", GLOB_NOSORT, NULL, &globbuf); 192 int globerr = glob(RUN_DEV_DIR "/shm/jack*", GLOB_NOSORT, NULL, &globbuf);
193 EUID_ROOT();
192 if (globerr && !arg_keep_dev_shm) { 194 if (globerr && !arg_keep_dev_shm) {
193 empty_dev_shm(); 195 empty_dev_shm();
194 return; 196 return;
diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c
index 4bcefa443..eab952eb8 100644
--- a/src/firejail/fs_home.c
+++ b/src/firejail/fs_home.c
@@ -42,15 +42,14 @@ static void skel(const char *homedir, uid_t u, gid_t g) {
42 // copy skel files 42 // copy skel files
43 if (asprintf(&fname, "%s/.zshrc", homedir) == -1) 43 if (asprintf(&fname, "%s/.zshrc", homedir) == -1)
44 errExit("asprintf"); 44 errExit("asprintf");
45 struct stat s;
46 // don't copy it if we already have the file 45 // don't copy it if we already have the file
47 if (stat(fname, &s) == 0) 46 if (access(fname, F_OK) == 0)
48 return; 47 return;
49 if (is_link(fname)) { // stat on dangling symlinks fails, try again using lstat 48 if (is_link(fname)) { // access(3) on dangling symlinks fails, try again using lstat
50 fprintf(stderr, "Error: invalid %s file\n", fname); 49 fprintf(stderr, "Error: invalid %s file\n", fname);
51 exit(1); 50 exit(1);
52 } 51 }
53 if (stat("/etc/skel/.zshrc", &s) == 0) { 52 if (access("/etc/skel/.zshrc", R_OK) == 0) {
54 copy_file_as_user("/etc/skel/.zshrc", fname, u, g, 0644); // regular user 53 copy_file_as_user("/etc/skel/.zshrc", fname, u, g, 0644); // regular user
55 fs_logger("clone /etc/skel/.zshrc"); 54 fs_logger("clone /etc/skel/.zshrc");
56 fs_logger2("clone", fname); 55 fs_logger2("clone", fname);
@@ -67,16 +66,14 @@ static void skel(const char *homedir, uid_t u, gid_t g) {
67 // copy skel files 66 // copy skel files
68 if (asprintf(&fname, "%s/.cshrc", homedir) == -1) 67 if (asprintf(&fname, "%s/.cshrc", homedir) == -1)
69 errExit("asprintf"); 68 errExit("asprintf");
70 struct stat s;
71
72 // don't copy it if we already have the file 69 // don't copy it if we already have the file
73 if (stat(fname, &s) == 0) 70 if (access(fname, F_OK) == 0)
74 return; 71 return;
75 if (is_link(fname)) { // stat on dangling symlinks fails, try again using lstat 72 if (is_link(fname)) { // access(3) on dangling symlinks fails, try again using lstat
76 fprintf(stderr, "Error: invalid %s file\n", fname); 73 fprintf(stderr, "Error: invalid %s file\n", fname);
77 exit(1); 74 exit(1);
78 } 75 }
79 if (stat("/etc/skel/.cshrc", &s) == 0) { 76 if (access("/etc/skel/.cshrc", R_OK) == 0) {
80 copy_file_as_user("/etc/skel/.cshrc", fname, u, g, 0644); // regular user 77 copy_file_as_user("/etc/skel/.cshrc", fname, u, g, 0644); // regular user
81 fs_logger("clone /etc/skel/.cshrc"); 78 fs_logger("clone /etc/skel/.cshrc");
82 fs_logger2("clone", fname); 79 fs_logger2("clone", fname);
@@ -93,15 +90,14 @@ static void skel(const char *homedir, uid_t u, gid_t g) {
93 // copy skel files 90 // copy skel files
94 if (asprintf(&fname, "%s/.bashrc", homedir) == -1) 91 if (asprintf(&fname, "%s/.bashrc", homedir) == -1)
95 errExit("asprintf"); 92 errExit("asprintf");
96 struct stat s;
97 // don't copy it if we already have the file 93 // don't copy it if we already have the file
98 if (stat(fname, &s) == 0) 94 if (access(fname, F_OK) == 0)
99 return; 95 return;
100 if (is_link(fname)) { // stat on dangling symlinks fails, try again using lstat 96 if (is_link(fname)) { // access(3) on dangling symlinks fails, try again using lstat
101 fprintf(stderr, "Error: invalid %s file\n", fname); 97 fprintf(stderr, "Error: invalid %s file\n", fname);
102 exit(1); 98 exit(1);
103 } 99 }
104 if (stat("/etc/skel/.bashrc", &s) == 0) { 100 if (access("/etc/skel/.bashrc", R_OK) == 0) {
105 copy_file_as_user("/etc/skel/.bashrc", fname, u, g, 0644); // regular user 101 copy_file_as_user("/etc/skel/.bashrc", fname, u, g, 0644); // regular user
106 fs_logger("clone /etc/skel/.bashrc"); 102 fs_logger("clone /etc/skel/.bashrc");
107 fs_logger2("clone", fname); 103 fs_logger2("clone", fname);
@@ -122,8 +118,8 @@ static int store_xauthority(void) {
122 errExit("asprintf"); 118 errExit("asprintf");
123 119
124 struct stat s; 120 struct stat s;
125 if (stat(src, &s) == 0) { 121 if (lstat_as_user(src, &s) == 0) {
126 if (is_link(src)) { 122 if (S_ISLNK(s.st_mode)) {
127 fwarning("invalid .Xauthority file\n"); 123 fwarning("invalid .Xauthority file\n");
128 free(src); 124 free(src);
129 return 0; 125 return 0;
@@ -161,11 +157,11 @@ static int store_asoundrc(void) {
161 errExit("asprintf"); 157 errExit("asprintf");
162 158
163 struct stat s; 159 struct stat s;
164 if (stat(src, &s) == 0) { 160 if (lstat_as_user(src, &s) == 0) {
165 if (is_link(src)) { 161 if (S_ISLNK(s.st_mode)) {
166 // make sure the real path of the file is inside the home directory 162 // make sure the real path of the file is inside the home directory
167 /* coverity[toctou] */ 163 /* coverity[toctou] */
168 char* rp = realpath(src, NULL); 164 char *rp = realpath_as_user(src);
169 if (!rp) { 165 if (!rp) {
170 fprintf(stderr, "Error: Cannot access %s\n", src); 166 fprintf(stderr, "Error: Cannot access %s\n", src);
171 exit(1); 167 exit(1);
@@ -234,6 +230,7 @@ static void copy_asoundrc(void) {
234 } 230 }
235 231
236 copy_file_as_user(src, dest, getuid(), getgid(), S_IRUSR | S_IWUSR); // regular user 232 copy_file_as_user(src, dest, getuid(), getgid(), S_IRUSR | S_IWUSR); // regular user
233 selinux_relabel_path(dest, src);
237 fs_logger2("clone", dest); 234 fs_logger2("clone", dest);
238 free(dest); 235 free(dest);
239 236
@@ -262,6 +259,7 @@ void fs_private_homedir(void) {
262 if (arg_debug) 259 if (arg_debug)
263 printf("Mount-bind %s on top of %s\n", private_homedir, homedir); 260 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 261 // get file descriptors for homedir and private_homedir, fails if there is any symlink
262 EUID_USER();
265 int src = safer_openat(-1, private_homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 263 int src = safer_openat(-1, private_homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
266 if (src == -1) 264 if (src == -1)
267 errExit("opening private directory"); 265 errExit("opening private directory");
@@ -286,17 +284,10 @@ void fs_private_homedir(void) {
286 exit(1); 284 exit(1);
287 } 285 }
288 // mount via the links in /proc/self/fd 286 // mount via the links in /proc/self/fd
289 char *proc_src, *proc_dst; 287 EUID_ROOT();
290 if (asprintf(&proc_src, "/proc/self/fd/%d", src) == -1) 288 if (bind_mount_by_fd(src, dst))
291 errExit("asprintf");
292 if (asprintf(&proc_dst, "/proc/self/fd/%d", dst) == -1)
293 errExit("asprintf");
294 if (mount(proc_src, proc_dst, NULL, MS_NOSUID | MS_NODEV | MS_BIND | MS_REC, NULL) < 0)
295 errExit("mount bind"); 289 errExit("mount bind");
296 free(proc_src); 290
297 free(proc_dst);
298 close(src);
299 close(dst);
300 // check /proc/self/mountinfo to confirm the mount is ok 291 // check /proc/self/mountinfo to confirm the mount is ok
301 MountData *mptr = get_last_mount(); 292 MountData *mptr = get_last_mount();
302 size_t len = strlen(homedir); 293 size_t len = strlen(homedir);
@@ -304,6 +295,8 @@ void fs_private_homedir(void) {
304 (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/')) 295 (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/'))
305 errLogExit("invalid private mount"); 296 errLogExit("invalid private mount");
306 297
298 close(src);
299 close(dst);
307 fs_logger3("mount-bind", private_homedir, homedir); 300 fs_logger3("mount-bind", private_homedir, homedir);
308 fs_logger2("whitelist", homedir); 301 fs_logger2("whitelist", homedir);
309// preserve mode and ownership 302// preserve mode and ownership
@@ -438,6 +431,7 @@ void fs_check_private_cwd(const char *dir) {
438// --private-home 431// --private-home
439//*********************************************************************************** 432//***********************************************************************************
440static char *check_dir_or_file(const char *name) { 433static char *check_dir_or_file(const char *name) {
434 EUID_ASSERT();
441 assert(name); 435 assert(name);
442 436
443 // basic checks 437 // basic checks
@@ -498,6 +492,7 @@ errexit:
498} 492}
499 493
500static void duplicate(char *name) { 494static void duplicate(char *name) {
495 EUID_ASSERT();
501 char *fname = check_dir_or_file(name); 496 char *fname = check_dir_or_file(name);
502 497
503 if (arg_debug) 498 if (arg_debug)
@@ -553,10 +548,10 @@ void fs_private_home_list(void) {
553 selinux_relabel_path(RUN_HOME_DIR, homedir); 548 selinux_relabel_path(RUN_HOME_DIR, homedir);
554 fs_logger_print(); // save the current log 549 fs_logger_print(); // save the current log
555 550
551 // copy the list of files in the new home directory
552 EUID_USER();
556 if (arg_debug) 553 if (arg_debug)
557 printf("Copying files in the new home:\n"); 554 printf("Copying files in the new home:\n");
558
559 // copy the list of files in the new home directory
560 char *dlist = strdup(cfg.home_private_keep); 555 char *dlist = strdup(cfg.home_private_keep);
561 if (!dlist) 556 if (!dlist)
562 errExit("strdup"); 557 errExit("strdup");
@@ -589,13 +584,11 @@ void fs_private_home_list(void) {
589 exit(1); 584 exit(1);
590 } 585 }
591 // mount using the file descriptor 586 // mount using the file descriptor
592 char *proc; 587 EUID_ROOT();
593 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 588 if (bind_mount_path_to_fd(RUN_HOME_DIR, fd))
594 errExit("asprintf");
595 if (mount(RUN_HOME_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0)
596 errExit("mount bind"); 589 errExit("mount bind");
597 free(proc);
598 close(fd); 590 close(fd);
591
599 // check /proc/self/mountinfo to confirm the mount is ok 592 // check /proc/self/mountinfo to confirm the mount is ok
600 MountData *mptr = get_last_mount(); 593 MountData *mptr = get_last_mount();
601 if (strcmp(mptr->dir, homedir) != 0 || strcmp(mptr->fstype, "tmpfs") != 0) 594 if (strcmp(mptr->dir, homedir) != 0 || strcmp(mptr->fstype, "tmpfs") != 0)
diff --git a/src/firejail/fs_lib.c b/src/firejail/fs_lib.c
index 5df356d04..9d7a17cf3 100644
--- a/src/firejail/fs_lib.c
+++ b/src/firejail/fs_lib.c
@@ -178,8 +178,7 @@ void fslib_mount(const char *full_path) {
178 178
179 if (*full_path == '\0' || 179 if (*full_path == '\0' ||
180 !valid_full_path(full_path) || 180 !valid_full_path(full_path) ||
181 access(full_path, F_OK) != 0 || 181 stat_as_user(full_path, &s) != 0 ||
182 stat(full_path, &s) != 0 ||
183 s.st_uid != 0) 182 s.st_uid != 0)
184 return; 183 return;
185 184
@@ -203,7 +202,7 @@ void fslib_mount_libs(const char *full_path, unsigned user) {
203 } 202 }
204 203
205 if (arg_debug || arg_debug_private_lib) 204 if (arg_debug || arg_debug_private_lib)
206 printf(" fslib_mount_libs %s (parse as %s)\n", full_path, user ? "user" : "root"); 205 printf(" fslib_mount_libs %s\n", full_path);
207 // create an empty RUN_LIB_FILE and allow the user to write to it 206 // create an empty RUN_LIB_FILE and allow the user to write to it
208 unlink(RUN_LIB_FILE); // in case is there 207 unlink(RUN_LIB_FILE); // in case is there
209 create_empty_file_as_root(RUN_LIB_FILE, 0644); 208 create_empty_file_as_root(RUN_LIB_FILE, 0644);
@@ -212,7 +211,7 @@ void fslib_mount_libs(const char *full_path, unsigned user) {
212 211
213 // run fldd to extract the list of files 212 // run fldd to extract the list of files
214 if (arg_debug || arg_debug_private_lib) 213 if (arg_debug || arg_debug_private_lib)
215 printf(" running fldd %s\n", full_path); 214 printf(" running fldd %s as %s\n", full_path, user ? "user" : "root");
216 unsigned mask; 215 unsigned mask;
217 if (user) 216 if (user)
218 mask = SBOX_USER; 217 mask = SBOX_USER;
@@ -246,7 +245,7 @@ static void load_library(const char *fname) {
246 245
247 // existing file owned by root 246 // existing file owned by root
248 struct stat s; 247 struct stat s;
249 if (!access(fname, F_OK) && stat(fname, &s) == 0 && s.st_uid == 0) { 248 if (stat_as_user(fname, &s) == 0 && s.st_uid == 0) {
250 // load directories, regular 64 bit libraries, and 64 bit executables 249 // load directories, regular 64 bit libraries, and 64 bit executables
251 if (S_ISDIR(s.st_mode)) 250 if (S_ISDIR(s.st_mode))
252 fslib_mount(fname); 251 fslib_mount(fname);
@@ -286,19 +285,21 @@ static void install_list_entry(const char *lib) {
286#define DO_GLOBBING 285#define DO_GLOBBING
287#ifdef DO_GLOBBING 286#ifdef DO_GLOBBING
288 // globbing 287 // globbing
288 EUID_USER();
289 glob_t globbuf; 289 glob_t globbuf;
290 int globerr = glob(fname, GLOB_NOCHECK | GLOB_NOSORT | GLOB_PERIOD, NULL, &globbuf); 290 int globerr = glob(fname, GLOB_NOCHECK | GLOB_NOSORT | GLOB_PERIOD, NULL, &globbuf);
291 if (globerr) { 291 if (globerr) {
292 fprintf(stderr, "Error: failed to glob private-lib pattern %s\n", fname); 292 fprintf(stderr, "Error: failed to glob private-lib pattern %s\n", fname);
293 exit(1); 293 exit(1);
294 } 294 }
295 EUID_ROOT();
295 size_t j; 296 size_t j;
296 for (j = 0; j < globbuf.gl_pathc; j++) { 297 for (j = 0; j < globbuf.gl_pathc; j++) {
297 assert(globbuf.gl_pathv[j]); 298 assert(globbuf.gl_pathv[j]);
298//printf("glob %s\n", globbuf.gl_pathv[j]); 299//printf("glob %s\n", globbuf.gl_pathv[j]);
299 // GLOB_NOCHECK - no pattern matched returns the original pattern; try to load it anyway 300 // GLOB_NOCHECK - no pattern matched returns the original pattern; try to load it anyway
300 301
301 // foobar/* includes foobar/. and foobar/.. 302 // foobar/* expands to foobar/. and foobar/..
302 const char *base = gnu_basename(globbuf.gl_pathv[j]); 303 const char *base = gnu_basename(globbuf.gl_pathv[j]);
303 if (strcmp(base, ".") == 0 || strcmp(base, "..") == 0) 304 if (strcmp(base, ".") == 0 || strcmp(base, "..") == 0)
304 continue; 305 continue;
diff --git a/src/firejail/fs_mkdir.c b/src/firejail/fs_mkdir.c
index 8cfeea582..bbc2aa938 100644
--- a/src/firejail/fs_mkdir.c
+++ b/src/firejail/fs_mkdir.c
@@ -25,6 +25,9 @@
25#include <sys/wait.h> 25#include <sys/wait.h>
26#include <string.h> 26#include <string.h>
27 27
28#ifdef HAVE_GCOV
29#include <gcov.h>
30#endif
28 31
29static void check(const char *fname) { 32static void check(const char *fname) {
30 // manufacture /run/user directory 33 // manufacture /run/user directory
diff --git a/src/firejail/fs_trace.c b/src/firejail/fs_trace.c
index 1fc38361e..475a391ec 100644
--- a/src/firejail/fs_trace.c
+++ b/src/firejail/fs_trace.c
@@ -71,12 +71,8 @@ void fs_tracefile(void) {
71 // mount using the symbolic link in /proc/self/fd 71 // mount using the symbolic link in /proc/self/fd
72 if (arg_debug) 72 if (arg_debug)
73 printf("Bind mount %s to %s\n", arg_tracefile, RUN_TRACE_FILE); 73 printf("Bind mount %s to %s\n", arg_tracefile, RUN_TRACE_FILE);
74 char *proc; 74 if (bind_mount_fd_to_path(fd, RUN_TRACE_FILE))
75 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
76 errExit("asprintf");
77 if (mount(proc, RUN_TRACE_FILE, NULL, MS_BIND|MS_REC, NULL) < 0)
78 errExit("mount bind " RUN_TRACE_FILE); 75 errExit("mount bind " RUN_TRACE_FILE);
79 free(proc);
80 close(fd); 76 close(fd);
81 // now that RUN_TRACE_FILE is user-writable, mount it noexec 77 // now that RUN_TRACE_FILE is user-writable, mount it noexec
82 fs_remount(RUN_TRACE_FILE, MOUNT_NOEXEC, 0); 78 fs_remount(RUN_TRACE_FILE, MOUNT_NOEXEC, 0);
diff --git a/src/firejail/fs_var.c b/src/firejail/fs_var.c
index bae3d6df0..20e262d80 100644
--- a/src/firejail/fs_var.c
+++ b/src/firejail/fs_var.c
@@ -323,4 +323,8 @@ void fs_var_utmp(void) {
323 if (mount(RUN_UTMP_FILE, UTMP_FILE, NULL, MS_BIND|MS_NOSUID|MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) 323 if (mount(RUN_UTMP_FILE, UTMP_FILE, NULL, MS_BIND|MS_NOSUID|MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0)
324 errExit("mount bind utmp"); 324 errExit("mount bind utmp");
325 fs_logger2("create", UTMP_FILE); 325 fs_logger2("create", UTMP_FILE);
326
327 // blacklist RUN_UTMP_FILE
328 if (mount(RUN_RO_FILE, RUN_UTMP_FILE, NULL, MS_BIND, "mode=400,gid=0") < 0)
329 errExit("mount bind");
326} 330}
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c
index 77bb5e5bb..370035a4d 100644
--- a/src/firejail/fs_whitelist.c
+++ b/src/firejail/fs_whitelist.c
@@ -195,15 +195,7 @@ static void whitelist_file(int dirfd, const char *relpath, const char *path) {
195 195
196 if (arg_debug || arg_debug_whitelists) 196 if (arg_debug || arg_debug_whitelists)
197 printf("Whitelisting %s\n", path); 197 printf("Whitelisting %s\n", path);
198 198 if (bind_mount_by_fd(fd, fd3))
199 // in order to make this mount resilient against symlink attacks, use
200 // magic links in /proc/self/fd instead of mounting the paths directly
201 char *proc_src, *proc_dst;
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)
205 errExit("asprintf");
206 if (mount(proc_src, proc_dst, NULL, MS_BIND | MS_REC, NULL) < 0)
207 errExit("mount bind"); 199 errExit("mount bind");
208 // check the last mount operation 200 // check the last mount operation
209 MountData *mptr = get_last_mount(); // will do exit(1) if the mount cannot be found 201 MountData *mptr = get_last_mount(); // will do exit(1) if the mount cannot be found
@@ -221,8 +213,6 @@ static void whitelist_file(int dirfd, const char *relpath, const char *path) {
221 // - there should be more than one '/' char in dest string 213 // - there should be more than one '/' char in dest string
222 if (mptr->dir == strrchr(mptr->dir, '/')) 214 if (mptr->dir == strrchr(mptr->dir, '/'))
223 errLogExit("invalid whitelist mount"); 215 errLogExit("invalid whitelist mount");
224 free(proc_src);
225 free(proc_dst);
226 close(fd); 216 close(fd);
227 close(fd3); 217 close(fd3);
228 fs_logger2("whitelist", path); 218 fs_logger2("whitelist", path);
@@ -267,6 +257,7 @@ static void whitelist_symlink(const char *link, const char *target) {
267} 257}
268 258
269static void globbing(const char *pattern) { 259static void globbing(const char *pattern) {
260 EUID_ASSERT();
270 assert(pattern); 261 assert(pattern);
271 262
272 // globbing 263 // globbing
@@ -304,7 +295,6 @@ static void globbing(const char *pattern) {
304} 295}
305 296
306// mount tmpfs on all top level directories 297// mount tmpfs on all top level directories
307// home directories *inside* /run/user/$UID are not fully supported
308static void tmpfs_topdirs(const TopDir *topdirs) { 298static void tmpfs_topdirs(const TopDir *topdirs) {
309 int tmpfs_home = 0; 299 int tmpfs_home = 0;
310 int tmpfs_runuser = 0; 300 int tmpfs_runuser = 0;
@@ -335,18 +325,15 @@ static void tmpfs_topdirs(const TopDir *topdirs) {
335 325
336 // mount tmpfs 326 // mount tmpfs
337 fs_tmpfs(topdirs[i].path, 0); 327 fs_tmpfs(topdirs[i].path, 0);
328 selinux_relabel_path(topdirs[i].path, topdirs[i].path);
338 329
339 // init tmpfs 330 // init tmpfs
340 if (strcmp(topdirs[i].path, "/run") == 0) { 331 if (strcmp(topdirs[i].path, "/run") == 0) {
341 // restore /run/firejail directory 332 // restore /run/firejail directory
342 if (mkdir(RUN_FIREJAIL_DIR, 0755) == -1) 333 if (mkdir(RUN_FIREJAIL_DIR, 0755) == -1)
343 errExit("mkdir"); 334 errExit("mkdir");
344 char *proc; 335 if (bind_mount_fd_to_path(fd, RUN_FIREJAIL_DIR))
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"); 336 errExit("mount bind");
349 free(proc);
350 close(fd); 337 close(fd);
351 fs_logger2("whitelist", RUN_FIREJAIL_DIR); 338 fs_logger2("whitelist", RUN_FIREJAIL_DIR);
352 339
@@ -384,8 +371,6 @@ static void tmpfs_topdirs(const TopDir *topdirs) {
384 const char *rel = cfg.homedir + topdir_len + 1; 371 const char *rel = cfg.homedir + topdir_len + 1;
385 whitelist_file(topdirs[i].fd, rel, cfg.homedir); 372 whitelist_file(topdirs[i].fd, rel, cfg.homedir);
386 } 373 }
387
388 selinux_relabel_path(topdirs[i].path, topdirs[i].path);
389 } 374 }
390 375
391 // user home directory 376 // user home directory
@@ -423,6 +408,13 @@ static TopDir *add_topdir(const char *dir, TopDir *topdirs, const char *path) {
423 strcmp(dir, "/sys") == 0) 408 strcmp(dir, "/sys") == 0)
424 whitelist_error(path); 409 whitelist_error(path);
425 410
411 // whitelisting home directory is disabled if --private option is present
412 if (arg_private && strcmp(dir, cfg.homedir) == 0) {
413 if (arg_debug || arg_debug_whitelists)
414 printf("Debug %d: skip %s - a private home dir is configured!\n", __LINE__, path);
415 return NULL;
416 }
417
426 // do nothing if directory doesn't exist 418 // do nothing if directory doesn't exist
427 struct stat s; 419 struct stat s;
428 if (lstat(dir, &s) != 0) { 420 if (lstat(dir, &s) != 0) {
@@ -460,9 +452,9 @@ static TopDir *add_topdir(const char *dir, TopDir *topdirs, const char *path) {
460 errExit("strdup"); 452 errExit("strdup");
461 453
462 // open the directory, don't follow symbolic links 454 // open the directory, don't follow symbolic links
463 rv->fd = safer_openat(-1, rv->path, O_PATH|O_NOFOLLOW|O_DIRECTORY|O_CLOEXEC); 455 rv->fd = safer_openat(-1, dir, O_PATH|O_NOFOLLOW|O_DIRECTORY|O_CLOEXEC);
464 if (rv->fd == -1) { 456 if (rv->fd == -1) {
465 fprintf(stderr, "Error: cannot open %s\n", rv->path); 457 fprintf(stderr, "Error: cannot open %s\n", dir);
466 exit(1); 458 exit(1);
467 } 459 }
468 460
@@ -743,10 +735,11 @@ void fs_whitelist(void) {
743 } 735 }
744 736
745 // create the link if any 737 // create the link if any
746 if (link) 738 if (link) {
747 whitelist_symlink(link, file); 739 whitelist_symlink(link, file);
740 free(link);
741 }
748 742
749 free(link);
750 free(file); 743 free(file);
751 free(entry->wparam); 744 free(entry->wparam);
752 entry->wparam = NULL; 745 entry->wparam = NULL;
diff --git a/src/firejail/join.c b/src/firejail/join.c
index bab4b830f..394bbb528 100644
--- a/src/firejail/join.c
+++ b/src/firejail/join.c
@@ -147,7 +147,7 @@ static void extract_command(int argc, char **argv, int index) {
147 } 147 }
148 148
149 // build command 149 // build command
150 build_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, index); 150 build_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, index, true);
151} 151}
152 152
153static void extract_nogroups(pid_t pid) { 153static void extract_nogroups(pid_t pid) {
diff --git a/src/firejail/ls.c b/src/firejail/ls.c
index 796c42290..6ee557648 100644
--- a/src/firejail/ls.c
+++ b/src/firejail/ls.c
@@ -31,6 +31,10 @@
31//#include <stdio.h> 31//#include <stdio.h>
32//#include <stdlib.h> 32//#include <stdlib.h>
33 33
34#ifdef HAVE_GCOV
35#include <gcov.h>
36#endif
37
34// uid/gid cache 38// uid/gid cache
35static uid_t c_uid = 0; 39static uid_t c_uid = 0;
36static char *c_uid_name = NULL; 40static char *c_uid_name = NULL;
diff --git a/src/firejail/macros.c b/src/firejail/macros.c
index bcac1feb4..cd29d8f85 100644
--- a/src/firejail/macros.c
+++ b/src/firejail/macros.c
@@ -149,6 +149,7 @@ static char *resolve_xdg(const char *var) {
149 149
150// returns mallocated memory 150// returns mallocated memory
151static char *resolve_hardcoded(char *entries[]) { 151static char *resolve_hardcoded(char *entries[]) {
152 EUID_ASSERT();
152 char *fname; 153 char *fname;
153 struct stat s; 154 struct stat s;
154 155
diff --git a/src/firejail/main.c b/src/firejail/main.c
index 4afd1d6b6..b376095f1 100644
--- a/src/firejail/main.c
+++ b/src/firejail/main.c
@@ -44,6 +44,10 @@
44#define O_PATH 010000000 44#define O_PATH 010000000
45#endif 45#endif
46 46
47#ifdef HAVE_GCOV
48#include <gcov.h>
49#endif
50
47#ifdef __ia64__ 51#ifdef __ia64__
48/* clone(2) has a different interface on ia64, as it needs to know 52/* clone(2) has a different interface on ia64, as it needs to know
49 the size of the stack */ 53 the size of the stack */
@@ -259,8 +263,8 @@ static void init_cfg(int argc, char **argv) {
259 fprintf(stderr, "Error: user %s doesn't have a user directory assigned\n", cfg.username); 263 fprintf(stderr, "Error: user %s doesn't have a user directory assigned\n", cfg.username);
260 exit(1); 264 exit(1);
261 } 265 }
266 check_homedir(pw->pw_dir);
262 cfg.homedir = clean_pathname(pw->pw_dir); 267 cfg.homedir = clean_pathname(pw->pw_dir);
263 check_homedir();
264 268
265 // initialize random number generator 269 // initialize random number generator
266 sandbox_pid = getpid(); 270 sandbox_pid = getpid();
@@ -862,12 +866,11 @@ static void run_cmd_and_exit(int i, int argc, char **argv) {
862char *guess_shell(void) { 866char *guess_shell(void) {
863 const char *shell; 867 const char *shell;
864 char *retval; 868 char *retval;
865 struct stat s;
866 869
867 shell = env_get("SHELL"); 870 shell = env_get("SHELL");
868 if (shell) { 871 if (shell) {
869 invalid_filename(shell, 0); // no globbing 872 invalid_filename(shell, 0); // no globbing
870 if (!is_dir(shell) && strstr(shell, "..") == NULL && stat(shell, &s) == 0 && access(shell, X_OK) == 0 && 873 if (access(shell, X_OK) == 0 && !is_dir(shell) && strstr(shell, "..") == NULL &&
871 strcmp(shell, PATH_FIREJAIL) != 0) 874 strcmp(shell, PATH_FIREJAIL) != 0)
872 goto found; 875 goto found;
873 } 876 }
@@ -878,12 +881,15 @@ char *guess_shell(void) {
878 int i = 0; 881 int i = 0;
879 while (shells[i] != NULL) { 882 while (shells[i] != NULL) {
880 // access call checks as real UID/GID, not as effective UID/GID 883 // access call checks as real UID/GID, not as effective UID/GID
881 if (stat(shells[i], &s) == 0 && access(shells[i], X_OK) == 0) { 884 if (access(shells[i], X_OK) == 0) {
882 shell = shells[i]; 885 shell = shells[i];
883 break; 886 goto found;
884 } 887 }
885 i++; 888 i++;
886 } 889 }
890
891 return NULL;
892
887 found: 893 found:
888 retval = strdup(shell); 894 retval = strdup(shell);
889 if (!retval) 895 if (!retval)
@@ -1256,8 +1262,10 @@ int main(int argc, char **argv, char **envp) {
1256 for (i = 1; i < argc; i++) { 1262 for (i = 1; i < argc; i++) {
1257 run_cmd_and_exit(i, argc, argv); // will exit if the command is recognized 1263 run_cmd_and_exit(i, argc, argv); // will exit if the command is recognized
1258 1264
1259 if (strcmp(argv[i], "--debug") == 0 && !arg_quiet) 1265 if (strcmp(argv[i], "--debug") == 0) {
1260 arg_debug = 1; 1266 arg_debug = 1;
1267 arg_quiet = 0;
1268 }
1261 else if (strcmp(argv[i], "--debug-blacklists") == 0) 1269 else if (strcmp(argv[i], "--debug-blacklists") == 0)
1262 arg_debug_blacklists = 1; 1270 arg_debug_blacklists = 1;
1263 else if (strcmp(argv[i], "--debug-whitelists") == 0) 1271 else if (strcmp(argv[i], "--debug-whitelists") == 0)
@@ -1265,8 +1273,8 @@ int main(int argc, char **argv, char **envp) {
1265 else if (strcmp(argv[i], "--debug-private-lib") == 0) 1273 else if (strcmp(argv[i], "--debug-private-lib") == 0)
1266 arg_debug_private_lib = 1; 1274 arg_debug_private_lib = 1;
1267 else if (strcmp(argv[i], "--quiet") == 0) { 1275 else if (strcmp(argv[i], "--quiet") == 0) {
1268 arg_quiet = 1; 1276 if (!arg_debug)
1269 arg_debug = 0; 1277 arg_quiet = 1;
1270 } 1278 }
1271 else if (strcmp(argv[i], "--allow-debuggers") == 0) { 1279 else if (strcmp(argv[i], "--allow-debuggers") == 0) {
1272 // already handled 1280 // already handled
@@ -1910,8 +1918,6 @@ int main(int argc, char **argv, char **envp) {
1910 } 1918 }
1911 else if (strcmp(argv[i], "--private") == 0) { 1919 else if (strcmp(argv[i], "--private") == 0) {
1912 arg_private = 1; 1920 arg_private = 1;
1913 // disable whitelisting in home directory
1914 profile_add("whitelist ~/*");
1915 } 1921 }
1916 else if (strncmp(argv[i], "--private=", 10) == 0) { 1922 else if (strncmp(argv[i], "--private=", 10) == 0) {
1917 if (cfg.home_private_keep) { 1923 if (cfg.home_private_keep) {
@@ -1933,8 +1939,6 @@ int main(int argc, char **argv, char **envp) {
1933 cfg.home_private = NULL; 1939 cfg.home_private = NULL;
1934 } 1940 }
1935 arg_private = 1; 1941 arg_private = 1;
1936 // disable whitelisting in home directory
1937 profile_add("whitelist ~/*");
1938 } 1942 }
1939#ifdef HAVE_PRIVATE_HOME 1943#ifdef HAVE_PRIVATE_HOME
1940 else if (strncmp(argv[i], "--private-home=", 15) == 0) { 1944 else if (strncmp(argv[i], "--private-home=", 15) == 0) {
@@ -1967,61 +1971,77 @@ int main(int argc, char **argv, char **envp) {
1967 arg_keep_dev_shm = 1; 1971 arg_keep_dev_shm = 1;
1968 } 1972 }
1969 else if (strncmp(argv[i], "--private-etc=", 14) == 0) { 1973 else if (strncmp(argv[i], "--private-etc=", 14) == 0) {
1970 if (arg_writable_etc) { 1974 if (checkcfg(CFG_PRIVATE_ETC)) {
1971 fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n"); 1975 if (arg_writable_etc) {
1972 exit(1); 1976 fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n");
1973 } 1977 exit(1);
1978 }
1974 1979
1975 // extract private etc list 1980 // extract private etc list
1976 if (*(argv[i] + 14) == '\0') { 1981 if (*(argv[i] + 14) == '\0') {
1977 fprintf(stderr, "Error: invalid private-etc option\n"); 1982 fprintf(stderr, "Error: invalid private-etc option\n");
1978 exit(1); 1983 exit(1);
1984 }
1985 if (cfg.etc_private_keep) {
1986 if ( asprintf(&cfg.etc_private_keep, "%s,%s", cfg.etc_private_keep, argv[i] + 14) < 0 )
1987 errExit("asprintf");
1988 } else
1989 cfg.etc_private_keep = argv[i] + 14;
1990 arg_private_etc = 1;
1979 } 1991 }
1980 if (cfg.etc_private_keep) { 1992 else
1981 if ( asprintf(&cfg.etc_private_keep, "%s,%s", cfg.etc_private_keep, argv[i] + 14) < 0 ) 1993 exit_err_feature("private-etc");
1982 errExit("asprintf");
1983 } else
1984 cfg.etc_private_keep = argv[i] + 14;
1985 arg_private_etc = 1;
1986 } 1994 }
1987 else if (strncmp(argv[i], "--private-opt=", 14) == 0) { 1995 else if (strncmp(argv[i], "--private-opt=", 14) == 0) {
1988 // extract private opt list 1996 if (checkcfg(CFG_PRIVATE_OPT)) {
1989 if (*(argv[i] + 14) == '\0') { 1997 // extract private opt list
1990 fprintf(stderr, "Error: invalid private-opt option\n"); 1998 if (*(argv[i] + 14) == '\0') {
1991 exit(1); 1999 fprintf(stderr, "Error: invalid private-opt option\n");
2000 exit(1);
2001 }
2002 if (cfg.opt_private_keep) {
2003 if ( asprintf(&cfg.opt_private_keep, "%s,%s", cfg.opt_private_keep, argv[i] + 14) < 0 )
2004 errExit("asprintf");
2005 } else
2006 cfg.opt_private_keep = argv[i] + 14;
2007 arg_private_opt = 1;
1992 } 2008 }
1993 if (cfg.opt_private_keep) { 2009 else
1994 if ( asprintf(&cfg.opt_private_keep, "%s,%s", cfg.opt_private_keep, argv[i] + 14) < 0 ) 2010 exit_err_feature("private-opt");
1995 errExit("asprintf");
1996 } else
1997 cfg.opt_private_keep = argv[i] + 14;
1998 arg_private_opt = 1;
1999 } 2011 }
2000 else if (strncmp(argv[i], "--private-srv=", 14) == 0) { 2012 else if (strncmp(argv[i], "--private-srv=", 14) == 0) {
2001 // extract private srv list 2013 if (checkcfg(CFG_PRIVATE_SRV)) {
2002 if (*(argv[i] + 14) == '\0') { 2014 // extract private srv list
2003 fprintf(stderr, "Error: invalid private-srv option\n"); 2015 if (*(argv[i] + 14) == '\0') {
2004 exit(1); 2016 fprintf(stderr, "Error: invalid private-srv option\n");
2017 exit(1);
2018 }
2019 if (cfg.srv_private_keep) {
2020 if ( asprintf(&cfg.srv_private_keep, "%s,%s", cfg.srv_private_keep, argv[i] + 14) < 0 )
2021 errExit("asprintf");
2022 } else
2023 cfg.srv_private_keep = argv[i] + 14;
2024 arg_private_srv = 1;
2005 } 2025 }
2006 if (cfg.srv_private_keep) { 2026 else
2007 if ( asprintf(&cfg.srv_private_keep, "%s,%s", cfg.srv_private_keep, argv[i] + 14) < 0 ) 2027 exit_err_feature("private-srv");
2008 errExit("asprintf");
2009 } else
2010 cfg.srv_private_keep = argv[i] + 14;
2011 arg_private_srv = 1;
2012 } 2028 }
2013 else if (strncmp(argv[i], "--private-bin=", 14) == 0) { 2029 else if (strncmp(argv[i], "--private-bin=", 14) == 0) {
2014 // extract private bin list 2030 if (checkcfg(CFG_PRIVATE_BIN)) {
2015 if (*(argv[i] + 14) == '\0') { 2031 // extract private bin list
2016 fprintf(stderr, "Error: invalid private-bin option\n"); 2032 if (*(argv[i] + 14) == '\0') {
2017 exit(1); 2033 fprintf(stderr, "Error: invalid private-bin option\n");
2034 exit(1);
2035 }
2036 if (cfg.bin_private_keep) {
2037 if ( asprintf(&cfg.bin_private_keep, "%s,%s", cfg.bin_private_keep, argv[i] + 14) < 0 )
2038 errExit("asprintf");
2039 } else
2040 cfg.bin_private_keep = argv[i] + 14;
2041 arg_private_bin = 1;
2018 } 2042 }
2019 if (cfg.bin_private_keep) { 2043 else
2020 if ( asprintf(&cfg.bin_private_keep, "%s,%s", cfg.bin_private_keep, argv[i] + 14) < 0 ) 2044 exit_err_feature("private-bin");
2021 errExit("asprintf");
2022 } else
2023 cfg.bin_private_keep = argv[i] + 14;
2024 arg_private_bin = 1;
2025 } 2045 }
2026 else if (strncmp(argv[i], "--private-lib", 13) == 0) { 2046 else if (strncmp(argv[i], "--private-lib", 13) == 0) {
2027 if (checkcfg(CFG_PRIVATE_LIB)) { 2047 if (checkcfg(CFG_PRIVATE_LIB)) {
@@ -2809,6 +2829,11 @@ int main(int argc, char **argv, char **envp) {
2809 // build the sandbox command 2829 // build the sandbox command
2810 if (prog_index == -1 && cfg.shell) { 2830 if (prog_index == -1 && cfg.shell) {
2811 assert(cfg.command_line == NULL); // runs cfg.shell 2831 assert(cfg.command_line == NULL); // runs cfg.shell
2832 if (arg_appimage) {
2833 fprintf(stderr, "Error: no appimage archive specified\n");
2834 exit(1);
2835 }
2836
2812 cfg.window_title = cfg.shell; 2837 cfg.window_title = cfg.shell;
2813 cfg.command_name = cfg.shell; 2838 cfg.command_name = cfg.shell;
2814 } 2839 }
@@ -2816,10 +2841,11 @@ int main(int argc, char **argv, char **envp) {
2816 if (arg_debug) 2841 if (arg_debug)
2817 printf("Configuring appimage environment\n"); 2842 printf("Configuring appimage environment\n");
2818 appimage_set(cfg.command_name); 2843 appimage_set(cfg.command_name);
2819 build_appimage_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, prog_index); 2844 build_appimage_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, prog_index, true);
2820 } 2845 }
2821 else { 2846 else {
2822 build_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, prog_index); 2847 // Only add extra quotes if we were not launched by sshd.
2848 build_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, prog_index, !parent_sshd);
2823 } 2849 }
2824/* else { 2850/* else {
2825 fprintf(stderr, "Error: command must be specified when --shell=none used.\n"); 2851 fprintf(stderr, "Error: command must be specified when --shell=none used.\n");
@@ -2833,7 +2859,13 @@ int main(int argc, char **argv, char **envp) {
2833 2859
2834 // load the profile 2860 // load the profile
2835 if (!arg_noprofile && !custom_profile) { 2861 if (!arg_noprofile && !custom_profile) {
2836 custom_profile = profile_find_firejail(cfg.command_name, 1); 2862 if (arg_appimage) {
2863 custom_profile = appimage_find_profile(cfg.command_name);
2864 // disable shell=* for appimages
2865 arg_shell_none = 0;
2866 }
2867 else
2868 custom_profile = profile_find_firejail(cfg.command_name, 1);
2837 } 2869 }
2838 2870
2839 // use default.profile as the default 2871 // use default.profile as the default
@@ -2847,7 +2879,7 @@ int main(int argc, char **argv, char **envp) {
2847 custom_profile = profile_find_firejail(profile_name, 1); 2879 custom_profile = profile_find_firejail(profile_name, 1);
2848 2880
2849 if (!custom_profile) { 2881 if (!custom_profile) {
2850 fprintf(stderr, "Error: no default.profile installed\n"); 2882 fprintf(stderr, "Error: no %s installed\n", profile_name);
2851 exit(1); 2883 exit(1);
2852 } 2884 }
2853 2885
diff --git a/src/firejail/mountinfo.c b/src/firejail/mountinfo.c
index a700729d3..64a94bd84 100644
--- a/src/firejail/mountinfo.c
+++ b/src/firejail/mountinfo.c
@@ -22,7 +22,7 @@
22 22
23#include <fcntl.h> 23#include <fcntl.h>
24#ifndef O_PATH 24#ifndef O_PATH
25# define O_PATH 010000000 25#define O_PATH 010000000
26#endif 26#endif
27 27
28#define MAX_BUF 4096 28#define MAX_BUF 4096
@@ -153,6 +153,7 @@ MountData *get_last_mount(void) {
153 153
154// Extract the mount id from /proc/self/fdinfo and return it. 154// Extract the mount id from /proc/self/fdinfo and return it.
155int get_mount_id(const char *path) { 155int get_mount_id(const char *path) {
156 EUID_ASSERT();
156 assert(path); 157 assert(path);
157 158
158 int fd = open(path, O_PATH|O_CLOEXEC); 159 int fd = open(path, O_PATH|O_CLOEXEC);
@@ -162,7 +163,9 @@ int get_mount_id(const char *path) {
162 char *fdinfo; 163 char *fdinfo;
163 if (asprintf(&fdinfo, "/proc/self/fdinfo/%d", fd) == -1) 164 if (asprintf(&fdinfo, "/proc/self/fdinfo/%d", fd) == -1)
164 errExit("asprintf"); 165 errExit("asprintf");
166 EUID_ROOT();
165 FILE *fp = fopen(fdinfo, "re"); 167 FILE *fp = fopen(fdinfo, "re");
168 EUID_USER();
166 free(fdinfo); 169 free(fdinfo);
167 if (!fp) 170 if (!fp)
168 goto errexit; 171 goto errexit;
diff --git a/src/firejail/no_sandbox.c b/src/firejail/no_sandbox.c
index 0e153c47b..665bef73d 100644
--- a/src/firejail/no_sandbox.c
+++ b/src/firejail/no_sandbox.c
@@ -204,7 +204,7 @@ void run_no_sandbox(int argc, char **argv) {
204 // force --shell=none in order to not break firecfg symbolic links 204 // force --shell=none in order to not break firecfg symbolic links
205 arg_shell_none = 1; 205 arg_shell_none = 1;
206 206
207 build_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, prog_index); 207 build_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, prog_index, true);
208 } 208 }
209 209
210 fwarning("an existing sandbox was detected. " 210 fwarning("an existing sandbox was detected. "
diff --git a/src/firejail/paths.c b/src/firejail/paths.c
index b800fa944..d58a9d272 100644
--- a/src/firejail/paths.c
+++ b/src/firejail/paths.c
@@ -136,7 +136,7 @@ int program_in_path(const char *program) {
136 // ('x' permission means something different for directories). 136 // ('x' permission means something different for directories).
137 // exec follows symlinks, so use stat, not lstat. 137 // exec follows symlinks, so use stat, not lstat.
138 struct stat st; 138 struct stat st;
139 if (stat(scratch, &st)) { 139 if (stat_as_user(scratch, &st)) {
140 perror(scratch); 140 perror(scratch);
141 exit(1); 141 exit(1);
142 } 142 }
diff --git a/src/firejail/profile.c b/src/firejail/profile.c
index ea8773a51..5b1478918 100644
--- a/src/firejail/profile.c
+++ b/src/firejail/profile.c
@@ -22,6 +22,11 @@
22#include "../include/syscall.h" 22#include "../include/syscall.h"
23#include <dirent.h> 23#include <dirent.h>
24#include <sys/stat.h> 24#include <sys/stat.h>
25
26#ifdef HAVE_GCOV
27#include <gcov.h>
28#endif
29
25extern char *xephyr_screen; 30extern char *xephyr_screen;
26 31
27#define MAX_READ 8192 // line buffer for profile files 32#define MAX_READ 8192 // line buffer for profile files
@@ -1275,56 +1280,69 @@ int profile_check_line(char *ptr, int lineno, const char *fname) {
1275 1280
1276 // private /etc list of files and directories 1281 // private /etc list of files and directories
1277 if (strncmp(ptr, "private-etc ", 12) == 0) { 1282 if (strncmp(ptr, "private-etc ", 12) == 0) {
1278 if (arg_writable_etc) { 1283 if (checkcfg(CFG_PRIVATE_ETC)) {
1279 fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n"); 1284 if (arg_writable_etc) {
1280 exit(1); 1285 fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n");
1281 } 1286 exit(1);
1282 if (cfg.etc_private_keep) { 1287 }
1283 if ( asprintf(&cfg.etc_private_keep, "%s,%s", cfg.etc_private_keep, ptr + 12) < 0 ) 1288 if (cfg.etc_private_keep) {
1284 errExit("asprintf"); 1289 if ( asprintf(&cfg.etc_private_keep, "%s,%s", cfg.etc_private_keep, ptr + 12) < 0 )
1285 } else { 1290 errExit("asprintf");
1286 cfg.etc_private_keep = ptr + 12; 1291 } else {
1292 cfg.etc_private_keep = ptr + 12;
1293 }
1294 arg_private_etc = 1;
1287 } 1295 }
1288 arg_private_etc = 1; 1296 else
1289 1297 warning_feature_disabled("private-etc");
1290 return 0; 1298 return 0;
1291 } 1299 }
1292 1300
1293 // private /opt list of files and directories 1301 // private /opt list of files and directories
1294 if (strncmp(ptr, "private-opt ", 12) == 0) { 1302 if (strncmp(ptr, "private-opt ", 12) == 0) {
1295 if (cfg.opt_private_keep) { 1303 if (checkcfg(CFG_PRIVATE_OPT)) {
1296 if ( asprintf(&cfg.opt_private_keep, "%s,%s", cfg.opt_private_keep, ptr + 12) < 0 ) 1304 if (cfg.opt_private_keep) {
1297 errExit("asprintf"); 1305 if ( asprintf(&cfg.opt_private_keep, "%s,%s", cfg.opt_private_keep, ptr + 12) < 0 )
1298 } else { 1306 errExit("asprintf");
1299 cfg.opt_private_keep = ptr + 12; 1307 } else {
1308 cfg.opt_private_keep = ptr + 12;
1309 }
1310 arg_private_opt = 1;
1300 } 1311 }
1301 arg_private_opt = 1; 1312 else
1302 1313 warning_feature_disabled("private-opt");
1303 return 0; 1314 return 0;
1304 } 1315 }
1305 1316
1306 // private /srv list of files and directories 1317 // private /srv list of files and directories
1307 if (strncmp(ptr, "private-srv ", 12) == 0) { 1318 if (strncmp(ptr, "private-srv ", 12) == 0) {
1308 if (cfg.srv_private_keep) { 1319 if (checkcfg(CFG_PRIVATE_SRV)) {
1309 if ( asprintf(&cfg.srv_private_keep, "%s,%s", cfg.srv_private_keep, ptr + 12) < 0 ) 1320 if (cfg.srv_private_keep) {
1310 errExit("asprintf"); 1321 if ( asprintf(&cfg.srv_private_keep, "%s,%s", cfg.srv_private_keep, ptr + 12) < 0 )
1311 } else { 1322 errExit("asprintf");
1312 cfg.srv_private_keep = ptr + 12; 1323 } else {
1324 cfg.srv_private_keep = ptr + 12;
1325 }
1326 arg_private_srv = 1;
1313 } 1327 }
1314 arg_private_srv = 1; 1328 else
1315 1329 warning_feature_disabled("private-srv");
1316 return 0; 1330 return 0;
1317 } 1331 }
1318 1332
1319 // private /bin list of files 1333 // private /bin list of files
1320 if (strncmp(ptr, "private-bin ", 12) == 0) { 1334 if (strncmp(ptr, "private-bin ", 12) == 0) {
1321 if (cfg.bin_private_keep) { 1335 if (checkcfg(CFG_PRIVATE_BIN)) {
1322 if ( asprintf(&cfg.bin_private_keep, "%s,%s", cfg.bin_private_keep, ptr + 12) < 0 ) 1336 if (cfg.bin_private_keep) {
1323 errExit("asprintf"); 1337 if ( asprintf(&cfg.bin_private_keep, "%s,%s", cfg.bin_private_keep, ptr + 12) < 0 )
1324 } else { 1338 errExit("asprintf");
1325 cfg.bin_private_keep = ptr + 12; 1339 } else {
1340 cfg.bin_private_keep = ptr + 12;
1341 }
1342 arg_private_bin = 1;
1326 } 1343 }
1327 arg_private_bin = 1; 1344 else
1345 warning_feature_disabled("private-bin");
1328 return 0; 1346 return 0;
1329 } 1347 }
1330 1348
@@ -1740,7 +1758,7 @@ void profile_read(const char *fname) {
1740 if (strcmp(ptr, "quiet") == 0) { 1758 if (strcmp(ptr, "quiet") == 0) {
1741 if (is_in_ignore_list(ptr)) 1759 if (is_in_ignore_list(ptr))
1742 arg_quiet = 0; 1760 arg_quiet = 0;
1743 else 1761 else if (!arg_debug)
1744 arg_quiet = 1; 1762 arg_quiet = 1;
1745 free(ptr); 1763 free(ptr);
1746 continue; 1764 continue;
diff --git a/src/firejail/pulseaudio.c b/src/firejail/pulseaudio.c
index 1b01a71c6..f8d4c2f3c 100644
--- a/src/firejail/pulseaudio.c
+++ b/src/firejail/pulseaudio.c
@@ -75,31 +75,34 @@ void pulseaudio_disable(void) {
75 closedir(dir); 75 closedir(dir);
76} 76}
77 77
78static void pulseaudio_fallback(const char *path) {
79 assert(path);
80
81 fmessage("Cannot mount tmpfs on %s/.config/pulse\n", cfg.homedir);
82 env_store_name_val("PULSE_CLIENTCONFIG", path, SETENV);
83}
84
85// disable shm in pulseaudio (issue #69) 78// disable shm in pulseaudio (issue #69)
86void pulseaudio_init(void) { 79void pulseaudio_init(void) {
87 struct stat s;
88
89 // do we have pulseaudio in the system? 80 // do we have pulseaudio in the system?
90 if (stat(PULSE_CLIENT_SYSCONF, &s) == -1) { 81 if (access(PULSE_CLIENT_SYSCONF, R_OK)) {
91 if (arg_debug) 82 if (arg_debug)
92 printf("%s not found\n", PULSE_CLIENT_SYSCONF); 83 printf("Cannot read %s\n", PULSE_CLIENT_SYSCONF);
93 return; 84 return;
94 } 85 }
95 86
87 // create ~/.config/pulse directory if not present
88 char *homeusercfg = NULL;
89 if (asprintf(&homeusercfg, "%s/.config", cfg.homedir) == -1)
90 errExit("asprintf");
91 if (create_empty_dir_as_user(homeusercfg, 0700))
92 fs_logger2("create", homeusercfg);
93
94 free(homeusercfg);
95 if (asprintf(&homeusercfg, "%s/.config/pulse", cfg.homedir) == -1)
96 errExit("asprintf");
97 if (create_empty_dir_as_user(homeusercfg, 0700))
98 fs_logger2("create", homeusercfg);
99
96 // create the new user pulseaudio directory 100 // create the new user pulseaudio directory
101 // that will be mounted over ~/.config/pulse
97 if (mkdir(RUN_PULSE_DIR, 0700) == -1) 102 if (mkdir(RUN_PULSE_DIR, 0700) == -1)
98 errExit("mkdir"); 103 errExit("mkdir");
99 selinux_relabel_path(RUN_PULSE_DIR, RUN_PULSE_DIR); 104 selinux_relabel_path(RUN_PULSE_DIR, homeusercfg);
100 // mount it nosuid, noexec, nodev
101 fs_remount(RUN_PULSE_DIR, MOUNT_NOEXEC, 0); 105 fs_remount(RUN_PULSE_DIR, MOUNT_NOEXEC, 0);
102
103 // create the new client.conf file 106 // create the new client.conf file
104 char *pulsecfg = NULL; 107 char *pulsecfg = NULL;
105 if (asprintf(&pulsecfg, "%s/client.conf", RUN_PULSE_DIR) == -1) 108 if (asprintf(&pulsecfg, "%s/client.conf", RUN_PULSE_DIR) == -1)
@@ -116,37 +119,14 @@ void pulseaudio_init(void) {
116 if (set_perms(RUN_PULSE_DIR, getuid(), getgid(), 0700)) 119 if (set_perms(RUN_PULSE_DIR, getuid(), getgid(), 0700))
117 errExit("set_perms"); 120 errExit("set_perms");
118 121
119 // create ~/.config/pulse directory if not present
120 char *homeusercfg = NULL;
121 if (asprintf(&homeusercfg, "%s/.config", cfg.homedir) == -1)
122 errExit("asprintf");
123 if (create_empty_dir_as_user(homeusercfg, 0700))
124 fs_logger2("create", homeusercfg);
125
126 free(homeusercfg);
127 if (asprintf(&homeusercfg, "%s/.config/pulse", cfg.homedir) == -1)
128 errExit("asprintf");
129 if (create_empty_dir_as_user(homeusercfg, 0700))
130 fs_logger2("create", homeusercfg);
131
132 // if ~/.config/pulse exists and there are no symbolic links, mount the new directory 122 // if ~/.config/pulse exists and there are no symbolic links, mount the new directory
133 // else set environment variable 123 // else set environment variable
124 EUID_USER();
134 int fd = safer_openat(-1, homeusercfg, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 125 int fd = safer_openat(-1, homeusercfg, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
126 EUID_ROOT();
135 if (fd == -1) { 127 if (fd == -1) {
136 pulseaudio_fallback(pulsecfg); 128 fwarning("not mounting tmpfs on %s\n", homeusercfg);
137 goto out; 129 env_store_name_val("PULSE_CLIENTCONFIG", pulsecfg, SETENV);
138 }
139 // confirm the actual mount destination is owned by the user
140 if (fstat(fd, &s) == -1) { // FUSE
141 if (errno != EACCES)
142 errExit("fstat");
143 close(fd);
144 pulseaudio_fallback(pulsecfg);
145 goto out;
146 }
147 if (s.st_uid != getuid()) {
148 close(fd);
149 pulseaudio_fallback(pulsecfg);
150 goto out; 130 goto out;
151 } 131 }
152 // preserve a read-only mount 132 // preserve a read-only mount
@@ -158,17 +138,13 @@ void pulseaudio_init(void) {
158 // mount via the link in /proc/self/fd 138 // mount via the link in /proc/self/fd
159 if (arg_debug) 139 if (arg_debug)
160 printf("Mounting %s on %s\n", RUN_PULSE_DIR, homeusercfg); 140 printf("Mounting %s on %s\n", RUN_PULSE_DIR, homeusercfg);
161 char *proc; 141 if (bind_mount_path_to_fd(RUN_PULSE_DIR, fd))
162 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
163 errExit("asprintf");
164 if (mount(RUN_PULSE_DIR, proc, "none", MS_BIND, NULL) < 0)
165 errExit("mount pulseaudio"); 142 errExit("mount pulseaudio");
166 // check /proc/self/mountinfo to confirm the mount is ok 143 // check /proc/self/mountinfo to confirm the mount is ok
167 MountData *mptr = get_last_mount(); 144 MountData *mptr = get_last_mount();
168 if (strcmp(mptr->dir, homeusercfg) != 0 || strcmp(mptr->fstype, "tmpfs") != 0) 145 if (strcmp(mptr->dir, homeusercfg) != 0 || strcmp(mptr->fstype, "tmpfs") != 0)
169 errLogExit("invalid pulseaudio mount"); 146 errLogExit("invalid pulseaudio mount");
170 fs_logger2("tmpfs", homeusercfg); 147 fs_logger2("tmpfs", homeusercfg);
171 free(proc);
172 close(fd); 148 close(fd);
173 149
174 char *p; 150 char *p;
diff --git a/src/firejail/restrict_users.c b/src/firejail/restrict_users.c
index 53e395b89..6f17231a4 100644
--- a/src/firejail/restrict_users.c
+++ b/src/firejail/restrict_users.c
@@ -104,12 +104,8 @@ static void sanitize_home(void) {
104 selinux_relabel_path(cfg.homedir, cfg.homedir); 104 selinux_relabel_path(cfg.homedir, cfg.homedir);
105 105
106 // bring back real user home directory 106 // bring back real user home directory
107 char *proc; 107 if (bind_mount_fd_to_path(fd, cfg.homedir))
108 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
109 errExit("asprintf");
110 if (mount(proc, cfg.homedir, NULL, MS_BIND|MS_REC, NULL) < 0)
111 errExit("mount bind"); 108 errExit("mount bind");
112 free(proc);
113 close(fd); 109 close(fd);
114 110
115 if (!arg_private) 111 if (!arg_private)
@@ -154,12 +150,8 @@ static void sanitize_run(void) {
154 selinux_relabel_path(runuser, runuser); 150 selinux_relabel_path(runuser, runuser);
155 151
156 // bring back real run/user/$UID directory 152 // bring back real run/user/$UID directory
157 char *proc; 153 if (bind_mount_fd_to_path(fd, runuser))
158 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
159 errExit("asprintf");
160 if (mount(proc, runuser, NULL, MS_BIND|MS_REC, NULL) < 0)
161 errExit("mount bind"); 154 errExit("mount bind");
162 free(proc);
163 close(fd); 155 close(fd);
164 156
165 fs_logger2("whitelist", runuser); 157 fs_logger2("whitelist", runuser);
@@ -246,6 +238,11 @@ static void sanitize_passwd(void) {
246 // mount-bind tne new password file 238 // mount-bind tne new password file
247 if (mount(RUN_PASSWD_FILE, "/etc/passwd", "none", MS_BIND, "mode=400,gid=0") < 0) 239 if (mount(RUN_PASSWD_FILE, "/etc/passwd", "none", MS_BIND, "mode=400,gid=0") < 0)
248 errExit("mount"); 240 errExit("mount");
241
242 // blacklist RUN_PASSWD_FILE
243 if (mount(RUN_RO_FILE, RUN_PASSWD_FILE, "none", MS_BIND, "mode=400,gid=0") < 0)
244 errExit("mount");
245
249 fs_logger("create /etc/passwd"); 246 fs_logger("create /etc/passwd");
250 247
251 return; 248 return;
@@ -376,6 +373,11 @@ static void sanitize_group(void) {
376 // mount-bind tne new group file 373 // mount-bind tne new group file
377 if (mount(RUN_GROUP_FILE, "/etc/group", "none", MS_BIND, "mode=400,gid=0") < 0) 374 if (mount(RUN_GROUP_FILE, "/etc/group", "none", MS_BIND, "mode=400,gid=0") < 0)
378 errExit("mount"); 375 errExit("mount");
376
377 // blacklist RUN_GROUP_FILE
378 if (mount(RUN_RO_FILE, RUN_GROUP_FILE, "none", MS_BIND, "mode=400,gid=0") < 0)
379 errExit("mount");
380
379 fs_logger("create /etc/group"); 381 fs_logger("create /etc/group");
380 382
381 return; 383 return;
diff --git a/src/firejail/rlimit.c b/src/firejail/rlimit.c
index 78f00bc63..dd6fec972 100644
--- a/src/firejail/rlimit.c
+++ b/src/firejail/rlimit.c
@@ -21,6 +21,10 @@
21#include <sys/time.h> 21#include <sys/time.h>
22#include <sys/resource.h> 22#include <sys/resource.h>
23 23
24#ifdef HAVE_GCOV
25#include <gcov.h>
26#endif
27
24void set_rlimits(void) { 28void set_rlimits(void) {
25 EUID_ASSERT(); 29 EUID_ASSERT();
26 // resource limits 30 // resource limits
diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c
index a6bcec02c..e06ba3617 100644
--- a/src/firejail/sandbox.c
+++ b/src/firejail/sandbox.c
@@ -49,6 +49,9 @@
49#include <sys/apparmor.h> 49#include <sys/apparmor.h>
50#endif 50#endif
51 51
52#ifdef HAVE_GCOV
53#include <gcov.h>
54#endif
52 55
53static int force_nonewprivs = 0; 56static int force_nonewprivs = 0;
54 57
diff --git a/src/firejail/sbox.c b/src/firejail/sbox.c
index 4a8dd1bf7..37111324a 100644
--- a/src/firejail/sbox.c
+++ b/src/firejail/sbox.c
@@ -265,7 +265,6 @@ int sbox_run(unsigned filtermask, int num, ...) {
265} 265}
266 266
267int sbox_run_v(unsigned filtermask, char * const arg[]) { 267int sbox_run_v(unsigned filtermask, char * const arg[]) {
268 EUID_ROOT();
269 assert(arg); 268 assert(arg);
270 269
271 if (arg_debug) { 270 if (arg_debug) {
@@ -285,6 +284,7 @@ int sbox_run_v(unsigned filtermask, char * const arg[]) {
285 if (child < 0) 284 if (child < 0)
286 errExit("fork"); 285 errExit("fork");
287 if (child == 0) { 286 if (child == 0) {
287 EUID_ROOT();
288 sbox_do_exec_v(filtermask, arg); 288 sbox_do_exec_v(filtermask, arg);
289 } 289 }
290 290
@@ -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/selinux.c b/src/firejail/selinux.c
index 06189d7f6..6969e7a3d 100644
--- a/src/firejail/selinux.c
+++ b/src/firejail/selinux.c
@@ -19,10 +19,13 @@
19*/ 19*/
20#if HAVE_SELINUX 20#if HAVE_SELINUX
21#include "firejail.h" 21#include "firejail.h"
22
23#include <sys/types.h> 22#include <sys/types.h>
24#include <sys/stat.h> 23#include <sys/stat.h>
24
25#include <fcntl.h> 25#include <fcntl.h>
26#ifndef O_PATH
27#define O_PATH 010000000
28#endif
26 29
27#include <selinux/context.h> 30#include <selinux/context.h>
28#include <selinux/label.h> 31#include <selinux/label.h>
@@ -52,8 +55,9 @@ void selinux_relabel_path(const char *path, const char *inside_path)
52 if (!label_hnd) 55 if (!label_hnd)
53 errExit("selabel_open"); 56 errExit("selabel_open");
54 57
55 /* Open the file as O_PATH, to pin it while we determine and adjust the label */ 58 /* Open the file as O_PATH, to pin it while we determine and adjust the label
56 fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH); 59 * Defeat symlink races by not allowing symbolic links */
60 fd = safer_openat(-1, path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
57 if (fd < 0) 61 if (fd < 0)
58 return; 62 return;
59 if (fstat(fd, &st) < 0) 63 if (fstat(fd, &st) < 0)
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 e2a4a1d90..68b76b8e8 100644
--- a/src/firejail/util.c
+++ b/src/firejail/util.c
@@ -44,6 +44,10 @@
44#include <linux/openat2.h> 44#include <linux/openat2.h>
45#endif 45#endif
46 46
47#ifdef HAVE_GCOV
48#include <gcov.h>
49#endif
50
47#define MAX_GROUPS 1024 51#define MAX_GROUPS 1024
48#define MAXBUF 4098 52#define MAXBUF 4098
49#define EMPTY_STRING ("") 53#define EMPTY_STRING ("")
@@ -435,11 +439,11 @@ void touch_file_as_user(const char *fname, mode_t mode) {
435 // drop privileges 439 // drop privileges
436 drop_privs(0); 440 drop_privs(0);
437 441
438 FILE *fp = fopen(fname, "wx"); 442 int fd = open(fname, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR | S_IWUSR);
439 if (fp) { 443 if (fd > -1) {
440 fprintf(fp, "\n"); 444 int err = fchmod(fd, mode);
441 SET_PERMS_STREAM(fp, -1, -1, mode); 445 (void) err;
442 fclose(fp); 446 close(fd);
443 } 447 }
444 else 448 else
445 fwarning("cannot create %s\n", fname); 449 fwarning("cannot create %s\n", fname);
@@ -458,6 +462,13 @@ int is_dir(const char *fname) {
458 if (*fname == '\0') 462 if (*fname == '\0')
459 return 0; 463 return 0;
460 464
465 int called_as_root = 0;
466 if (geteuid() == 0)
467 called_as_root = 1;
468
469 if (called_as_root)
470 EUID_USER();
471
461 // if fname doesn't end in '/', add one 472 // if fname doesn't end in '/', add one
462 int rv; 473 int rv;
463 struct stat s; 474 struct stat s;
@@ -473,6 +484,9 @@ int is_dir(const char *fname) {
473 free(tmp); 484 free(tmp);
474 } 485 }
475 486
487 if (called_as_root)
488 EUID_ROOT();
489
476 if (rv == -1) 490 if (rv == -1)
477 return 0; 491 return 0;
478 492
@@ -488,18 +502,83 @@ int is_link(const char *fname) {
488 if (*fname == '\0') 502 if (*fname == '\0')
489 return 0; 503 return 0;
490 504
491 char *dup = strdup(fname); 505 int called_as_root = 0;
492 if (!dup) 506 if (geteuid() == 0)
507 called_as_root = 1;
508
509 if (called_as_root)
510 EUID_USER();
511
512 // remove trailing '/' if any
513 char *tmp = strdup(fname);
514 if (!tmp)
493 errExit("strdup"); 515 errExit("strdup");
494 trim_trailing_slash_or_dot(dup); 516 trim_trailing_slash_or_dot(tmp);
495 517
496 char c; 518 char c;
497 ssize_t rv = readlink(dup, &c, 1); 519 ssize_t rv = readlink(tmp, &c, 1);
520 free(tmp);
521
522 if (called_as_root)
523 EUID_ROOT();
498 524
499 free(dup);
500 return (rv != -1); 525 return (rv != -1);
501} 526}
502 527
528char *realpath_as_user(const char *fname) {
529 assert(fname);
530
531 int called_as_root = 0;
532 if (geteuid() == 0)
533 called_as_root = 1;
534
535 if (called_as_root)
536 EUID_USER();
537
538 char *rv = realpath(fname, NULL);
539
540 if (called_as_root)
541 EUID_ROOT();
542
543 return rv;
544}
545
546int stat_as_user(const char *fname, struct stat *s) {
547 assert(fname);
548
549 int called_as_root = 0;
550 if (geteuid() == 0)
551 called_as_root = 1;
552
553 if (called_as_root)
554 EUID_USER();
555
556 int rv = stat(fname, s);
557
558 if (called_as_root)
559 EUID_ROOT();
560
561 return rv;
562}
563
564int lstat_as_user(const char *fname, struct stat *s) {
565 assert(fname);
566
567 int called_as_root = 0;
568 if (geteuid() == 0)
569 called_as_root = 1;
570
571 if (called_as_root)
572 EUID_USER();
573
574 int rv = lstat(fname, s);
575
576 if (called_as_root)
577 EUID_ROOT();
578
579 return rv;
580}
581
503// remove all slashes and single dots from the end of a path 582// remove all slashes and single dots from the end of a path
504// for example /foo/bar///././. -> /foo/bar 583// for example /foo/bar///././. -> /foo/bar
505void trim_trailing_slash_or_dot(char *path) { 584void trim_trailing_slash_or_dot(char *path) {
@@ -688,9 +767,11 @@ int find_child(pid_t parent, pid_t *child) {
688 if (parent == atoi(ptr)) { 767 if (parent == atoi(ptr)) {
689 // we don't want /usr/bin/xdg-dbus-proxy! 768 // we don't want /usr/bin/xdg-dbus-proxy!
690 char *cmdline = pid_proc_cmdline(pid); 769 char *cmdline = pid_proc_cmdline(pid);
691 if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0) 770 if (cmdline) {
692 *child = pid; 771 if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0)
693 free(cmdline); 772 *child = pid;
773 free(cmdline);
774 }
694 } 775 }
695 break; // stop reading the file 776 break; // stop reading the file
696 } 777 }
@@ -930,35 +1011,37 @@ static int remove_callback(const char *fpath, const struct stat *sb, int typefla
930 1011
931int remove_overlay_directory(void) { 1012int remove_overlay_directory(void) {
932 EUID_ASSERT(); 1013 EUID_ASSERT();
933 struct stat s;
934 sleep(1); 1014 sleep(1);
935 1015
936 char *path; 1016 char *path;
937 if (asprintf(&path, "%s/.firejail", cfg.homedir) == -1) 1017 if (asprintf(&path, "%s/.firejail", cfg.homedir) == -1)
938 errExit("asprintf"); 1018 errExit("asprintf");
939 1019
940 if (lstat(path, &s) == 0) { 1020 if (access(path, F_OK) == 0) {
941 // deal with obvious problems such as symlinks and root ownership
942 if (!S_ISDIR(s.st_mode)) {
943 if (S_ISLNK(s.st_mode))
944 fprintf(stderr, "Error: %s is a symbolic link\n", path);
945 else
946 fprintf(stderr, "Error: %s is not a directory\n", path);
947 exit(1);
948 }
949 if (s.st_uid != getuid()) {
950 fprintf(stderr, "Error: %s is not owned by the current user\n", path);
951 exit(1);
952 }
953
954 pid_t child = fork(); 1021 pid_t child = fork();
955 if (child < 0) 1022 if (child < 0)
956 errExit("fork"); 1023 errExit("fork");
957 if (child == 0) { 1024 if (child == 0) {
958 // open ~/.firejail, fails if there is any symlink 1025 // open ~/.firejail
959 int fd = safer_openat(-1, path, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 1026 int fd = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
960 if (fd == -1) 1027 if (fd == -1) {
961 errExit("safer_openat"); 1028 fprintf(stderr, "Error: cannot open %s\n", path);
1029 exit(1);
1030 }
1031 struct stat s;
1032 if (fstat(fd, &s) == -1)
1033 errExit("fstat");
1034 if (!S_ISDIR(s.st_mode)) {
1035 if (S_ISLNK(s.st_mode))
1036 fprintf(stderr, "Error: %s is a symbolic link\n", path);
1037 else
1038 fprintf(stderr, "Error: %s is not a directory\n", path);
1039 exit(1);
1040 }
1041 if (s.st_uid != getuid()) {
1042 fprintf(stderr, "Error: %s is not owned by the current user\n", path);
1043 exit(1);
1044 }
962 // chdir to ~/.firejail 1045 // chdir to ~/.firejail
963 if (fchdir(fd) == -1) 1046 if (fchdir(fd) == -1)
964 errExit("fchdir"); 1047 errExit("fchdir");
@@ -981,7 +1064,7 @@ int remove_overlay_directory(void) {
981 // wait for the child to finish 1064 // wait for the child to finish
982 waitpid(child, NULL, 0); 1065 waitpid(child, NULL, 0);
983 // check if ~/.firejail was deleted 1066 // check if ~/.firejail was deleted
984 if (stat(path, &s) == 0) 1067 if (access(path, F_OK) == 0)
985 return 1; 1068 return 1;
986 } 1069 }
987 return 0; 1070 return 0;
@@ -1014,9 +1097,8 @@ void flush_stdin(void) {
1014int create_empty_dir_as_user(const char *dir, mode_t mode) { 1097int create_empty_dir_as_user(const char *dir, mode_t mode) {
1015 assert(dir); 1098 assert(dir);
1016 mode &= 07777; 1099 mode &= 07777;
1017 struct stat s;
1018 1100
1019 if (stat(dir, &s)) { 1101 if (access(dir, F_OK) != 0) {
1020 if (arg_debug) 1102 if (arg_debug)
1021 printf("Creating empty %s directory\n", dir); 1103 printf("Creating empty %s directory\n", dir);
1022 pid_t child = fork(); 1104 pid_t child = fork();
@@ -1027,8 +1109,8 @@ int create_empty_dir_as_user(const char *dir, mode_t mode) {
1027 drop_privs(0); 1109 drop_privs(0);
1028 1110
1029 if (mkdir(dir, mode) == 0) { 1111 if (mkdir(dir, mode) == 0) {
1030 if (chmod(dir, mode) == -1) 1112 int err = chmod(dir, mode);
1031 {;} // do nothing 1113 (void) err;
1032 } 1114 }
1033 else if (arg_debug) 1115 else if (arg_debug)
1034 printf("Directory %s not created: %s\n", dir, strerror(errno)); 1116 printf("Directory %s not created: %s\n", dir, strerror(errno));
@@ -1038,7 +1120,7 @@ int create_empty_dir_as_user(const char *dir, mode_t mode) {
1038 _exit(0); 1120 _exit(0);
1039 } 1121 }
1040 waitpid(child, NULL, 0); 1122 waitpid(child, NULL, 0);
1041 if (stat(dir, &s) == 0) 1123 if (access(dir, F_OK) == 0)
1042 return 1; 1124 return 1;
1043 } 1125 }
1044 return 0; 1126 return 0;
@@ -1149,20 +1231,34 @@ unsigned extract_timeout(const char *str) {
1149} 1231}
1150 1232
1151void disable_file_or_dir(const char *fname) { 1233void disable_file_or_dir(const char *fname) {
1234 assert(fname);
1235
1236 EUID_USER();
1237 int fd = open(fname, O_PATH|O_CLOEXEC);
1238 EUID_ROOT();
1239 if (fd < 0)
1240 return;
1241
1152 struct stat s; 1242 struct stat s;
1153 if (stat(fname, &s) != -1) { 1243 if (fstat(fd, &s) < 0) { // FUSE
1154 if (arg_debug) 1244 if (errno != EACCES)
1155 printf("blacklist %s\n", fname); 1245 errExit("fstat");
1156 if (is_dir(fname)) { 1246 close(fd);
1157 if (mount(RUN_RO_DIR, fname, "none", MS_BIND, "mode=400,gid=0") < 0) 1247 return;
1158 errExit("disable directory");
1159 }
1160 else {
1161 if (mount(RUN_RO_FILE, fname, "none", MS_BIND, "mode=400,gid=0") < 0)
1162 errExit("disable file");
1163 }
1164 fs_logger2("blacklist", fname);
1165 } 1248 }
1249
1250 if (arg_debug)
1251 printf("blacklist %s\n", fname);
1252 if (S_ISDIR(s.st_mode)) {
1253 if (bind_mount_path_to_fd(RUN_RO_DIR, fd) < 0)
1254 errExit("disable directory");
1255 }
1256 else {
1257 if (bind_mount_path_to_fd(RUN_RO_FILE, fd) < 0)
1258 errExit("disable file");
1259 }
1260 close(fd);
1261 fs_logger2("blacklist", fname);
1166} 1262}
1167 1263
1168void disable_file_path(const char *path, const char *file) { 1264void disable_file_path(const char *path, const char *file) {
@@ -1249,6 +1345,60 @@ int safer_openat(int dirfd, const char *path, int flags) {
1249 return fd; 1345 return fd;
1250} 1346}
1251 1347
1348int remount_by_fd(int dst, unsigned long mountflags) {
1349 char *proc;
1350 if (asprintf(&proc, "/proc/self/fd/%d", dst) < 0)
1351 errExit("asprintf");
1352
1353 int rv = mount(NULL, proc, NULL, mountflags|MS_BIND|MS_REMOUNT, NULL);
1354 if (rv < 0 && arg_debug)
1355 printf("Failed mount: %s\n", strerror(errno));
1356
1357 free(proc);
1358 return rv;
1359}
1360
1361int bind_mount_by_fd(int src, int dst) {
1362 char *proc_src, *proc_dst;
1363 if (asprintf(&proc_src, "/proc/self/fd/%d", src) < 0 ||
1364 asprintf(&proc_dst, "/proc/self/fd/%d", dst) < 0)
1365 errExit("asprintf");
1366
1367 int rv = mount(proc_src, proc_dst, NULL, MS_BIND|MS_REC, NULL);
1368 if (rv < 0 && arg_debug)
1369 printf("Failed mount: %s\n", strerror(errno));
1370
1371 free(proc_src);
1372 free(proc_dst);
1373 return rv;
1374}
1375
1376int bind_mount_fd_to_path(int src, const char *destname) {
1377 char *proc;
1378 if (asprintf(&proc, "/proc/self/fd/%d", src) < 0)
1379 errExit("asprintf");
1380
1381 int rv = mount(proc, destname, NULL, MS_BIND|MS_REC, NULL);
1382 if (rv < 0 && arg_debug)
1383 printf("Failed mount: %s\n", strerror(errno));
1384
1385 free(proc);
1386 return rv;
1387}
1388
1389int bind_mount_path_to_fd(const char *srcname, int dst) {
1390 char *proc;
1391 if (asprintf(&proc, "/proc/self/fd/%d", dst) < 0)
1392 errExit("asprintf");
1393
1394 int rv = mount(srcname, proc, NULL, MS_BIND|MS_REC, NULL);
1395 if (rv < 0 && arg_debug)
1396 printf("Failed mount: %s\n", strerror(errno));
1397
1398 free(proc);
1399 return rv;
1400}
1401
1252int has_handler(pid_t pid, int signal) { 1402int has_handler(pid_t pid, int signal) {
1253 if (signal > 0 && signal <= SIGRTMAX) { 1403 if (signal > 0 && signal <= SIGRTMAX) {
1254 char *fname; 1404 char *fname;
@@ -1358,14 +1508,14 @@ static int has_link(const char *dir) {
1358 return 0; 1508 return 0;
1359} 1509}
1360 1510
1361void check_homedir(void) { 1511void check_homedir(const char *dir) {
1362 assert(cfg.homedir); 1512 assert(dir);
1363 if (cfg.homedir[0] != '/') { 1513 if (dir[0] != '/') {
1364 fprintf(stderr, "Error: invalid user directory \"%s\"\n", cfg.homedir); 1514 fprintf(stderr, "Error: invalid user directory \"%s\"\n", cfg.homedir);
1365 exit(1); 1515 exit(1);
1366 } 1516 }
1367 // symlinks are rejected in many places 1517 // symlinks are rejected in many places
1368 if (has_link(cfg.homedir)) { 1518 if (has_link(dir)) {
1369 fprintf(stderr, "No full support for symbolic links in path of user directory.\n" 1519 fprintf(stderr, "No full support for symbolic links in path of user directory.\n"
1370 "Please provide resolved path in password database (/etc/passwd).\n\n"); 1520 "Please provide resolved path in password database (/etc/passwd).\n\n");
1371 } 1521 }
diff --git a/src/firejail/x11.c b/src/firejail/x11.c
index 257d376a1..0619ff380 100644
--- a/src/firejail/x11.c
+++ b/src/firejail/x11.c
@@ -204,7 +204,6 @@ static int random_display_number(void) {
204void x11_start_xvfb(int argc, char **argv) { 204void x11_start_xvfb(int argc, char **argv) {
205 EUID_ASSERT(); 205 EUID_ASSERT();
206 int i; 206 int i;
207 struct stat s;
208 pid_t jail = 0; 207 pid_t jail = 0;
209 pid_t server = 0; 208 pid_t server = 0;
210 209
@@ -348,7 +347,7 @@ void x11_start_xvfb(int argc, char **argv) {
348 // wait for x11 server to start 347 // wait for x11 server to start
349 while (++n < 10) { 348 while (++n < 10) {
350 sleep(1); 349 sleep(1);
351 if (stat(fname, &s) == 0) 350 if (access(fname, F_OK) == 0)
352 break; 351 break;
353 }; 352 };
354 353
@@ -427,7 +426,6 @@ static char *extract_setting(int argc, char **argv, const char *argument) {
427void x11_start_xephyr(int argc, char **argv) { 426void x11_start_xephyr(int argc, char **argv) {
428 EUID_ASSERT(); 427 EUID_ASSERT();
429 int i; 428 int i;
430 struct stat s;
431 pid_t jail = 0; 429 pid_t jail = 0;
432 pid_t server = 0; 430 pid_t server = 0;
433 431
@@ -586,7 +584,7 @@ void x11_start_xephyr(int argc, char **argv) {
586 // wait for x11 server to start 584 // wait for x11 server to start
587 while (++n < 10) { 585 while (++n < 10) {
588 sleep(1); 586 sleep(1);
589 if (stat(fname, &s) == 0) 587 if (access(fname, F_OK) == 0)
590 break; 588 break;
591 }; 589 };
592 590
@@ -701,7 +699,6 @@ static char * get_title_arg_str() {
701static void __attribute__((noreturn)) x11_start_xpra_old(int argc, char **argv, int display, char *display_str) { 699static void __attribute__((noreturn)) x11_start_xpra_old(int argc, char **argv, int display, char *display_str) {
702 EUID_ASSERT(); 700 EUID_ASSERT();
703 int i; 701 int i;
704 struct stat s;
705 pid_t client = 0; 702 pid_t client = 0;
706 pid_t server = 0; 703 pid_t server = 0;
707 704
@@ -818,7 +815,7 @@ static void __attribute__((noreturn)) x11_start_xpra_old(int argc, char **argv,
818 // wait for x11 server to start 815 // wait for x11 server to start
819 while (++n < 10) { 816 while (++n < 10) {
820 sleep(1); 817 sleep(1);
821 if (stat(fname, &s) == 0) 818 if (access(fname, F_OK) == 0)
822 break; 819 break;
823 } 820 }
824 821
@@ -1207,14 +1204,13 @@ void x11_xorg(void) {
1207 fmessage("Generating a new .Xauthority file\n"); 1204 fmessage("Generating a new .Xauthority file\n");
1208 mkdir_attr(RUN_XAUTHORITY_SEC_DIR, 0700, getuid(), getgid()); 1205 mkdir_attr(RUN_XAUTHORITY_SEC_DIR, 0700, getuid(), getgid());
1209 // create new Xauthority file in RUN_XAUTHORITY_SEC_DIR 1206 // create new Xauthority file in RUN_XAUTHORITY_SEC_DIR
1207 EUID_USER();
1210 char tmpfname[] = RUN_XAUTHORITY_SEC_DIR "/.Xauth-XXXXXX"; 1208 char tmpfname[] = RUN_XAUTHORITY_SEC_DIR "/.Xauth-XXXXXX";
1211 int fd = mkstemp(tmpfname); 1209 int fd = mkstemp(tmpfname);
1212 if (fd == -1) { 1210 if (fd == -1) {
1213 fprintf(stderr, "Error: cannot create .Xauthority file\n"); 1211 fprintf(stderr, "Error: cannot create .Xauthority file\n");
1214 exit(1); 1212 exit(1);
1215 } 1213 }
1216 if (fchown(fd, getuid(), getgid()) == -1)
1217 errExit("chown");
1218 close(fd); 1214 close(fd);
1219 1215
1220 // run xauth 1216 // run xauth
@@ -1224,16 +1220,14 @@ void x11_xorg(void) {
1224 else 1220 else
1225 sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 7, RUN_XAUTH_FILE, "-f", tmpfname, 1221 sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 7, RUN_XAUTH_FILE, "-f", tmpfname,
1226 "generate", display, "MIT-MAGIC-COOKIE-1", "untrusted"); 1222 "generate", display, "MIT-MAGIC-COOKIE-1", "untrusted");
1227 // remove xauth copy
1228 unlink(RUN_XAUTH_FILE);
1229 1223
1230 // ensure there is already a file ~/.Xauthority, so that bind-mount below will work. 1224 // ensure there is already a file ~/.Xauthority, so that bind-mount below will work.
1231 char *dest; 1225 char *dest;
1232 if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1) 1226 if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1)
1233 errExit("asprintf"); 1227 errExit("asprintf");
1234 if (lstat(dest, &s) == -1) { 1228 if (access(dest, F_OK) == -1) {
1235 touch_file_as_user(dest, 0600); 1229 touch_file_as_user(dest, 0600);
1236 if (stat(dest, &s) == -1) { 1230 if (access(dest, F_OK) == -1) {
1237 fprintf(stderr, "Error: cannot create %s\n", dest); 1231 fprintf(stderr, "Error: cannot create %s\n", dest);
1238 exit(1); 1232 exit(1);
1239 } 1233 }
@@ -1276,21 +1270,16 @@ void x11_xorg(void) {
1276 // mount via the link in /proc/self/fd 1270 // mount via the link in /proc/self/fd
1277 if (arg_debug) 1271 if (arg_debug)
1278 printf("Mounting %s on %s\n", tmpfname, dest); 1272 printf("Mounting %s on %s\n", tmpfname, dest);
1279 char *proc_src, *proc_dst; 1273 EUID_ROOT();
1280 if (asprintf(&proc_src, "/proc/self/fd/%d", src) == -1) 1274 if (bind_mount_by_fd(src, dst)) {
1281 errExit("asprintf");
1282 if (asprintf(&proc_dst, "/proc/self/fd/%d", dst) == -1)
1283 errExit("asprintf");
1284 if (mount(proc_src, proc_dst, NULL, MS_BIND, NULL) == -1) {
1285 fprintf(stderr, "Error: cannot mount the new .Xauthority file\n"); 1275 fprintf(stderr, "Error: cannot mount the new .Xauthority file\n");
1286 exit(1); 1276 exit(1);
1287 } 1277 }
1278 EUID_USER();
1288 // check /proc/self/mountinfo to confirm the mount is ok 1279 // check /proc/self/mountinfo to confirm the mount is ok
1289 MountData *mptr = get_last_mount(); 1280 MountData *mptr = get_last_mount();
1290 if (strcmp(mptr->dir, dest) != 0 || strcmp(mptr->fstype, "tmpfs") != 0) 1281 if (strcmp(mptr->dir, dest) != 0 || strcmp(mptr->fstype, "tmpfs") != 0)
1291 errLogExit("invalid .Xauthority mount"); 1282 errLogExit("invalid .Xauthority mount");
1292 free(proc_src);
1293 free(proc_dst);
1294 close(src); 1283 close(src);
1295 close(dst); 1284 close(dst);
1296 1285
@@ -1302,6 +1291,7 @@ void x11_xorg(void) {
1302 char *rp = realpath(envar, NULL); 1291 char *rp = realpath(envar, NULL);
1303 if (rp) { 1292 if (rp) {
1304 if (strcmp(rp, dest) != 0) 1293 if (strcmp(rp, dest) != 0)
1294 // disable_file_or_dir returns with EUID 0
1305 disable_file_or_dir(rp); 1295 disable_file_or_dir(rp);
1306 free(rp); 1296 free(rp);
1307 } 1297 }
@@ -1311,9 +1301,13 @@ void x11_xorg(void) {
1311 free(dest); 1301 free(dest);
1312 1302
1313 // mask RUN_XAUTHORITY_SEC_DIR 1303 // mask RUN_XAUTHORITY_SEC_DIR
1304 EUID_ROOT();
1314 if (mount("tmpfs", RUN_XAUTHORITY_SEC_DIR, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME, "mode=755,gid=0") < 0) 1305 if (mount("tmpfs", RUN_XAUTHORITY_SEC_DIR, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME, "mode=755,gid=0") < 0)
1315 errExit("mounting tmpfs"); 1306 errExit("mounting tmpfs");
1316 fs_logger2("tmpfs", RUN_XAUTHORITY_SEC_DIR); 1307 fs_logger2("tmpfs", RUN_XAUTHORITY_SEC_DIR);
1308
1309 // cleanup
1310 unlink(RUN_XAUTH_FILE);
1317#endif 1311#endif
1318} 1312}
1319 1313
@@ -1327,7 +1321,7 @@ void fs_x11(void) {
1327 struct stat s1, s2; 1321 struct stat s1, s2;
1328 if (stat("/tmp", &s1) != 0 || lstat("/tmp/.X11-unix", &s2) != 0) 1322 if (stat("/tmp", &s1) != 0 || lstat("/tmp/.X11-unix", &s2) != 0)
1329 return; 1323 return;
1330 if ((s1.st_mode & S_ISVTX) == 0) { 1324 if ((s1.st_mode & S_ISVTX) != S_ISVTX) {
1331 fwarning("cannot mask X11 sockets: sticky bit not set on /tmp directory\n"); 1325 fwarning("cannot mask X11 sockets: sticky bit not set on /tmp directory\n");
1332 return; 1326 return;
1333 } 1327 }
@@ -1335,68 +1329,46 @@ void fs_x11(void) {
1335 fwarning("cannot mask X11 sockets: /tmp/.X11-unix not owned by root user\n"); 1329 fwarning("cannot mask X11 sockets: /tmp/.X11-unix not owned by root user\n");
1336 return; 1330 return;
1337 } 1331 }
1332
1333 // the mount source is under control of the user, so be careful and
1334 // mount without following symbolic links, using a file descriptor
1338 char *x11file; 1335 char *x11file;
1339 if (asprintf(&x11file, "/tmp/.X11-unix/X%d", display) == -1) 1336 if (asprintf(&x11file, "/tmp/.X11-unix/X%d", display) == -1)
1340 errExit("asprintf"); 1337 errExit("asprintf");
1341 struct stat x11stat; 1338 int src = open(x11file, O_PATH|O_NOFOLLOW|O_CLOEXEC);
1342 if (lstat(x11file, &x11stat) != 0 || !S_ISSOCK(x11stat.st_mode)) { 1339 if (src < 0) {
1340 free(x11file);
1341 return;
1342 }
1343 struct stat s3;
1344 if (fstat(src, &s3) < 0)
1345 errExit("fstat");
1346 if (!S_ISSOCK(s3.st_mode)) {
1347 close(src);
1343 free(x11file); 1348 free(x11file);
1344 return; 1349 return;
1345 } 1350 }
1346 1351
1347 if (arg_debug || arg_debug_whitelists) 1352 if (arg_debug || arg_debug_whitelists)
1348 fprintf(stderr, "Masking all X11 sockets except %s\n", x11file); 1353 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 1354 // This directory must be mode 1777
1359 if (mount("tmpfs", "/tmp/.X11-unix", "tmpfs", 1355 if (mount("tmpfs", "/tmp/.X11-unix", "tmpfs",
1360 MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_STRICTATIME, 1356 MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_STRICTATIME,
1361 "mode=1777,uid=0,gid=0") < 0) 1357 "mode=1777,uid=0,gid=0") < 0)
1362 errExit("mounting tmpfs on /tmp/.X11-unix"); 1358 errExit("mounting tmpfs on /tmp/.X11-unix");
1359 selinux_relabel_path("/tmp/.X11-unix", "/tmp/.X11-unix");
1363 fs_logger("tmpfs /tmp/.X11-unix"); 1360 fs_logger("tmpfs /tmp/.X11-unix");
1364 1361
1365 // create an empty root-owned file which will have the desired socket bind-mounted over it 1362 // 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); 1363 int dst = open(x11file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR | S_IWUSR);
1367 if (fd < 0) 1364 if (dst < 0)
1368 errExit(x11file); 1365 errExit("open");
1369 close(fd);
1370 1366
1371 // the mount source is under control of the user, so be careful and 1367 if (bind_mount_by_fd(src, dst))
1372 // mount without following symbolic links, using a file descriptor
1373 char *wx11file;
1374 if (asprintf(&wx11file, "%s/X%d", RUN_WHITELIST_X11_DIR, display) == -1)
1375 errExit("asprintf");
1376 fd = safer_openat(-1, 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");
1389 if (mount(proc, x11file, NULL, MS_BIND|MS_REC, NULL) < 0)
1390 errExit("mount bind"); 1368 errExit("mount bind");
1369 close(src);
1370 close(dst);
1391 fs_logger2("whitelist", x11file); 1371 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); 1372 free(x11file);
1401#endif 1373#endif
1402} 1374}
diff --git a/src/firemon/interface.c b/src/firemon/interface.c
index e04b6f431..b93d4a5a2 100644
--- a/src/firemon/interface.c
+++ b/src/firemon/interface.c
@@ -33,6 +33,10 @@
33//#include <net/route.h> 33//#include <net/route.h>
34//#include <linux/if_bridge.h> 34//#include <linux/if_bridge.h>
35 35
36#ifdef HAVE_GCOV
37#include <gcov.h>
38#endif
39
36// print IP addresses for all interfaces 40// print IP addresses for all interfaces
37static void net_ifprint(void) { 41static void net_ifprint(void) {
38 uint32_t ip; 42 uint32_t ip;
diff --git a/src/firemon/netstats.c b/src/firemon/netstats.c
index 850959eb3..23d228e26 100644
--- a/src/firemon/netstats.c
+++ b/src/firemon/netstats.c
@@ -24,6 +24,10 @@
24#include <sys/stat.h> 24#include <sys/stat.h>
25#include <unistd.h> 25#include <unistd.h>
26 26
27#ifdef HAVE_GCOV
28#include <gcov.h>
29#endif
30
27#define MAXBUF 4096 31#define MAXBUF 4096
28 32
29// ip -s link: device stats 33// ip -s link: device stats
diff --git a/src/firemon/procevent.c b/src/firemon/procevent.c
index 8085d2d29..4e809681e 100644
--- a/src/firemon/procevent.c
+++ b/src/firemon/procevent.c
@@ -30,6 +30,10 @@
30#include <fcntl.h> 30#include <fcntl.h>
31#include <sys/uio.h> 31#include <sys/uio.h>
32 32
33#ifdef HAVE_GCOV
34#include <gcov.h>
35#endif
36
33#define PIDS_BUFLEN 4096 37#define PIDS_BUFLEN 4096
34#define SERVER_PORT 889 // 889-899 is left unassigned by IANA 38#define SERVER_PORT 889 // 889-899 is left unassigned by IANA
35 39
diff --git a/src/firemon/top.c b/src/firemon/top.c
index a25e3c0d8..9d6f34991 100644
--- a/src/firemon/top.c
+++ b/src/firemon/top.c
@@ -24,6 +24,10 @@
24#include <sys/stat.h> 24#include <sys/stat.h>
25#include <unistd.h> 25#include <unistd.h>
26 26
27#ifdef HAVE_GCOV
28#include <gcov.h>
29#endif
30
27static unsigned pgs_rss = 0; 31static unsigned pgs_rss = 0;
28static unsigned pgs_shared = 0; 32static unsigned pgs_shared = 0;
29static unsigned clocktick = 0; 33static unsigned clocktick = 0;
diff --git a/src/include/rundefs.h b/src/include/rundefs.h
index a172dd511..3db750da3 100644
--- a/src/include/rundefs.h
+++ b/src/include/rundefs.h
@@ -79,12 +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
88#define RUN_XAUTHORITY_FILE RUN_MNT_DIR "/.Xauthority" // private options 84#define RUN_XAUTHORITY_FILE RUN_MNT_DIR "/.Xauthority" // private options
89#define RUN_XAUTH_FILE RUN_MNT_DIR "/xauth" // x11=xorg 85#define RUN_XAUTH_FILE RUN_MNT_DIR "/xauth" // x11=xorg
90#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/jailcheck/access.c b/src/jailcheck/access.c
index c18d64a82..3c2f46495 100644
--- a/src/jailcheck/access.c
+++ b/src/jailcheck/access.c
@@ -36,7 +36,7 @@ void access_setup(const char *directory) {
36 assert(user_home_dir); 36 assert(user_home_dir);
37 37
38 if (files_cnt >= MAX_TEST_FILES) { 38 if (files_cnt >= MAX_TEST_FILES) {
39 fprintf(stderr, "Error: maximum number of test directories exceded\n"); 39 fprintf(stderr, "Error: maximum number of test directories exceeded\n");
40 exit(1); 40 exit(1);
41 } 41 }
42 42
diff --git a/src/jailcheck/jailcheck.h b/src/jailcheck/jailcheck.h
index 32be1c978..be3104da3 100644
--- a/src/jailcheck/jailcheck.h
+++ b/src/jailcheck/jailcheck.h
@@ -53,6 +53,8 @@ void apparmor_test(pid_t pid);
53// seccomp.c 53// seccomp.c
54void seccomp_test(pid_t pid); 54void seccomp_test(pid_t pid);
55 55
56// network.c
57void network_test(void);
56// utils.c 58// utils.c
57char *get_sudo_user(void); 59char *get_sudo_user(void);
58char *get_homedir(const char *user, uid_t *uid, gid_t *gid); 60char *get_homedir(const char *user, uid_t *uid, gid_t *gid);
diff --git a/src/jailcheck/main.c b/src/jailcheck/main.c
index 4d642bf96..812ac5808 100644
--- a/src/jailcheck/main.c
+++ b/src/jailcheck/main.c
@@ -157,6 +157,7 @@ int main(int argc, char **argv) {
157 seccomp_test(pid); 157 seccomp_test(pid);
158 fflush(0); 158 fflush(0);
159 159
160 // filesystem tests
160 pid_t child = fork(); 161 pid_t child = fork();
161 if (child == -1) 162 if (child == -1)
162 errExit("fork"); 163 errExit("fork");
@@ -185,6 +186,28 @@ int main(int argc, char **argv) {
185 } 186 }
186 int status; 187 int status;
187 wait(&status); 188 wait(&status);
189
190 // network test
191 child = fork();
192 if (child == -1)
193 errExit("fork");
194 if (child == 0) {
195 int rv = join_namespace(pid, "net");
196 if (rv == 0)
197 network_test();
198 else {
199 printf(" Error: I cannot join the process network stack\n");
200 exit(1);
201 }
202
203 // drop privileges in order not to trigger cleanup()
204 if (setgid(user_gid) != 0)
205 errExit("setgid");
206 if (setuid(user_uid) != 0)
207 errExit("setuid");
208 return 0;
209 }
210 wait(&status);
188 } 211 }
189 } 212 }
190 213
diff --git a/src/jailcheck/network.c b/src/jailcheck/network.c
new file mode 100644
index 000000000..636344e77
--- /dev/null
+++ b/src/jailcheck/network.c
@@ -0,0 +1,56 @@
1/*
2 * Copyright (C) 2014-2021 Firejail Authors
3 *
4 * This file is part of firejail project
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
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.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/
20#include "jailcheck.h"
21#include <netdb.h>
22#include <arpa/inet.h>
23#include <ifaddrs.h>
24#include <net/if.h>
25#include <linux/connector.h>
26#include <linux/netlink.h>
27#include <linux/if_link.h>
28#include <linux/sockios.h>
29#include <sys/ioctl.h>
30
31
32void network_test(void) {
33 // I am root running in a network namespace
34 struct ifaddrs *ifaddr, *ifa;
35 int found = 0;
36
37 // walk through the linked list
38 if (getifaddrs(&ifaddr) == -1)
39 errExit("getifaddrs");
40 for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
41 if (strcmp(ifa->ifa_name, "lo") == 0)
42 continue;
43 found = 1;
44 break;
45 }
46
47 freeifaddrs(ifaddr);
48
49 if (found)
50 printf(" Networking: enabled\n");
51 else
52 printf(" Networking: disabled\n");
53}
54
55
56
diff --git a/src/jailcheck/sysfiles.c b/src/jailcheck/sysfiles.c
index caeb580af..9a0d6350e 100644
--- a/src/jailcheck/sysfiles.c
+++ b/src/jailcheck/sysfiles.c
@@ -34,7 +34,7 @@ void sysfiles_setup(const char *file) {
34 assert(file); 34 assert(file);
35 35
36 if (files_cnt >= MAX_TEST_FILES) { 36 if (files_cnt >= MAX_TEST_FILES) {
37 fprintf(stderr, "Error: maximum number of system test files exceded\n"); 37 fprintf(stderr, "Error: maximum number of system test files exceeded\n");
38 exit(1); 38 exit(1);
39 } 39 }
40 40
diff --git a/src/lib/ldd_utils.c b/src/lib/ldd_utils.c
index cd60d74e4..c5dde85b0 100644
--- a/src/lib/ldd_utils.c
+++ b/src/lib/ldd_utils.c
@@ -50,7 +50,7 @@ int is_lib_64(const char *exe) {
50 unsigned char buf[EI_NIDENT]; 50 unsigned char buf[EI_NIDENT];
51 ssize_t len = 0; 51 ssize_t len = 0;
52 while (len < EI_NIDENT) { 52 while (len < EI_NIDENT) {
53 ssize_t sz = read(fd, buf, EI_NIDENT); 53 ssize_t sz = read(fd, buf + len, EI_NIDENT - len);
54 if (sz <= 0) 54 if (sz <= 0)
55 goto doexit; 55 goto doexit;
56 len += sz; 56 len += sz;
diff --git a/src/man/firejail-profile.txt b/src/man/firejail-profile.txt
index 12e841af5..db58e0910 100644
--- a/src/man/firejail-profile.txt
+++ b/src/man/firejail-profile.txt
@@ -420,7 +420,7 @@ Make directory or file read-only.
420Make directory or file read-write. 420Make directory or file read-write.
421.TP 421.TP
422\fBtmpfs directory 422\fBtmpfs directory
423Mount an empty tmpfs filesystem on top of directory. This option is available only when running the sandbox as root. 423Mount an empty tmpfs filesystem on top of directory. Directories outside user home or not owned by the user are not allowed. Sandboxes running as root are exempt from these restrictions.
424.TP 424.TP
425\fBtracelog 425\fBtracelog
426Blacklist violations logged to syslog. 426Blacklist violations logged to syslog.
@@ -428,8 +428,9 @@ Blacklist violations logged to syslog.
428\fBwhitelist file_or_directory 428\fBwhitelist file_or_directory
429Whitelist directory or file. A temporary file system is mounted on the top directory, and the 429Whitelist directory or file. A temporary file system is mounted on the top directory, and the
430whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, 430whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent,
431everything else is discarded when the sandbox is closed. The top directory could be 431everything else is discarded when the sandbox is closed. The top directory can be
432user home, /dev, /etc, /media, /mnt, /opt, /srv, /sys/module, /usr/share, /var, and /tmp. 432all directories in / (except /proc and /sys), /sys/module, /run/user/$UID, $HOME and
433all directories in /usr.
433.br 434.br
434 435
435.br 436.br
diff --git a/src/man/firejail.txt b/src/man/firejail.txt
index c72a1dbd8..d18811316 100644
--- a/src/man/firejail.txt
+++ b/src/man/firejail.txt
@@ -2568,14 +2568,13 @@ Kill the sandbox automatically after the time has elapsed. The time is specified
2568$ firejail \-\-timeout=01:30:00 firefox 2568$ firejail \-\-timeout=01:30:00 firefox
2569.TP 2569.TP
2570\fB\-\-tmpfs=dirname 2570\fB\-\-tmpfs=dirname
2571Mount a writable tmpfs filesystem on directory dirname. This option is available only when running the sandbox as root. 2571Mount a writable tmpfs filesystem on directory dirname. Directories outside user home or not owned by the user are not allowed. Sandboxes running as root are exempt from these restrictions. File globbing is supported, see \fBFILE GLOBBING\fR section for more details.
2572File globbing is supported, see \fBFILE GLOBBING\fR section for more details.
2573.br 2572.br
2574 2573
2575.br 2574.br
2576Example: 2575Example:
2577.br 2576.br
2578# firejail \-\-tmpfs=/var 2577$ firejail \-\-tmpfs=~/.local/share
2579.TP 2578.TP
2580\fB\-\-top 2579\fB\-\-top
2581Monitor the most CPU-intensive sandboxes, see \fBMONITORING\fR section for more details. 2580Monitor the most CPU-intensive sandboxes, see \fBMONITORING\fR section for more details.
@@ -2725,8 +2724,9 @@ $ firejail \-\-net=br0 --veth-name=if0
2725\fB\-\-whitelist=dirname_or_filename 2724\fB\-\-whitelist=dirname_or_filename
2726Whitelist directory or file. A temporary file system is mounted on the top directory, and the 2725Whitelist directory or file. A temporary file system is mounted on the top directory, and the
2727whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, 2726whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent,
2728everything else is discarded when the sandbox is closed. The top directory could be 2727everything else is discarded when the sandbox is closed. The top directory can be
2729user home, /dev, /etc, /media, /mnt, /opt, /run/user/$UID, /srv, /sys/module, /tmp, /usr/share and /var. 2728all directories in / (except /proc and /sys), /sys/module, /run/user/$UID, $HOME and
2729all directories in /usr.
2730.br 2730.br
2731 2731
2732.br 2732.br
diff --git a/src/man/jailcheck.txt b/src/man/jailcheck.txt
index c80e305cc..483f47fb9 100644
--- a/src/man/jailcheck.txt
+++ b/src/man/jailcheck.txt
@@ -23,6 +23,8 @@ them from inside the sandbox.
23.TP 23.TP
24\fB5. Seccomp test 24\fB5. Seccomp test
25.TP 25.TP
26\fB6. Networking test
27.TP
26The program is started as root using sudo. 28The program is started as root using sudo.
27 29
28.SH OPTIONS 30.SH OPTIONS
@@ -56,6 +58,8 @@ $ sudo jailcheck
56.br 58.br
57 Warning: I can run programs in /home/netblue 59 Warning: I can run programs in /home/netblue
58.br 60.br
61 Networking: disabled
62.br
59 63
60.br 64.br
612055:netblue::firejail /usr/bin/ssh -X netblue@x.y.z.net 652055:netblue::firejail /usr/bin/ssh -X netblue@x.y.z.net
@@ -64,12 +68,16 @@ $ sudo jailcheck
64.br 68.br
65 Warning: I can read ~/.ssh 69 Warning: I can read ~/.ssh
66.br 70.br
71 Networking: enabled
72.br
67 73
68.br 74.br
692186:netblue:libreoffice:firejail --appimage /opt/LibreOffice-fresh.appimage 752186:netblue:libreoffice:firejail --appimage /opt/LibreOffice-fresh.appimage
70.br 76.br
71 Virtual dirs: /tmp, /var/tmp, /dev, 77 Virtual dirs: /tmp, /var/tmp, /dev,
72.br 78.br
79 Networking: enabled
80.br
73 81
74.br 82.br
7526090:netblue::/usr/bin/firejail /opt/firefox/firefox 8326090:netblue::/usr/bin/firejail /opt/firefox/firefox
@@ -78,6 +86,8 @@ $ sudo jailcheck
78.br 86.br
79 /run/user/1000, 87 /run/user/1000,
80.br 88.br
89 Networking: enabled
90.br
81 91
82.br 92.br
8326160:netblue:tor:firejail --private=~/tor-browser_en-US ./start-tor 9326160:netblue:tor:firejail --private=~/tor-browser_en-US ./start-tor
@@ -90,6 +100,8 @@ $ sudo jailcheck
90.br 100.br
91 Warning: I can run programs in /home/netblue 101 Warning: I can run programs in /home/netblue
92.br 102.br
103 Networking: enabled
104.br
93 105
94 106
95.SH LICENSE 107.SH LICENSE