diff options
author | Reiner Herrmann <reiner@reiner-h.de> | 2021-06-21 23:10:09 +0200 |
---|---|---|
committer | Reiner Herrmann <reiner@reiner-h.de> | 2021-06-21 23:10:09 +0200 |
commit | 0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2 (patch) | |
tree | 0875693a6ceef54818511972601d587a09a1aab4 /src | |
parent | style: grammer and codestyle improvements (diff) | |
parent | creating alpine.profile (#4350) (diff) | |
download | firejail-0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2.tar.gz firejail-0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2.tar.zst firejail-0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2.zip |
Merge branch 'master' into kuesji/master
Diffstat (limited to 'src')
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 | ||
341 | errexit: | 348 | errexit: |
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 | |||
38 | akonadi_control | 38 | akonadi_control |
39 | akregator | 39 | akregator |
40 | alacarte | 40 | alacarte |
41 | alpine | ||
42 | alpinef | ||
41 | amarok | 43 | amarok |
42 | amule | 44 | amule |
43 | amuled | 45 | amuled |
@@ -167,6 +169,7 @@ cvlc | |||
167 | cyberfox | 169 | cyberfox |
168 | darktable | 170 | darktable |
169 | dconf-editor | 171 | dconf-editor |
172 | ddgr | ||
170 | ddgtk | 173 | ddgtk |
171 | deadbeef | 174 | deadbeef |
172 | deluge | 175 | deluge |
@@ -350,6 +353,7 @@ google-chrome-unstable | |||
350 | google-earth | 353 | google-earth |
351 | google-earth-pro | 354 | google-earth-pro |
352 | google-play-music-desktop-player | 355 | google-play-music-desktop-player |
356 | googler | ||
353 | gpa | 357 | gpa |
354 | gpicview | 358 | gpicview |
355 | gpredict | 359 | gpredict |
@@ -452,6 +456,7 @@ liferea | |||
452 | lightsoff | 456 | lightsoff |
453 | lincity-ng | 457 | lincity-ng |
454 | links | 458 | links |
459 | links2 | ||
455 | linphone | 460 | linphone |
456 | lmms | 461 | lmms |
457 | lobase | 462 | lobase |
@@ -491,6 +496,7 @@ mathematica | |||
491 | matrix-mirage | 496 | matrix-mirage |
492 | mattermost-desktop | 497 | mattermost-desktop |
493 | mcabber | 498 | mcabber |
499 | mcomix | ||
494 | mediainfo | 500 | mediainfo |
495 | mediathekview | 501 | mediathekview |
496 | megaglest | 502 | megaglest |
@@ -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) |
653 | qbittorrent | 659 | qbittorrent |
660 | qcomicbook | ||
654 | qemu-launcher | 661 | qemu-launcher |
655 | qgis | 662 | qgis |
656 | qlipper | 663 | qlipper |
@@ -871,6 +878,7 @@ xfce4-notes | |||
871 | xfce4-screenshooter | 878 | xfce4-screenshooter |
872 | xiphos | 879 | xiphos |
873 | xlinks | 880 | xlinks |
881 | xlinks2 | ||
874 | xmms | 882 | xmms |
875 | xmr-stak | 883 | xmr-stak |
876 | xonotic | 884 | xonotic |
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 | |||
31 | static char *devloop = NULL; // device file | 35 | static char *devloop = NULL; // device file |
32 | static long unsigned size = 0; // offset into appimage file | 36 | static 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 |
35 | static void err_loop(void) { | 40 | static 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 | ||
47 | int 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 | |||
41 | void appimage_set(const char *appimage) { | 76 | void 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 | ||
295 | void print_compiletime_support(void) { | 298 | void 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 |
34 | void fs_check_chroot_dir(void) { | 37 | void 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 | ||
29 | static int cmdline_length(int argc, char **argv, int index) { | 29 | static 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 | ||
67 | static void quote_cmdline(char *command_line, char *window_title, int len, int argc, char **argv, int index) { | 68 | static 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 | ||
137 | void build_cmdline(char **command_line, char **window_title, int argc, char **argv, int index) { | 139 | void 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 | ||
164 | void build_appimage_cmdline(char **command_line, char **window_title, int argc, char **argv, int index) { | 166 | void 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_ | |||
504 | void touch_file_as_user(const char *fname, mode_t mode); | 513 | void touch_file_as_user(const char *fname, mode_t mode); |
505 | int is_dir(const char *fname); | 514 | int is_dir(const char *fname); |
506 | int is_link(const char *fname); | 515 | int is_link(const char *fname); |
516 | char *realpath_as_user(const char *fname); | ||
517 | int stat_as_user(const char *fname, struct stat *s); | ||
518 | int lstat_as_user(const char *fname, struct stat *s); | ||
507 | void trim_trailing_slash_or_dot(char *path); | 519 | void trim_trailing_slash_or_dot(char *path); |
508 | char *line_remove_spaces(const char *buf); | 520 | char *line_remove_spaces(const char *buf); |
509 | char *split_comma(char *str); | 521 | char *split_comma(char *str); |
@@ -527,11 +539,15 @@ unsigned extract_timeout(const char *str); | |||
527 | void disable_file_or_dir(const char *fname); | 539 | void disable_file_or_dir(const char *fname); |
528 | void disable_file_path(const char *path, const char *file); | 540 | void disable_file_path(const char *path, const char *file); |
529 | int safer_openat(int dirfd, const char *path, int flags); | 541 | int safer_openat(int dirfd, const char *path, int flags); |
542 | int remount_by_fd(int dst, unsigned long mountflags); | ||
543 | int bind_mount_by_fd(int src, int dst); | ||
544 | int bind_mount_path_to_fd(const char *srcname, int dst); | ||
545 | int bind_mount_fd_to_path(int src, const char *destname); | ||
530 | int has_handler(pid_t pid, int signal); | 546 | int has_handler(pid_t pid, int signal); |
531 | void enter_network_namespace(pid_t pid); | 547 | void enter_network_namespace(pid_t pid); |
532 | int read_pid(const char *name, pid_t *pid); | 548 | int read_pid(const char *name, pid_t *pid); |
533 | pid_t require_pid(const char *name); | 549 | pid_t require_pid(const char *name); |
534 | void check_homedir(void); | 550 | void 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); | |||
796 | void print_compiletime_support(void); | 816 | void print_compiletime_support(void); |
797 | 817 | ||
798 | // appimage.c | 818 | // appimage.c |
819 | int appimage_find_profile(const char *archive); | ||
799 | void appimage_set(const char *appimage_path); | 820 | void appimage_set(const char *appimage_path); |
800 | void appimage_mount(void); | 821 | void appimage_mount(void); |
801 | void appimage_clear(void); | 822 | void appimage_clear(void); |
@@ -804,8 +825,8 @@ void appimage_clear(void); | |||
804 | long unsigned int appimage2_size(int fd); | 825 | long unsigned int appimage2_size(int fd); |
805 | 826 | ||
806 | // cmdline.c | 827 | // cmdline.c |
807 | void build_cmdline(char **command_line, char **window_title, int argc, char **argv, int index); | 828 | void build_cmdline(char **command_line, char **window_title, int argc, char **argv, int index, bool want_extra_quotes); |
808 | void build_appimage_cmdline(char **command_line, char **window_title, int argc, char **argv, int index); | 829 | void 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 | ||
57 | typedef enum { | ||
58 | UNSUCCESSFUL, | ||
59 | SUCCESSFUL | ||
60 | } LAST_DISABLE_OPERATION; | ||
61 | LAST_DISABLE_OPERATION last_disable = UNSUCCESSFUL; | ||
62 | |||
63 | static void disable_file(OPERATION op, const char *filename) { | 61 | static 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 |
192 | static void globbing(OPERATION op, const char *pattern, const char *noblacklist[], size_t noblacklist_len) { | 210 | static 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 |
451 | void fs_tmpfs(const char *dir, unsigned check_owner) { | 471 | void 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 |
492 | static void fs_remount_simple(const char *path, OPERATION op) { | 514 | static 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 | ||
591 | out: | 623 | out: |
@@ -594,7 +626,9 @@ out: | |||
594 | 626 | ||
595 | // remount recursively; requires a resolved path | 627 | // remount recursively; requires a resolved path |
596 | static void fs_remount_rec(const char *dir, OPERATION op) { | 628 | static 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 |
633 | void fs_remount(const char *path, OPERATION op, int rec) { | 667 | void 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 |
646 | void fs_mnt(const int enforce) { | 691 | void 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 |
783 | void disable_config(void) { | 823 | void 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 | ||
809 | void fs_basic_fs(void) { | 842 | void 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 |
863 | char *fs_check_overlay_dir(const char *subdirname, int allow_reuse) { | 898 | char *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 |
1223 | void fs_private_tmp(void) { | 1259 | void 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) { | |||
187 | static void process_dev_shm(void) { | 187 | static 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 | //*********************************************************************************** |
440 | static char *check_dir_or_file(const char *name) { | 433 | static 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 | ||
500 | static void duplicate(char *name) { | 494 | static 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 | ||
29 | static void check(const char *fname) { | 32 | static 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 | ||
269 | static void globbing(const char *pattern) { | 259 | static 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 | ||
308 | static void tmpfs_topdirs(const TopDir *topdirs) { | 298 | static 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 | ||
153 | static void extract_nogroups(pid_t pid) { | 153 | static 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 |
35 | static uid_t c_uid = 0; | 39 | static uid_t c_uid = 0; |
36 | static char *c_uid_name = NULL; | 40 | static 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 |
151 | static char *resolve_hardcoded(char *entries[]) { | 151 | static 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) { | |||
862 | char *guess_shell(void) { | 866 | char *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. |
155 | int get_mount_id(const char *path) { | 155 | int 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 | |||
25 | extern char *xephyr_screen; | 30 | extern 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 | ||
78 | static 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) |
86 | void pulseaudio_init(void) { | 79 | void 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 | |||
24 | void set_rlimits(void) { | 28 | void 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 | ||
53 | static int force_nonewprivs = 0; | 56 | static 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 | ||
267 | int sbox_run_v(unsigned filtermask, char * const arg[]) { | 267 | int 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 | ||
528 | char *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 | |||
546 | int 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 | |||
564 | int 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 |
505 | void trim_trailing_slash_or_dot(char *path) { | 584 | void 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 | ||
931 | int remove_overlay_directory(void) { | 1012 | int 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) { | |||
1014 | int create_empty_dir_as_user(const char *dir, mode_t mode) { | 1097 | int 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 | ||
1151 | void disable_file_or_dir(const char *fname) { | 1233 | void 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 | ||
1168 | void disable_file_path(const char *path, const char *file) { | 1264 | void 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 | ||
1348 | int 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 | |||
1361 | int 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 | |||
1376 | int 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 | |||
1389 | int 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 | |||
1252 | int has_handler(pid_t pid, int signal) { | 1402 | int 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 | ||
1361 | void check_homedir(void) { | 1511 | void 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) { | |||
204 | void x11_start_xvfb(int argc, char **argv) { | 204 | void 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) { | |||
427 | void x11_start_xephyr(int argc, char **argv) { | 426 | void 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() { | |||
701 | static void __attribute__((noreturn)) x11_start_xpra_old(int argc, char **argv, int display, char *display_str) { | 699 | static 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 |
37 | static void net_ifprint(void) { | 41 | static 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 | |||
27 | static unsigned pgs_rss = 0; | 31 | static unsigned pgs_rss = 0; |
28 | static unsigned pgs_shared = 0; | 32 | static unsigned pgs_shared = 0; |
29 | static unsigned clocktick = 0; | 33 | static 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 |
54 | void seccomp_test(pid_t pid); | 54 | void seccomp_test(pid_t pid); |
55 | 55 | ||
56 | // network.c | ||
57 | void network_test(void); | ||
56 | // utils.c | 58 | // utils.c |
57 | char *get_sudo_user(void); | 59 | char *get_sudo_user(void); |
58 | char *get_homedir(const char *user, uid_t *uid, gid_t *gid); | 60 | char *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 | |||
32 | void 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. | |||
420 | Make directory or file read-write. | 420 | Make directory or file read-write. |
421 | .TP | 421 | .TP |
422 | \fBtmpfs directory | 422 | \fBtmpfs directory |
423 | Mount an empty tmpfs filesystem on top of directory. This option is available only when running the sandbox as root. | 423 | Mount 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 |
426 | Blacklist violations logged to syslog. | 426 | Blacklist violations logged to syslog. |
@@ -428,8 +428,9 @@ Blacklist violations logged to syslog. | |||
428 | \fBwhitelist file_or_directory | 428 | \fBwhitelist file_or_directory |
429 | Whitelist directory or file. A temporary file system is mounted on the top directory, and the | 429 | Whitelist directory or file. A temporary file system is mounted on the top directory, and the |
430 | whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, | 430 | whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, |
431 | everything else is discarded when the sandbox is closed. The top directory could be | 431 | everything else is discarded when the sandbox is closed. The top directory can be |
432 | user home, /dev, /etc, /media, /mnt, /opt, /srv, /sys/module, /usr/share, /var, and /tmp. | 432 | all directories in / (except /proc and /sys), /sys/module, /run/user/$UID, $HOME and |
433 | all directories in /usr. | ||
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 |
2571 | Mount a writable tmpfs filesystem on directory dirname. This option is available only when running the sandbox as root. | 2571 | Mount 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. |
2572 | File globbing is supported, see \fBFILE GLOBBING\fR section for more details. | ||
2573 | .br | 2572 | .br |
2574 | 2573 | ||
2575 | .br | 2574 | .br |
2576 | Example: | 2575 | Example: |
2577 | .br | 2576 | .br |
2578 | # firejail \-\-tmpfs=/var | 2577 | $ firejail \-\-tmpfs=~/.local/share |
2579 | .TP | 2578 | .TP |
2580 | \fB\-\-top | 2579 | \fB\-\-top |
2581 | Monitor the most CPU-intensive sandboxes, see \fBMONITORING\fR section for more details. | 2580 | Monitor 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 |
2726 | Whitelist directory or file. A temporary file system is mounted on the top directory, and the | 2725 | Whitelist directory or file. A temporary file system is mounted on the top directory, and the |
2727 | whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, | 2726 | whitelisted files are mount-binded inside. Modifications to whitelisted files are persistent, |
2728 | everything else is discarded when the sandbox is closed. The top directory could be | 2727 | everything else is discarded when the sandbox is closed. The top directory can be |
2729 | user home, /dev, /etc, /media, /mnt, /opt, /run/user/$UID, /srv, /sys/module, /tmp, /usr/share and /var. | 2728 | all directories in / (except /proc and /sys), /sys/module, /run/user/$UID, $HOME and |
2729 | all 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 | ||
26 | The program is started as root using sudo. | 28 | The 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 |
61 | 2055:netblue::firejail /usr/bin/ssh -X netblue@x.y.z.net | 65 | 2055: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 |
69 | 2186:netblue:libreoffice:firejail --appimage /opt/LibreOffice-fresh.appimage | 75 | 2186: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 |
75 | 26090:netblue::/usr/bin/firejail /opt/firefox/firefox | 83 | 26090: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 |
83 | 26160:netblue:tor:firejail --private=~/tor-browser_en-US ./start-tor | 93 | 26160: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 |