aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar netblue30 <netblue30@protonmail.com>2021-05-18 09:00:45 -0500
committerLibravatar GitHub <noreply@github.com>2021-05-18 09:00:45 -0500
commited7db097bd4eb387cd4583a71ba76cf126e5d8c7 (patch)
tree58cb4dd296e6ccdab0eb68e972f8919ebcdb5731
parentFix #4282 -- Unable to open X display when running firejail chromium command (diff)
parentadd /run whitelist support (diff)
downloadfirejail-ed7db097bd4eb387cd4583a71ba76cf126e5d8c7.tar.gz
firejail-ed7db097bd4eb387cd4583a71ba76cf126e5d8c7.tar.zst
firejail-ed7db097bd4eb387cd4583a71ba76cf126e5d8c7.zip
Merge pull request #4229 from smitsohu/whitelist2
Whitelist2
-rw-r--r--etc/firejail.config4
-rw-r--r--src/firejail/checkcfg.c26
-rw-r--r--src/firejail/chroot.c8
-rw-r--r--src/firejail/dbus.c2
-rw-r--r--src/firejail/firejail.h29
-rw-r--r--src/firejail/fs.c10
-rw-r--r--src/firejail/fs_home.c6
-rw-r--r--src/firejail/fs_whitelist.c1336
-rw-r--r--src/firejail/pulseaudio.c2
-rw-r--r--src/firejail/restrict_users.c2
-rw-r--r--src/firejail/util.c69
-rw-r--r--src/firejail/x11.c10
-rw-r--r--src/include/rundefs.h12
13 files changed, 540 insertions, 976 deletions
diff --git a/etc/firejail.config b/etc/firejail.config
index 731e744dd..9dd33b5ed 100644
--- a/etc/firejail.config
+++ b/etc/firejail.config
@@ -116,6 +116,10 @@
116# Enable or disable whitelisting support, default enabled. 116# Enable or disable whitelisting support, default enabled.
117# whitelist yes 117# whitelist yes
118 118
119# Disable whitelist top level directories, in addition to those
120# that are disabled out of the box. None by default; this is an example.
121# whitelist-disable-topdir /etc,/usr/etc
122
119# Enable or disable X11 sandboxing support, default enabled. 123# Enable or disable X11 sandboxing support, default enabled.
120# x11 yes 124# x11 yes
121 125
diff --git a/src/firejail/checkcfg.c b/src/firejail/checkcfg.c
index d6643cf3a..614b144e5 100644
--- a/src/firejail/checkcfg.c
+++ b/src/firejail/checkcfg.c
@@ -35,6 +35,7 @@ char *xvfb_extra_params = "";
35char *netfilter_default = NULL; 35char *netfilter_default = NULL;
36unsigned long join_timeout = 5000000; // microseconds 36unsigned long join_timeout = 5000000; // microseconds
37char *config_seccomp_error_action_str = "EPERM"; 37char *config_seccomp_error_action_str = "EPERM";
38char **whitelist_reject_topdirs = NULL;
38 39
39int checkcfg(int val) { 40int checkcfg(int val) {
40 assert(val < CFG_MAX); 41 assert(val < CFG_MAX);
@@ -238,6 +239,31 @@ int checkcfg(int val) {
238 errExit("strdup"); 239 errExit("strdup");
239 } 240 }
240 241
242 else if (strncmp(ptr, "whitelist-disable-topdir ", 25) == 0) {
243 char *str = strdup(ptr + 25);
244 if (!str)
245 errExit("strdup");
246
247 size_t cnt = 0;
248 size_t sz = 4;
249 whitelist_reject_topdirs = malloc(sz * sizeof(char *));
250 if (!whitelist_reject_topdirs)
251 errExit("malloc");
252
253 char *tok = strtok(str, ",");
254 while (tok) {
255 whitelist_reject_topdirs[cnt++] = tok;
256 if (cnt >= sz) {
257 sz *= 2;
258 whitelist_reject_topdirs = realloc(whitelist_reject_topdirs, sz * sizeof(char *));
259 if (!whitelist_reject_topdirs)
260 errExit("realloc");
261 }
262 tok = strtok(NULL, ",");
263 }
264 whitelist_reject_topdirs[cnt] = NULL;
265 }
266
241 else 267 else
242 goto errout; 268 goto errout;
243 269
diff --git a/src/firejail/chroot.c b/src/firejail/chroot.c
index d7e96cf4c..757ffb1f7 100644
--- a/src/firejail/chroot.c
+++ b/src/firejail/chroot.c
@@ -131,9 +131,9 @@ void fs_chroot(const char *rootdir) {
131 assert(rootdir); 131 assert(rootdir);
132 132
133 // fails if there is any symlink or if rootdir is not a directory 133 // fails if there is any symlink or if rootdir is not a directory
134 int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 134 int parentfd = safer_openat(-1, rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
135 if (parentfd == -1) 135 if (parentfd == -1)
136 errExit("safe_fd"); 136 errExit("safer_openat");
137 // rootdir has to be owned by root and is not allowed to be generally writable, 137 // rootdir has to be owned by root and is not allowed to be generally writable,
138 // this also excludes /tmp and friends 138 // this also excludes /tmp and friends
139 struct stat s; 139 struct stat s;
@@ -215,12 +215,12 @@ void fs_chroot(const char *rootdir) {
215 215
216 if (arg_debug) 216 if (arg_debug)
217 printf("Mounting %s on chroot %s\n", orig_pulse, orig_pulse); 217 printf("Mounting %s on chroot %s\n", orig_pulse, orig_pulse);
218 int src = safe_fd(orig_pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 218 int src = safer_openat(-1, orig_pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
219 if (src == -1) { 219 if (src == -1) {
220 fprintf(stderr, "Error: cannot open %s\n", orig_pulse); 220 fprintf(stderr, "Error: cannot open %s\n", orig_pulse);
221 exit(1); 221 exit(1);
222 } 222 }
223 int dst = safe_fd(pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 223 int dst = safer_openat(-1, pulse, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
224 if (dst == -1) { 224 if (dst == -1) {
225 fprintf(stderr, "Error: cannot open %s\n", pulse); 225 fprintf(stderr, "Error: cannot open %s\n", pulse);
226 exit(1); 226 exit(1);
diff --git a/src/firejail/dbus.c b/src/firejail/dbus.c
index 658b84537..b8aa2c974 100644
--- a/src/firejail/dbus.c
+++ b/src/firejail/dbus.c
@@ -416,7 +416,7 @@ void dbus_proxy_stop(void) {
416} 416}
417 417
418static void socket_overlay(char *socket_path, char *proxy_path) { 418static void socket_overlay(char *socket_path, char *proxy_path) {
419 int fd = safe_fd(proxy_path, O_PATH | O_NOFOLLOW | O_CLOEXEC); 419 int fd = safer_openat(-1, proxy_path, O_PATH | O_NOFOLLOW | O_CLOEXEC);
420 if (fd == -1) 420 if (fd == -1)
421 errExit("opening DBus proxy socket"); 421 errExit("opening DBus proxy socket");
422 struct stat s; 422 struct stat s;
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h
index ac2fd279e..1c1ad4e97 100644
--- a/src/firejail/firejail.h
+++ b/src/firejail/firejail.h
@@ -122,26 +122,22 @@ typedef struct interface_t {
122 uint8_t configured; 122 uint8_t configured;
123} Interface; 123} Interface;
124 124
125typedef struct topdir_t {
126 char *path;
127 int fd;
128} TopDir;
129
125typedef struct profile_entry_t { 130typedef struct profile_entry_t {
126 struct profile_entry_t *next; 131 struct profile_entry_t *next;
127 char *data; // command 132 char *data; // command
128 133
129 // whitelist command parameters 134 // whitelist command parameters
130 char *link; // link name - set if the file is a link 135 struct wparam_t {
131 enum { 136 char *file; // resolved file path
132 WLDIR_HOME = 1, // whitelist in home directory 137 char *link; // link path
133 WLDIR_TMP, // whitelist in /tmp directory 138 TopDir *top; // top level directory
134 WLDIR_MEDIA, // whitelist in /media directory 139 } *wparam;
135 WLDIR_MNT, // whitelist in /mnt directory 140
136 WLDIR_VAR, // whitelist in /var directory
137 WLDIR_DEV, // whitelist in /dev directory
138 WLDIR_OPT, // whitelist in /opt directory
139 WLDIR_SRV, // whitelist in /srv directory
140 WLDIR_ETC, // whitelist in /etc directory
141 WLDIR_SHARE, // whitelist in /usr/share directory
142 WLDIR_MODULE, // whitelist in /sys/module directory
143 WLDIR_RUN // whitelist in /run/user/$uid directory
144 } wldir;
145} ProfileEntry; 141} ProfileEntry;
146 142
147typedef struct config_t { 143typedef struct config_t {
@@ -529,7 +525,7 @@ void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid);
529unsigned extract_timeout(const char *str); 525unsigned extract_timeout(const char *str);
530void disable_file_or_dir(const char *fname); 526void disable_file_or_dir(const char *fname);
531void disable_file_path(const char *path, const char *file); 527void disable_file_path(const char *path, const char *file);
532int safe_fd(const char *path, int flags); 528int safer_openat(int dirfd, const char *path, int flags);
533int has_handler(pid_t pid, int signal); 529int has_handler(pid_t pid, int signal);
534void enter_network_namespace(pid_t pid); 530void enter_network_namespace(pid_t pid);
535int read_pid(const char *name, pid_t *pid); 531int read_pid(const char *name, pid_t *pid);
@@ -794,6 +790,7 @@ extern char *xvfb_extra_params;
794extern char *netfilter_default; 790extern char *netfilter_default;
795extern unsigned long join_timeout; 791extern unsigned long join_timeout;
796extern char *config_seccomp_error_action_str; 792extern char *config_seccomp_error_action_str;
793extern char **whitelist_reject_topdirs;
797 794
798int checkcfg(int val); 795int checkcfg(int val);
799void print_compiletime_support(void); 796void print_compiletime_support(void);
diff --git a/src/firejail/fs.c b/src/firejail/fs.c
index fc67a15f3..09de11de9 100644
--- a/src/firejail/fs.c
+++ b/src/firejail/fs.c
@@ -453,7 +453,7 @@ void fs_tmpfs(const char *dir, unsigned check_owner) {
453 if (arg_debug) 453 if (arg_debug)
454 printf("Mounting tmpfs on %s, check owner: %s\n", dir, (check_owner)? "yes": "no"); 454 printf("Mounting tmpfs on %s, check owner: %s\n", dir, (check_owner)? "yes": "no");
455 // get a file descriptor for dir, fails if there is any symlink 455 // get a file descriptor for dir, fails if there is any symlink
456 int fd = safe_fd(dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 456 int fd = safer_openat(-1, dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
457 if (fd == -1) 457 if (fd == -1)
458 errExit("while opening directory"); 458 errExit("while opening directory");
459 struct stat s; 459 struct stat s;
@@ -493,7 +493,7 @@ static void fs_remount_simple(const char *path, OPERATION op) {
493 assert(path); 493 assert(path);
494 494
495 // open path without following symbolic links 495 // open path without following symbolic links
496 int fd1 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); 496 int fd1 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
497 if (fd1 == -1) 497 if (fd1 == -1)
498 goto out; 498 goto out;
499 struct stat s1; 499 struct stat s1;
@@ -559,7 +559,7 @@ static void fs_remount_simple(const char *path, OPERATION op) {
559 559
560 // mount --bind -o remount,ro path 560 // mount --bind -o remount,ro path
561 // need to open path again without following symbolic links 561 // need to open path again without following symbolic links
562 int fd2 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); 562 int fd2 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC);
563 if (fd2 == -1) 563 if (fd2 == -1)
564 errExit("open"); 564 errExit("open");
565 struct stat s2; 565 struct stat s2;
@@ -992,9 +992,9 @@ void fs_overlayfs(void) {
992 char *firejail; 992 char *firejail;
993 if (asprintf(&firejail, "%s/.firejail", cfg.homedir) == -1) 993 if (asprintf(&firejail, "%s/.firejail", cfg.homedir) == -1)
994 errExit("asprintf"); 994 errExit("asprintf");
995 int fd = safe_fd(firejail, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 995 int fd = safer_openat(-1, firejail, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
996 if (fd == -1) 996 if (fd == -1)
997 errExit("safe_fd"); 997 errExit("safer_openat");
998 free(firejail); 998 free(firejail);
999 // create basedir if it doesn't exist 999 // create basedir if it doesn't exist
1000 // the new directory will be owned by root 1000 // the new directory will be owned by root
diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c
index c7b87235a..4bcefa443 100644
--- a/src/firejail/fs_home.c
+++ b/src/firejail/fs_home.c
@@ -262,10 +262,10 @@ void fs_private_homedir(void) {
262 if (arg_debug) 262 if (arg_debug)
263 printf("Mount-bind %s on top of %s\n", private_homedir, homedir); 263 printf("Mount-bind %s on top of %s\n", private_homedir, homedir);
264 // get file descriptors for homedir and private_homedir, fails if there is any symlink 264 // get file descriptors for homedir and private_homedir, fails if there is any symlink
265 int src = safe_fd(private_homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 265 int src = safer_openat(-1, private_homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
266 if (src == -1) 266 if (src == -1)
267 errExit("opening private directory"); 267 errExit("opening private directory");
268 int dst = safe_fd(homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 268 int dst = safer_openat(-1, homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
269 if (dst == -1) 269 if (dst == -1)
270 errExit("opening home directory"); 270 errExit("opening home directory");
271 // both mount source and target should be owned by the user 271 // both mount source and target should be owned by the user
@@ -576,7 +576,7 @@ void fs_private_home_list(void) {
576 if (arg_debug) 576 if (arg_debug)
577 printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir); 577 printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir);
578 578
579 int fd = safe_fd(homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 579 int fd = safer_openat(-1, homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
580 if (fd == -1) 580 if (fd == -1)
581 errExit("opening home directory"); 581 errExit("opening home directory");
582 // home directory should be owned by the user 582 // home directory should be owned by the user
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c
index 698d47b69..c7dbe6496 100644
--- a/src/firejail/fs_whitelist.c
+++ b/src/firejail/fs_whitelist.c
@@ -16,50 +16,46 @@
16 * You should have received a copy of the GNU General Public License along 16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc., 17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/ 19 */
20#include "firejail.h" 20#include "firejail.h"
21#include <sys/mount.h> 21#include <sys/mount.h>
22#include <sys/stat.h> 22#include <sys/stat.h>
23#include <linux/limits.h>
24#include <fnmatch.h> 23#include <fnmatch.h>
25#include <glob.h> 24#include <glob.h>
26#include <dirent.h>
27#include <errno.h> 25#include <errno.h>
28 26
29#include <fcntl.h> 27#include <fcntl.h>
30#ifndef O_PATH 28#ifndef O_PATH
31# define O_PATH 010000000 29#define O_PATH 010000000
32#endif 30#endif
33 31
32#define TOP_MAX 64 // maximum number of top level directories
33
34// mountinfo functionality test; 34// mountinfo functionality test;
35// 1. enable TEST_MOUNTINFO definition 35// 1. enable TEST_MOUNTINFO definition
36// 2. run firejail --whitelist=/any/directory 36// 2. run firejail --whitelist=/any/directory
37//#define TEST_MOUNTINFO 37//#define TEST_MOUNTINFO
38 38
39#define EMPTY_STRING ("") 39static size_t homedir_len = 0; // cache length of homedir string
40static size_t homedir_len; // cache length of homedir string 40static size_t runuser_len = 0; // cache length of runuser string
41static size_t runuser_len; // cache length of runuser string 41static char *runuser = NULL;
42static char *runuser;
43 42
44 43
45static int mkpath(const char* path, mode_t mode) {
46 assert(path && *path);
47 mode |= 0111;
48 44
49 // create directories with uid/gid as root, or as current user if inside home or run/user/$uid directory 45static void whitelist_error(const char *path) {
50 int userprivs = 0; 46 assert(path);
51 if ((strncmp(path, cfg.homedir, homedir_len) == 0 && path[homedir_len] == '/') || 47
52 (strncmp(path, runuser, runuser_len) == 0 && path[runuser_len] == '/')) { 48 fprintf(stderr, "Error: invalid whitelist path %s\n", path);
53 EUID_USER(); 49 exit(1);
54 userprivs = 1; 50}
55 }
56 51
52static int whitelist_mkpath(const char* path, mode_t mode) {
57 // work on a copy of the path 53 // work on a copy of the path
58 char *dup = strdup(path); 54 char *dup = strdup(path);
59 if (!dup) 55 if (!dup)
60 errExit("strdup"); 56 errExit("strdup");
61 57
62 // don't create the last path element 58 // only create leading directories, don't create the file
63 char *p = strrchr(dup, '/'); 59 char *p = strrchr(dup, '/');
64 assert(p); 60 assert(p);
65 *p = '\0'; 61 *p = '\0';
@@ -69,10 +65,10 @@ static int mkpath(const char* path, mode_t mode) {
69 errExit("open"); 65 errExit("open");
70 66
71 // traverse the path, return -1 if a symlink is encountered 67 // traverse the path, return -1 if a symlink is encountered
72 int done = 0;
73 int fd = -1; 68 int fd = -1;
69 int done = 0;
74 char *tok = strtok(dup, "/"); 70 char *tok = strtok(dup, "/");
75 assert(tok); // path is no top level directory 71 assert(tok);
76 while (tok) { 72 while (tok) {
77 // create the directory if necessary 73 // create the directory if necessary
78 if (mkdirat(parentfd, tok, mode) == -1) { 74 if (mkdirat(parentfd, tok, mode) == -1) {
@@ -81,9 +77,6 @@ static int mkpath(const char* path, mode_t mode) {
81 perror("mkdir"); 77 perror("mkdir");
82 close(parentfd); 78 close(parentfd);
83 free(dup); 79 free(dup);
84 if (userprivs) {
85 EUID_ROOT();
86 }
87 return -1; 80 return -1;
88 } 81 }
89 } 82 }
@@ -96,9 +89,6 @@ static int mkpath(const char* path, mode_t mode) {
96 perror("open"); 89 perror("open");
97 close(parentfd); 90 close(parentfd);
98 free(dup); 91 free(dup);
99 if (userprivs) {
100 EUID_ROOT();
101 }
102 return -1; 92 return -1;
103 } 93 }
104 // move on to next path segment 94 // move on to next path segment
@@ -111,195 +101,111 @@ static int mkpath(const char* path, mode_t mode) {
111 fs_logger2("mkpath", path); 101 fs_logger2("mkpath", path);
112 102
113 free(dup); 103 free(dup);
114 if (userprivs) {
115 EUID_ROOT();
116 }
117 return fd; 104 return fd;
118} 105}
119 106
120static void whitelist_path(ProfileEntry *entry) { 107static void whitelist_file(int dirfd, const char *topdir, const char *relpath, const char *path) {
121 assert(entry); 108 assert(topdir && relpath && path);
122 const char *path = entry->data + 10;
123 const char *fname;
124 char *wfile = NULL;
125
126 if (entry->wldir == WLDIR_HOME) {
127 if (strncmp(path, cfg.homedir, homedir_len) != 0 || path[homedir_len] != '/')
128 // either symlink pointing outside home directory
129 // or entire home directory, skip the mount
130 return;
131
132 fname = path + homedir_len + 1; // strlen("/home/user/")
133
134 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_HOME_USER_DIR, fname) == -1)
135 errExit("asprintf");
136 }
137 else if (entry->wldir == WLDIR_TMP) {
138 fname = path + 5; // strlen("/tmp/")
139
140 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_TMP_DIR, fname) == -1)
141 errExit("asprintf");
142 }
143 else if (entry->wldir == WLDIR_MEDIA) {
144 fname = path + 7; // strlen("/media/")
145
146 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_MEDIA_DIR, fname) == -1)
147 errExit("asprintf");
148 }
149 else if (entry->wldir == WLDIR_MNT) {
150 fname = path + 5; // strlen("/mnt/")
151
152 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_MNT_DIR, fname) == -1)
153 errExit("asprintf");
154 }
155 else if (entry->wldir == WLDIR_VAR) {
156 if (strncmp(path, "/var/", 5) != 0)
157 // symlink pointing outside /var, skip the mount
158 return;
159
160 fname = path + 5; // strlen("/var/")
161
162 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_VAR_DIR, fname) == -1)
163 errExit("asprintf");
164 }
165 else if (entry->wldir == WLDIR_DEV) {
166 if (strncmp(path, "/dev/", 5) != 0)
167 // symlink pointing outside /dev, skip the mount
168 return;
169
170 fname = path + 5; // strlen("/dev/")
171
172 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_DEV_DIR, fname) == -1)
173 errExit("asprintf");
174 }
175 else if (entry->wldir == WLDIR_OPT) {
176 fname = path + 5; // strlen("/opt/")
177
178 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_OPT_DIR, fname) == -1)
179 errExit("asprintf");
180 }
181 else if (entry->wldir == WLDIR_SRV) {
182 fname = path + 5; // strlen("/srv/")
183
184 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_SRV_DIR, fname) == -1)
185 errExit("asprintf");
186 }
187 else if (entry->wldir == WLDIR_ETC) {
188 if (strncmp(path, "/etc/", 5) != 0)
189 // symlink pointing outside /etc, skip the mount
190 return;
191
192 fname = path + 5; // strlen("/etc/")
193
194 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_ETC_DIR, fname) == -1)
195 errExit("asprintf");
196 }
197 else if (entry->wldir == WLDIR_SHARE) {
198 fname = path + 11; // strlen("/usr/share/")
199
200 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_SHARE_DIR, fname) == -1)
201 errExit("asprintf");
202 }
203 else if (entry->wldir == WLDIR_MODULE) {
204 fname = path + 12; // strlen("/sys/module/")
205
206 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_MODULE_DIR, fname) == -1)
207 errExit("asprintf");
208 }
209 else if (entry->wldir == WLDIR_RUN) {
210 fname = path + runuser_len + 1; // strlen("/run/user/$uid/")
211
212 if (asprintf(&wfile, "%s/%s", RUN_WHITELIST_RUN_USER_DIR, fname) == -1)
213 errExit("asprintf");
214 }
215 assert(wfile);
216 109
217 if (arg_debug || arg_debug_whitelists) 110 if (arg_debug || arg_debug_whitelists)
218 printf("Whitelisting %s\n", path); 111 printf("Debug %d: dirfd: %d; topdir: %s; relpath: %s; path: %s\n", __LINE__, dirfd, topdir, relpath, path);
219 112
220 // confirm again the mount source exists and there is no symlink 113 // open mount source, using a file descriptor that refers to the
221 struct stat wfilestat; 114 // top level directory
222 EUID_USER(); 115 // as the top level directory was opened before mounting the tmpfs
223 int fd = safe_fd(wfile, O_PATH|O_NOFOLLOW|O_CLOEXEC); 116 // we still have full access to all directory contents
224 EUID_ROOT(); 117 // take care to not follow symbolic links
118 int fd = safer_openat(dirfd, relpath, O_PATH|O_NOFOLLOW|O_CLOEXEC);
225 if (fd == -1) { 119 if (fd == -1) {
226 if (arg_debug || arg_debug_whitelists) 120 if (arg_debug || arg_debug_whitelists)
227 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); 121 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path);
228 free(wfile);
229 return; 122 return;
230 } 123 }
231 if (fstat(fd, &wfilestat) == -1) 124 struct stat s;
125 if (fstat(fd, &s) == -1)
232 errExit("fstat"); 126 errExit("fstat");
233 close(fd); 127 if (S_ISLNK(s.st_mode)) {
234 if (S_ISLNK(wfilestat.st_mode)) {
235 if (arg_debug || arg_debug_whitelists) 128 if (arg_debug || arg_debug_whitelists)
236 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); 129 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path);
237 free(wfile); 130 close(fd);
238 return; 131 return;
239 } 132 }
240 133
241 // create path of the mount target if necessary 134 // create mount target as root, except if inside home or run/user/$UID directory
242 int fd2 = mkpath(path, 0755); 135 int userprivs = 0;
136 if (strcmp(topdir, cfg.homedir) == 0 || strcmp(topdir, runuser) == 0) {
137 EUID_USER();
138 userprivs = 1;
139 }
140
141 // create path of the mount target
142 int fd2 = whitelist_mkpath(path, 0755);
243 if (fd2 == -1) { 143 if (fd2 == -1) {
244 // something went wrong during path creation or a symlink was found; 144 // something went wrong during path creation or a symlink was found;
245 // if there is a symlink somewhere in the path of the mount target, 145 // if there is a symlink somewhere in the path of the mount target,
246 // assume the file is whitelisted already 146 // assume the file is whitelisted already
247 if (arg_debug || arg_debug_whitelists) 147 if (arg_debug || arg_debug_whitelists)
248 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); 148 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path);
249 free(wfile); 149 close(fd);
150 if (userprivs)
151 EUID_ROOT();
250 return; 152 return;
251 } 153 }
252 154
253 // get file name of the mount target 155 // get file name of the mount target
254 const char *file = gnu_basename(path); 156 const char *file = gnu_basename(path);
255 157
256 // create the mount target if necessary and open it, a symlink is rejected 158 // create mount target itself and open it, a symlink is rejected
257 int fd3 = -1; 159 int fd3 = -1;
258 if (S_ISDIR(wfilestat.st_mode)) { 160 if (S_ISDIR(s.st_mode)) {
259 // directory foo can exist already: 161 // directory foo can exist already:
260 // firejail --whitelist=/foo/bar --whitelist=/foo 162 // firejail --whitelist=~/foo/bar --whitelist=~/foo
261 if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) { 163 if (mkdirat(fd2, file, 0755) == -1 && errno != EEXIST) {
262 if (arg_debug || arg_debug_whitelists) { 164 if (arg_debug || arg_debug_whitelists) {
263 perror("mkdir"); 165 perror("mkdir");
264 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path); 166 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path);
265 } 167 }
168 close(fd);
266 close(fd2); 169 close(fd2);
267 free(wfile); 170 if (userprivs)
171 EUID_ROOT();
268 return; 172 return;
269 } 173 }
270 fd3 = openat(fd2, file, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 174 fd3 = openat(fd2, file, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
271 } 175 }
272 else { 176 else
273 // create an empty file, fails with EEXIST if it is whitelisted already: 177 // create an empty file, fails with EEXIST if it is whitelisted already:
274 // firejail --whitelist=/foo --whitelist=/foo/bar 178 // firejail --whitelist=/foo --whitelist=/foo/bar
275 fd3 = openat(fd2, file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR); 179 fd3 = openat(fd2, file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR|S_IWUSR);
276 }
277 180
278 if (fd3 == -1) { 181 if (fd3 == -1) {
279 if (arg_debug || arg_debug_whitelists) { 182 if (errno != EEXIST && (arg_debug || arg_debug_whitelists)) {
280 if (errno != EEXIST) { 183 perror("open");
281 perror("open"); 184 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path);
282 printf("Debug %d: skip whitelisting of %s\n", __LINE__, path);
283 }
284 } 185 }
186 close(fd);
285 close(fd2); 187 close(fd2);
286 free(wfile); 188 if (userprivs)
189 EUID_ROOT();
287 return; 190 return;
288 } 191 }
192
289 close(fd2); 193 close(fd2);
194 if (userprivs)
195 EUID_ROOT();
290 196
291 fs_logger2("whitelist", path); 197 if (arg_debug || arg_debug_whitelists)
198 printf("Whitelisting %s\n", path);
292 199
293 // in order to make this mount resilient against symlink attacks, use 200 // in order to make this mount resilient against symlink attacks, use
294 // a magic link in /proc/self/fd instead of mounting on path directly 201 // magic links in /proc/self/fd instead of mounting the paths directly
295 char *proc; 202 char *proc_src, *proc_dst;
296 if (asprintf(&proc, "/proc/self/fd/%d", fd3) == -1) 203 if (asprintf(&proc_src, "/proc/self/fd/%d", fd) == -1)
204 errExit("asprintf");
205 if (asprintf(&proc_dst, "/proc/self/fd/%d", fd3) == -1)
297 errExit("asprintf"); 206 errExit("asprintf");
298 if (mount(wfile, proc, NULL, MS_BIND|MS_REC, NULL) < 0) 207 if (mount(proc_src, proc_dst, NULL, MS_BIND | MS_REC, NULL) < 0)
299 errExit("mount bind"); 208 errExit("mount bind");
300 free(proc);
301 close(fd3);
302
303 // check the last mount operation 209 // check the last mount operation
304 MountData *mptr = get_last_mount(); // will do exit(1) if the mount cannot be found 210 MountData *mptr = get_last_mount(); // will do exit(1) if the mount cannot be found
305#ifdef TEST_MOUNTINFO 211#ifdef TEST_MOUNTINFO
@@ -316,35 +222,52 @@ static void whitelist_path(ProfileEntry *entry) {
316 // - there should be more than one '/' char in dest string 222 // - there should be more than one '/' char in dest string
317 if (mptr->dir == strrchr(mptr->dir, '/')) 223 if (mptr->dir == strrchr(mptr->dir, '/'))
318 errLogExit("invalid whitelist mount"); 224 errLogExit("invalid whitelist mount");
319 // confirm the right file was mounted by comparing device and inode numbers 225 free(proc_src);
320 int fd4 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); 226 free(proc_dst);
321 if (fd4 == -1) 227 close(fd);
322 errExit("safe_fd"); 228 close(fd3);
323 struct stat s; 229 fs_logger2("whitelist", path);
324 if (fstat(fd4, &s) == -1)
325 errExit("fstat");
326 if (s.st_dev != wfilestat.st_dev || s.st_ino != wfilestat.st_ino)
327 errLogExit("invalid whitelist mount");
328 close(fd4);
329
330 free(wfile);
331 return;
332} 230}
333 231
334static void whitelist_home(int topdir) { 232static void whitelist_symlink(const char *topdir, const char *link, const char *target) {
335 ProfileEntry entry; 233 assert(topdir && link && target);
336 memset(&entry, 0, sizeof(entry)); 234
337 char *cmd; 235 if (arg_debug || arg_debug_whitelists)
338 if (asprintf(&cmd, "whitelist %s", cfg.homedir) == -1) 236 printf("Debug %d: topdir: %s; link: %s; target: %s\n", __LINE__, topdir, link, target);
339 errExit("asprintf"); 237
340 entry.data = cmd; 238 // create files as root, except if inside home or run/user/$UID directory
341 entry.wldir = topdir; 239 int userprivs = 0;
342 // creates path owned by root, except homedir is inside /run/user/$uid 240 if (strcmp(topdir, cfg.homedir) == 0 || strcmp(topdir, runuser) == 0) {
343 // does nothing if homedir does not exist 241 EUID_USER();
344 whitelist_path(&entry); 242 userprivs = 1;
345 free(cmd); 243 }
346} 244
245 int fd = whitelist_mkpath(link, 0755);
246 if (fd == -1) {
247 if (arg_debug || arg_debug_whitelists)
248 printf("Debug %d: cannot create symbolic link %s\n", __LINE__, link);
249 if (userprivs)
250 EUID_ROOT();
251 return;
252 }
253
254 // get file name of symlink
255 const char *file = gnu_basename(link);
256
257 // create the link
258 if (symlinkat(target, fd, file) == -1) {
259 if (arg_debug || arg_debug_whitelists) {
260 perror("symlink");
261 printf("Debug %d: cannot create symbolic link %s\n", __LINE__, link);
262 }
263 }
264 else if (arg_debug || arg_debug_whitelists)
265 printf("Created symbolic link %s -> %s\n", link, target);
347 266
267 close(fd);
268 if (userprivs)
269 EUID_ROOT();
270}
348 271
349static void globbing(const char *pattern) { 272static void globbing(const char *pattern) {
350 assert(pattern); 273 assert(pattern);
@@ -363,6 +286,11 @@ static void globbing(const char *pattern) {
363 // testing for GLOB_NOCHECK - no pattern matched returns the original pattern 286 // testing for GLOB_NOCHECK - no pattern matched returns the original pattern
364 if (strcmp(globbuf.gl_pathv[i], pattern) == 0) 287 if (strcmp(globbuf.gl_pathv[i], pattern) == 0)
365 continue; 288 continue;
289 // foo/* expands to foo/. and foo/..
290 const char *base = gnu_basename(globbuf.gl_pathv[i]);
291 if (strcmp(base, ".") == 0 ||
292 strcmp(base, "..") == 0)
293 continue;
366 294
367 // build the new profile command 295 // build the new profile command
368 char *newcmd; 296 char *newcmd;
@@ -378,6 +306,219 @@ static void globbing(const char *pattern) {
378 globfree(&globbuf); 306 globfree(&globbuf);
379} 307}
380 308
309// mount tmpfs on all top level directories
310static void tmpfs_topdirs(const TopDir *topdirs) {
311 int tmpfs_home = 0;
312 int tmpfs_runuser = 0;
313
314 int i;
315 for (i = 0; i < TOP_MAX && topdirs[i].path; i++) {
316 // do user home and /run/user/$UID last
317 if (strcmp(topdirs[i].path, cfg.homedir) == 0) {
318 tmpfs_home = 1;
319 continue;
320 }
321 if (strcmp(topdirs[i].path, runuser) == 0) {
322 tmpfs_runuser = 1;
323 continue;
324 }
325
326 // special case /run
327 // open /run/firejail, so it can be restored right after mounting the tmpfs
328 int fd = -1;
329 if (strcmp(topdirs[i].path, "/run") == 0) {
330 fd = open(RUN_FIREJAIL_DIR, O_PATH|O_CLOEXEC);
331 if (fd == -1)
332 errExit("open");
333 }
334
335 // mount tmpfs
336 fs_tmpfs(topdirs[i].path, 0);
337
338 // init tmpfs
339 if (strcmp(topdirs[i].path, "/run") == 0) {
340 // restore /run/firejail directory
341 if (mkdir(RUN_FIREJAIL_DIR, 0755) == -1)
342 errExit("mkdir");
343 char *proc;
344 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
345 errExit("asprintf");
346 if (mount(proc, RUN_FIREJAIL_DIR, NULL, MS_BIND | MS_REC, NULL) < 0)
347 errExit("mount bind");
348 free(proc);
349 close(fd);
350 fs_logger2("whitelist", RUN_FIREJAIL_DIR);
351
352 // restore /run/user/$UID directory
353 // get path relative to /run
354 const char *rel = runuser + 5;
355 whitelist_file(topdirs[i].fd, topdirs[i].path, rel, runuser);
356 }
357 else if (strcmp(topdirs[i].path, "/tmp") == 0) {
358 // fix pam-tmpdir (#2685)
359 const char *env = env_get("TMP");
360 if (env) {
361 char *pamtmpdir;
362 if (asprintf(&pamtmpdir, "/tmp/user/%u", getuid()) == -1)
363 errExit("asprintf");
364 if (strcmp(env, pamtmpdir) == 0) {
365 // create empty user-owned /tmp/user/$UID directory
366 mkdir_attr("/tmp/user", 0711, 0, 0);
367 selinux_relabel_path("/tmp/user", "/tmp/user");
368 fs_logger("mkdir /tmp/user");
369 mkdir_attr(pamtmpdir, 0700, getuid(), 0);
370 selinux_relabel_path(pamtmpdir, pamtmpdir);
371 fs_logger2("mkdir", pamtmpdir);
372 }
373 free(pamtmpdir);
374 }
375 }
376
377 // restore user home directory if it is masked by the tmpfs
378 // creates path owned by root
379 size_t topdir_len = strlen(topdirs[i].path);
380 if (strncmp(topdirs[i].path, cfg.homedir, topdir_len) == 0 && cfg.homedir[topdir_len] == '/') {
381 // get path relative to top level directory
382 const char *rel = cfg.homedir + topdir_len + 1;
383 whitelist_file(topdirs[i].fd, topdirs[i].path, rel, cfg.homedir);
384 }
385
386 selinux_relabel_path(topdirs[i].path, topdirs[i].path);
387 }
388
389 // user home directory
390 if (tmpfs_home)
391 fs_private(); // checks owner if outside /home
392
393 // /run/user/$UID directory
394 if (tmpfs_runuser) {
395 fs_tmpfs(runuser, 0);
396 selinux_relabel_path(runuser, runuser);
397 }
398}
399
400static int reject_topdir(const char *dir) {
401 if (!whitelist_reject_topdirs)
402 return 0;
403
404 size_t i;
405 for (i = 0; whitelist_reject_topdirs[i]; i++) {
406 if (strcmp(dir, whitelist_reject_topdirs[i]) == 0)
407 return 1;
408 }
409 return 0;
410}
411
412// keep track of whitelist top level directories by adding them to an array
413// open each directory
414static TopDir *add_topdir(const char *dir, TopDir *topdirs, const char *path) {
415 assert(dir && path);
416
417 // /proc and /sys are not allowed
418 if (strcmp(dir, "/") == 0 ||
419 strcmp(dir, "/proc") == 0 ||
420 strcmp(dir, "/sys") == 0)
421 whitelist_error(path);
422
423 // do nothing if directory doesn't exist
424 struct stat s;
425 if (lstat(dir, &s) != 0) {
426 if (arg_debug || arg_debug_whitelists)
427 printf("Cannot access whitelist top level directory %s: %s\n", dir, strerror(errno));
428 return NULL;
429 }
430 // do nothing if directory is a link
431 if (!S_ISDIR(s.st_mode)) {
432 if (S_ISLNK(s.st_mode)) {
433 fwarning("skipping whitelist %s because %s is a symbolic link\n", path, dir);
434 return NULL;
435 }
436 whitelist_error(path);
437 }
438 // do nothing if directory is disabled by administrator
439 if (reject_topdir(dir)) {
440 fwarning("skipping whitelist %s because\n"
441 "whitelist top level directory is disabled in Firejail configuration file\n", path);
442 return NULL;
443 }
444
445 // add directory to array
446 if (arg_debug || arg_debug_whitelists)
447 printf("Adding whitelist top level directory %s\n", dir);
448 static int cnt = 0;
449 if (cnt >= TOP_MAX) {
450 fprintf(stderr, "Error: too many whitelist top level directories\n");
451 exit(1);
452 }
453 TopDir *rv = topdirs + cnt;
454 cnt++;
455
456 char *dup = strdup(dir);
457 if (!dup)
458 errExit("strdup");
459 rv->path = dup;
460
461 // open the directory, don't follow symbolic links
462 rv->fd = safer_openat(-1, dup, O_PATH|O_NOFOLLOW|O_DIRECTORY|O_CLOEXEC);
463 if (rv->fd == -1) {
464 fprintf(stderr, "Error: cannot open %s\n", dup);
465 exit(1);
466 }
467
468 return rv;
469}
470
471static TopDir *have_topdir(const char *dir, TopDir *topdirs) {
472 assert(dir);
473
474 int i;
475 for (i = 0; i < TOP_MAX; i++) {
476 TopDir *rv = topdirs + i;
477 if (!rv->path)
478 break;
479 if (strcmp(dir, rv->path) == 0)
480 return rv;
481 }
482 return NULL;
483}
484
485static char *extract_topdir(const char *path) {
486 assert(path);
487
488 char *dup = strdup(path);
489 if (!dup)
490 errExit("strdup");
491
492 // user home directory can be anywhere; disconnect user home
493 // whitelisting from top level directory whitelisting
494 // by treating user home as separate whitelist top level directory
495 if (strncmp(dup, cfg.homedir, homedir_len) == 0 && dup[homedir_len] == '/')
496 dup[homedir_len] = '\0';
497 // /run/user/$UID is treated as top level directory
498 else if (strncmp(dup, runuser, runuser_len) == 0 && dup[runuser_len] == '/')
499 dup[runuser_len] = '\0';
500 // whitelisting in /sys is not allowed, but /sys/module is an exception
501 // and is treated as top level directory here
502 else if (strncmp(dup, "/sys/module", 11) == 0 && dup[11] == '/')
503 dup[11] = '\0';
504 // treat /usr subdirectories as top level directories
505 else if (strncmp(dup, "/usr/", 5) == 0) {
506 char *p = strchr(dup+5, '/');
507 if (!p)
508 whitelist_error(path);
509 *p = '\0';
510 }
511 // all other top level directories
512 else {
513 assert(dup[0] == '/');
514 char *p = strchr(dup+1, '/');
515 if (!p)
516 whitelist_error(path);
517 *p = '\0';
518 }
519
520 return dup;
521}
381 522
382void fs_whitelist(void) { 523void fs_whitelist(void) {
383 ProfileEntry *entry = cfg.profile; 524 ProfileEntry *entry = cfg.profile;
@@ -389,29 +530,18 @@ void fs_whitelist(void) {
389 runuser_len = strlen(runuser); 530 runuser_len = strlen(runuser);
390 homedir_len = strlen(cfg.homedir); 531 homedir_len = strlen(cfg.homedir);
391 532
392 char *new_name = NULL;
393 int home_dir = 0; // /home/user directory flag
394 int tmp_dir = 0; // /tmp directory flag
395 int media_dir = 0; // /media directory flag
396 int mnt_dir = 0; // /mnt directory flag
397 int var_dir = 0; // /var directory flag
398 int dev_dir = 0; // /dev directory flag
399 int opt_dir = 0; // /opt directory flag
400 int srv_dir = 0; // /srv directory flag
401 int etc_dir = 0; // /etc directory flag
402 int share_dir = 0; // /usr/share directory flag
403 int module_dir = 0; // /sys/module directory flag
404 int run_dir = 0; // /run/user/$uid directory flag
405
406 size_t nowhitelist_c = 0; 533 size_t nowhitelist_c = 0;
407 size_t nowhitelist_m = 32; 534 size_t nowhitelist_m = 32;
408 char **nowhitelist = calloc(nowhitelist_m, sizeof(*nowhitelist)); 535 char **nowhitelist = calloc(nowhitelist_m, sizeof(*nowhitelist));
409 if (nowhitelist == NULL) 536 if (nowhitelist == NULL)
410 errExit("failed allocating memory for nowhitelist entries"); 537 errExit("calloc");
538
539 TopDir *topdirs = calloc(TOP_MAX, sizeof(*topdirs));
540 if (topdirs == NULL)
541 errExit("calloc");
411 542
412 // verify whitelist files, extract symbolic links, etc. 543 // verify whitelist files, extract symbolic links, etc.
413 EUID_USER(); 544 EUID_USER();
414 struct stat s;
415 while (entry) { 545 while (entry) {
416 int nowhitelist_flag = 0; 546 int nowhitelist_flag = 0;
417 547
@@ -424,48 +554,69 @@ void fs_whitelist(void) {
424 entry = entry->next; 554 entry = entry->next;
425 continue; 555 continue;
426 } 556 }
427 char *dataptr = (nowhitelist_flag)? entry->data + 12: entry->data + 10; 557 if (arg_debug || arg_debug_whitelists)
428 558 printf("Debug %d: %s\n", __LINE__, entry->data);
429 // replace ~/ or ${HOME} into /home/username or resolve macro 559
430 new_name = expand_macros(dataptr); 560 const char *dataptr = (nowhitelist_flag)? entry->data + 12: entry->data + 10;
431 assert(new_name); 561
432 562 // replace ~ into /home/username or resolve macro
433 // mount empty home directory if resolving the macro was not successful 563 char *expanded = expand_macros(dataptr);
434 if (is_macro(new_name) && macro_id(new_name) > -1) { 564
435 // no warning if home does not exist (e.g. in a chroot) 565 // check if respolving the macro was successful
436 if (stat(cfg.homedir, &s) == 0 && !nowhitelist_flag && !arg_private) { 566 if (is_macro(expanded) && macro_id(expanded) > -1) {
437 home_dir = 1; 567 if (!nowhitelist_flag && (have_topdir(cfg.homedir, topdirs) || add_topdir(cfg.homedir, topdirs, expanded)) && !arg_quiet) {
438 if (!arg_quiet) { 568 fprintf(stderr, "***\n");
439 fprintf(stderr, "***\n"); 569 fprintf(stderr, "*** Warning: cannot whitelist %s directory\n", expanded);
440 fprintf(stderr, "*** Warning: cannot whitelist %s directory\n", new_name); 570 fprintf(stderr, "*** Any file saved in this directory will be lost when the sandbox is closed.\n");
441 fprintf(stderr, "*** Any file saved in this directory will be lost when the sandbox is closed.\n"); 571 fprintf(stderr, "***\n");
442 fprintf(stderr, "***\n");
443 }
444 } 572 }
445 entry->data = EMPTY_STRING;
446 entry = entry->next; 573 entry = entry->next;
447 free(new_name); 574 free(expanded);
448 continue; 575 continue;
449 } 576 }
450 577
451 // remove trailing slashes and single dots 578 if (arg_debug || arg_debug_whitelists)
452 if (!nowhitelist_flag) 579 printf("Debug %d: expanded: %s\n", __LINE__, expanded);
453 trim_trailing_slash_or_dot(new_name); 580
581 // path should be absolute at this point
582 if (expanded[0] != '/')
583 whitelist_error(expanded);
584
585 // sane pathname
586 char *new_name = clean_pathname(expanded);
587 free(expanded);
454 588
455 if (arg_debug || arg_debug_whitelists) 589 if (arg_debug || arg_debug_whitelists)
456 fprintf(stderr, "Debug %d: new_name #%s#, %s\n", __LINE__, new_name, (nowhitelist_flag)? "nowhitelist": "whitelist"); 590 printf("Debug %d: new_name: %s\n", __LINE__, new_name);
591
592 if (strstr(new_name, ".."))
593 whitelist_error(new_name);
457 594
458 // valid path referenced to filesystem root 595 TopDir *current_top = NULL;
459 if (*new_name != '/') { 596 if (!nowhitelist_flag) {
597 // extract whitelist top level directory
598 char *dir = extract_topdir(new_name);
460 if (arg_debug || arg_debug_whitelists) 599 if (arg_debug || arg_debug_whitelists)
461 fprintf(stderr, "Debug %d: \n", __LINE__); 600 printf("Debug %d: dir: %s\n", __LINE__, dir);
462 goto errexit; 601
602 // check if this top level directory has been processed already
603 current_top = have_topdir(dir, topdirs);
604 if (!current_top) { // got new top level directory
605 current_top = add_topdir(dir, topdirs, new_name);
606 if (!current_top) { // skip this command, top level directory not valid
607 entry = entry->next;
608 free(new_name);
609 free(dir);
610 continue;
611 }
612 }
613 free(dir);
463 } 614 }
464 615
465 // extract the absolute path of the file 616 // extract resolved path of the file
466 // realpath function will fail with ENOENT if the file is not found or with EACCES if user has no permission 617 // realpath function will fail with ENOENT if the file is not found or with EACCES if user has no permission
467 // special processing for /dev/fd, /dev/stdin, /dev/stdout and /dev/stderr 618 // special processing for /dev/fd, /dev/stdin, /dev/stdout and /dev/stderr
468 char *fname; 619 char *fname = NULL;
469 if (strcmp(new_name, "/dev/fd") == 0) 620 if (strcmp(new_name, "/dev/fd") == 0)
470 fname = strdup("/proc/self/fd"); 621 fname = strdup("/proc/self/fd");
471 else if (strcmp(new_name, "/dev/stdin") == 0) 622 else if (strcmp(new_name, "/dev/stdin") == 0)
@@ -477,60 +628,26 @@ void fs_whitelist(void) {
477 else 628 else
478 fname = realpath(new_name, NULL); 629 fname = realpath(new_name, NULL);
479 630
480 // if this is not a real path, let's try globbing
481 // mark this entry as EMPTY_STRING and push the new paths at the end of profile entry list
482 // the new profile entries will be processed in this loop
483 // currently there is no globbing support for nowhitelist
484 if (!fname && !nowhitelist_flag)
485 globbing(new_name);
486
487 if (!fname) { 631 if (!fname) {
488 // file not found, blank the entry in the list and continue
489 if (arg_debug || arg_debug_whitelists) { 632 if (arg_debug || arg_debug_whitelists) {
490 printf("Removed whitelist/nowhitelist path: %s\n", entry->data); 633 printf("Removed path: %s\n", entry->data);
491 printf("\texpanded: %s\n", new_name); 634 printf("\texpanded: %s\n", new_name);
492 printf("\treal path: (null)\n"); 635 printf("\trealpath: (null)\n");
493 printf("\t");fflush(0); 636 printf("\t%s\n", strerror(errno));
494 perror("realpath");
495 } 637 }
496 638
497 // if 1 the file was not found; mount an empty directory
498 if (!nowhitelist_flag) { 639 if (!nowhitelist_flag) {
499 if (strncmp(new_name, cfg.homedir, homedir_len) == 0 && new_name[homedir_len] == '/') { 640 // if this is not a real path, let's try globbing
500 if(!arg_private) 641 // push the new paths at the end of profile entry list
501 home_dir = 1; 642 // the new profile entries will be processed in this loop
502 } 643 // currently there is no globbing support for nowhitelist
503 else if (strncmp(new_name, "/tmp/", 5) == 0) 644 globbing(new_name);
504 tmp_dir = 1;
505 else if (strncmp(new_name, "/media/", 7) == 0)
506 media_dir = 1;
507 else if (strncmp(new_name, "/mnt/", 5) == 0)
508 mnt_dir = 1;
509 else if (strncmp(new_name, "/var/", 5) == 0)
510 var_dir = 1;
511 else if (strncmp(new_name, "/dev/", 5) == 0)
512 dev_dir = 1;
513 else if (strncmp(new_name, "/opt/", 5) == 0)
514 opt_dir = 1;
515 else if (strncmp(new_name, "/srv/", 5) == 0)
516 srv_dir = 1;
517 else if (strncmp(new_name, "/etc/", 5) == 0)
518 etc_dir = 1;
519 else if (strncmp(new_name, "/usr/share/", 11) == 0)
520 share_dir = 1;
521 else if (strncmp(new_name, "/sys/module/", 12) == 0)
522 module_dir = 1;
523 else if (strncmp(new_name, runuser, runuser_len) == 0 && new_name[runuser_len] == '/')
524 run_dir = 1;
525 } 645 }
526 646
527 entry->data = EMPTY_STRING;
528 entry = entry->next; 647 entry = entry->next;
529 free(new_name); 648 free(new_name);
530 continue; 649 continue;
531 } 650 }
532 else if (arg_debug_whitelists)
533 printf("real path %s\n", fname);
534 651
535 if (nowhitelist_flag) { 652 if (nowhitelist_flag) {
536 // store the path in nowhitelist array 653 // store the path in nowhitelist array
@@ -544,175 +661,12 @@ void fs_whitelist(void) {
544 errExit("failed increasing memory for nowhitelist entries"); 661 errExit("failed increasing memory for nowhitelist entries");
545 } 662 }
546 nowhitelist[nowhitelist_c++] = fname; 663 nowhitelist[nowhitelist_c++] = fname;
547 entry->data = EMPTY_STRING;
548 entry = entry->next; 664 entry = entry->next;
549 free(new_name); 665 free(new_name);
550 continue; 666 continue;
551 } 667 }
552
553 // check for supported directories
554 if (strncmp(new_name, cfg.homedir, homedir_len) == 0 && new_name[homedir_len] == '/') {
555 // whitelisting home directory is disabled if --private option is present
556 if (arg_private) {
557 if (arg_debug || arg_debug_whitelists)
558 printf("\"%s\" disabled by --private\n", entry->data);
559
560 entry->data = EMPTY_STRING;
561 entry = entry->next;
562 free(fname);
563 free(new_name);
564 continue;
565 }
566
567 entry->wldir = WLDIR_HOME;
568 home_dir = 1;
569 if (arg_debug || arg_debug_whitelists)
570 fprintf(stderr, "Debug %d: fname #%s#, cfg.homedir #%s#\n",
571 __LINE__, fname, cfg.homedir);
572
573 // both path and absolute path are in user home,
574 // if not check if the symlink destination is owned by the user
575 if (strncmp(fname, cfg.homedir, homedir_len) != 0 || fname[homedir_len] != '/') {
576 if (checkcfg(CFG_FOLLOW_SYMLINK_AS_USER)) {
577 if (stat(fname, &s) == 0 && s.st_uid != getuid()) {
578 free(fname);
579 goto errexit;
580 }
581 }
582 }
583 }
584 else if (strncmp(new_name, "/tmp/", 5) == 0) {
585 entry->wldir = WLDIR_TMP;
586 tmp_dir = 1;
587
588 // both path and absolute path are under /tmp
589 if (strncmp(fname, "/tmp/", 5) != 0) {
590 free(fname);
591 goto errexit;
592 }
593 }
594 else if (strncmp(new_name, "/media/", 7) == 0) {
595 entry->wldir = WLDIR_MEDIA;
596 media_dir = 1;
597 // both path and absolute path are under /media
598 if (strncmp(fname, "/media/", 7) != 0) {
599 free(fname);
600 goto errexit;
601 }
602 }
603 else if (strncmp(new_name, "/mnt/", 5) == 0) {
604 entry->wldir = WLDIR_MNT;
605 mnt_dir = 1;
606 // both path and absolute path are under /mnt
607 if (strncmp(fname, "/mnt/", 5) != 0) {
608 free(fname);
609 goto errexit;
610 }
611 }
612 else if (strncmp(new_name, "/var/", 5) == 0) {
613 entry->wldir = WLDIR_VAR;
614 var_dir = 1;
615 // both path and absolute path are under /var
616 // exceptions: /var/tmp, /var/run and /var/lock
617 if (strcmp(new_name, "/var/run")== 0 && strcmp(fname, "/run") == 0);
618 else if (strcmp(new_name, "/var/lock")== 0 && strcmp(fname, "/run/lock") == 0);
619 else if (strcmp(new_name, "/var/tmp")== 0 && strcmp(fname, "/tmp") == 0);
620 else {
621 // both path and absolute path are under /var
622 if (strncmp(fname, "/var/", 5) != 0) {
623 free(fname);
624 goto errexit;
625 }
626 }
627 }
628 else if (strncmp(new_name, "/dev/", 5) == 0) {
629 entry->wldir = WLDIR_DEV;
630 dev_dir = 1;
631 // special handling for /dev/shm
632 // on some platforms (Debian wheezy, Ubuntu 14.04), it is a symlink to /run/shm
633 if (strcmp(new_name, "/dev/shm") == 0 && strcmp(fname, "/run/shm") == 0);
634 // special handling for /dev/log, which can be a symlink to /run/systemd/journal/dev-log
635 else if (strcmp(new_name, "/dev/log") == 0 && strcmp(fname, "/run/systemd/journal/dev-log") == 0);
636 // special processing for /proc/self/fd files
637 else if (strcmp(new_name, "/dev/fd") == 0 && strcmp(fname, "/proc/self/fd") == 0);
638 else if (strcmp(new_name, "/dev/stdin") == 0 && strcmp(fname, "/proc/self/fd/0") == 0);
639 else if (strcmp(new_name, "/dev/stdout") == 0 && strcmp(fname, "/proc/self/fd/1") == 0);
640 else if (strcmp(new_name, "/dev/stderr") == 0 && strcmp(fname, "/proc/self/fd/2") == 0);
641 else {
642 // both path and absolute path are under /dev
643 if (strncmp(fname, "/dev/", 5) != 0) {
644 free(fname);
645 goto errexit;
646 }
647 }
648 }
649 else if (strncmp(new_name, "/opt/", 5) == 0) {
650 entry->wldir = WLDIR_OPT;
651 opt_dir = 1;
652 // both path and absolute path are under /dev
653 if (strncmp(fname, "/opt/", 5) != 0) {
654 free(fname);
655 goto errexit;
656 }
657 }
658 else if (strncmp(new_name, "/srv/", 5) == 0) {
659 entry->wldir = WLDIR_SRV;
660 srv_dir = 1;
661 // both path and absolute path are under /srv
662 if (strncmp(fname, "/srv/", 5) != 0) {
663 free(fname);
664 goto errexit;
665 }
666 }
667 else if (strncmp(new_name, "/etc/", 5) == 0) {
668 entry->wldir = WLDIR_ETC;
669 etc_dir = 1;
670 // special handling for some of the symlinks
671 if (strcmp(new_name, "/etc/localtime") == 0);
672 else if (strcmp(new_name, "/etc/mtab") == 0);
673 else if (strcmp(new_name, "/etc/os-release") == 0);
674 // both path and absolute path are under /etc
675 else {
676 if (strncmp(fname, "/etc/", 5) != 0) {
677 free(fname);
678 goto errexit;
679 }
680 }
681 }
682 else if (strncmp(new_name, "/usr/share/", 11) == 0) {
683 entry->wldir = WLDIR_SHARE;
684 share_dir = 1;
685 // both path and absolute path are under /etc
686 if (strncmp(fname, "/usr/share/", 11) != 0) {
687 free(fname);
688 goto errexit;
689 }
690 }
691 else if (strncmp(new_name, "/sys/module/", 12) == 0) {
692 entry->wldir = WLDIR_MODULE;
693 module_dir = 1;
694 // both path and absolute path are under /sys/module
695 if (strncmp(fname, "/sys/module/", 12) != 0) {
696 free(fname);
697 goto errexit;
698 }
699 }
700 else if (strncmp(new_name, runuser, runuser_len) == 0 && new_name[runuser_len] == '/') {
701 entry->wldir = WLDIR_RUN;
702 run_dir = 1;
703 // both path and absolute path are under /run/user/$uid
704 if (strncmp(fname, runuser, runuser_len) != 0 || fname[runuser_len] != '/') {
705 free(fname);
706 goto errexit;
707 }
708 }
709 else { 668 else {
710 free(fname); 669 // check if the path is in nowhitelist array
711 goto errexit;
712 }
713
714 // check if the path is in nowhitelist array
715 if (nowhitelist_flag == 0) {
716 size_t i; 670 size_t i;
717 int found = 0; 671 int found = 0;
718 for (i = 0; i < nowhitelist_c; i++) { 672 for (i = 0; i < nowhitelist_c; i++) {
@@ -726,494 +680,76 @@ void fs_whitelist(void) {
726 if (found) { 680 if (found) {
727 if (arg_debug || arg_debug_whitelists) 681 if (arg_debug || arg_debug_whitelists)
728 printf("Skip nowhitelisted path %s\n", fname); 682 printf("Skip nowhitelisted path %s\n", fname);
729 entry->data = EMPTY_STRING;
730 entry = entry->next; 683 entry = entry->next;
731 free(fname);
732 free(new_name); 684 free(new_name);
685 free(fname);
733 continue; 686 continue;
734 } 687 }
735 } 688 }
736 689
737 // mark symbolic links 690 // attach whitelist parameters to profile entry
691 entry->wparam = calloc(1, sizeof(struct wparam_t));
692 if (!entry->wparam)
693 errExit("calloc");
694
695 assert(current_top);
696 entry->wparam->top = current_top;
697 entry->wparam->file = fname;
698
699 // mark link
738 if (is_link(new_name)) 700 if (is_link(new_name))
739 entry->link = new_name; 701 entry->wparam->link = new_name;
740 else { 702 else
741 free(new_name); 703 free(new_name);
742 entry->link = NULL;
743 }
744 704
745 // change file name in entry->data
746 if (strcmp(fname, entry->data + 10) != 0) {
747 char *newdata;
748 if (asprintf(&newdata, "whitelist %s", fname) == -1)
749 errExit("asprintf");
750 entry->data = newdata;
751 if (arg_debug || arg_debug_whitelists)
752 printf("Replaced whitelist path: %s\n", entry->data);
753 }
754 free(fname);
755 entry = entry->next; 705 entry = entry->next;
756 } 706 }
757 707
758 // release nowhitelist memory 708 // release nowhitelist memory
759 assert(nowhitelist);
760 free(nowhitelist); 709 free(nowhitelist);
761 710
711 // mount tmpfs on all top level directories
762 EUID_ROOT(); 712 EUID_ROOT();
763 // /tmp mountpoint 713 tmpfs_topdirs(topdirs);
764 if (tmp_dir) {
765 // check if /tmp directory exists
766 if (stat("/tmp", &s) == 0) {
767 // keep a copy of real /tmp directory in RUN_WHITELIST_TMP_DIR
768 mkdir_attr(RUN_WHITELIST_TMP_DIR, 1777, 0, 0);
769 if (mount("/tmp", RUN_WHITELIST_TMP_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
770 errExit("mount bind");
771
772 // mount tmpfs on /tmp
773 if (arg_debug || arg_debug_whitelists)
774 printf("Mounting tmpfs on /tmp directory\n");
775 if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=1777,gid=0") < 0)
776 errExit("mounting tmpfs on /tmp");
777 selinux_relabel_path("/tmp", "/tmp");
778 fs_logger("tmpfs /tmp");
779
780 // pam-tmpdir - issue #2685
781 const char *env = env_get("TMP");
782 if (env) {
783 char *pamtmpdir;
784 if (asprintf(&pamtmpdir, "/tmp/user/%u", getuid()) == -1)
785 errExit("asprintf");
786 if (strcmp(env, pamtmpdir) == 0) {
787 // create empty user-owned /tmp/user/$uid directory
788 mkdir_attr("/tmp/user", 0711, 0, 0);
789 selinux_relabel_path("/tmp/user", "/tmp/user");
790 fs_logger("mkdir /tmp/user");
791 mkdir_attr(pamtmpdir, 0700, getuid(), 0);
792 selinux_relabel_path(pamtmpdir, pamtmpdir);
793 fs_logger2("mkdir", pamtmpdir);
794 }
795 free(pamtmpdir);
796 }
797
798 // autowhitelist home directory if it is masked by the tmpfs
799 if (strncmp(cfg.homedir, "/tmp/", 5) == 0)
800 whitelist_home(WLDIR_TMP);
801 }
802 else
803 tmp_dir = 0;
804 }
805
806 // /media mountpoint
807 if (media_dir) {
808 // some distros don't have a /media directory
809 if (stat("/media", &s) == 0) {
810 // keep a copy of real /media directory in RUN_WHITELIST_MEDIA_DIR
811 mkdir_attr(RUN_WHITELIST_MEDIA_DIR, 0755, 0, 0);
812 if (mount("/media", RUN_WHITELIST_MEDIA_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
813 errExit("mount bind");
814
815 // mount tmpfs on /media
816 if (arg_debug || arg_debug_whitelists)
817 printf("Mounting tmpfs on /media directory\n");
818 if (mount("tmpfs", "/media", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
819 errExit("mounting tmpfs on /media");
820 selinux_relabel_path("/media", "/media");
821 fs_logger("tmpfs /media");
822
823 // autowhitelist home directory if it is masked by the tmpfs
824 if (strncmp(cfg.homedir, "/media/", 7) == 0)
825 whitelist_home(WLDIR_MEDIA);
826 }
827 else
828 media_dir = 0;
829 }
830
831 // /mnt mountpoint
832 if (mnt_dir) {
833 // check if /mnt directory exists
834 if (stat("/mnt", &s) == 0) {
835 // keep a copy of real /mnt directory in RUN_WHITELIST_MNT_DIR
836 mkdir_attr(RUN_WHITELIST_MNT_DIR, 0755, 0, 0);
837 if (mount("/mnt", RUN_WHITELIST_MNT_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
838 errExit("mount bind");
839
840 // mount tmpfs on /mnt
841 if (arg_debug || arg_debug_whitelists)
842 printf("Mounting tmpfs on /mnt directory\n");
843 if (mount("tmpfs", "/mnt", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
844 errExit("mounting tmpfs on /mnt");
845 selinux_relabel_path("/mnt", "/mnt");
846 fs_logger("tmpfs /mnt");
847
848 // autowhitelist home directory if it is masked by the tmpfs
849 if (strncmp(cfg.homedir, "/mnt/", 5) == 0)
850 whitelist_home(WLDIR_MNT);
851 }
852 else
853 mnt_dir = 0;
854 }
855
856 // /var mountpoint
857 if (var_dir) {
858 // check if /var directory exists
859 if (stat("/var", &s) == 0) {
860 // keep a copy of real /var directory in RUN_WHITELIST_VAR_DIR
861 mkdir_attr(RUN_WHITELIST_VAR_DIR, 0755, 0, 0);
862 if (mount("/var", RUN_WHITELIST_VAR_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
863 errExit("mount bind");
864
865 // mount tmpfs on /var
866 if (arg_debug || arg_debug_whitelists)
867 printf("Mounting tmpfs on /var directory\n");
868 if (mount("tmpfs", "/var", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
869 errExit("mounting tmpfs on /var");
870 selinux_relabel_path("/var", "/var");
871 fs_logger("tmpfs /var");
872
873 // autowhitelist home directory if it is masked by the tmpfs
874 if (strncmp(cfg.homedir, "/var/", 5) == 0)
875 whitelist_home(WLDIR_VAR);
876 }
877 else
878 var_dir = 0;
879 }
880
881 // /dev mountpoint
882 if (dev_dir) {
883 // check if /dev directory exists
884 if (stat("/dev", &s) == 0) {
885 // keep a copy of real /dev directory in RUN_WHITELIST_DEV_DIR
886 mkdir_attr(RUN_WHITELIST_DEV_DIR, 0755, 0, 0);
887 if (mount("/dev", RUN_WHITELIST_DEV_DIR, NULL, MS_BIND|MS_REC, "mode=755,gid=0") < 0)
888 errExit("mount bind");
889
890 // mount tmpfs on /dev
891 if (arg_debug || arg_debug_whitelists)
892 printf("Mounting tmpfs on /dev directory\n");
893 if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
894 errExit("mounting tmpfs on /dev");
895 selinux_relabel_path("/dev", "/dev");
896 fs_logger("tmpfs /dev");
897
898 // autowhitelist home directory if it is masked by the tmpfs
899 if (strncmp(cfg.homedir, "/dev/", 5) == 0)
900 whitelist_home(WLDIR_DEV);
901 }
902 else
903 dev_dir = 0;
904 }
905
906 // /opt mountpoint
907 if (opt_dir) {
908 // check if /opt directory exists
909 if (stat("/opt", &s) == 0) {
910 // keep a copy of real /opt directory in RUN_WHITELIST_OPT_DIR
911 mkdir_attr(RUN_WHITELIST_OPT_DIR, 0755, 0, 0);
912 if (mount("/opt", RUN_WHITELIST_OPT_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
913 errExit("mount bind");
914
915 // mount tmpfs on /opt
916 if (arg_debug || arg_debug_whitelists)
917 printf("Mounting tmpfs on /opt directory\n");
918 if (mount("tmpfs", "/opt", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
919 errExit("mounting tmpfs on /opt");
920 selinux_relabel_path("/opt", "/opt");
921 fs_logger("tmpfs /opt");
922
923 // autowhitelist home directory if it is masked by the tmpfs
924 if (strncmp(cfg.homedir, "/opt/", 5) == 0)
925 whitelist_home(WLDIR_OPT);
926 }
927 else
928 opt_dir = 0;
929 }
930
931 // /srv mountpoint
932 if (srv_dir) {
933 // check if /srv directory exists
934 if (stat("/srv", &s) == 0) {
935 // keep a copy of real /srv directory in RUN_WHITELIST_SRV_DIR
936 mkdir_attr(RUN_WHITELIST_SRV_DIR, 0755, 0, 0);
937 if (mount("/srv", RUN_WHITELIST_SRV_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
938 errExit("mount bind");
939
940 // mount tmpfs on /srv
941 if (arg_debug || arg_debug_whitelists)
942 printf("Mounting tmpfs on /srv directory\n");
943 if (mount("tmpfs", "/srv", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
944 errExit("mounting tmpfs on /srv");
945 selinux_relabel_path("/srv", "/srv");
946 fs_logger("tmpfs /srv");
947
948 // autowhitelist home directory if it is masked by the tmpfs
949 if (strncmp(cfg.homedir, "/srv/", 5) == 0)
950 whitelist_home(WLDIR_SRV);
951 }
952 else
953 srv_dir = 0;
954 }
955
956 // /etc mountpoint
957 if (etc_dir) {
958 // check if /etc directory exists
959 if (stat("/etc", &s) == 0) {
960 // keep a copy of real /etc directory in RUN_WHITELIST_ETC_DIR
961 mkdir_attr(RUN_WHITELIST_ETC_DIR, 0755, 0, 0);
962 if (mount("/etc", RUN_WHITELIST_ETC_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
963 errExit("mount bind");
964
965 // mount tmpfs on /etc
966 if (arg_debug || arg_debug_whitelists)
967 printf("Mounting tmpfs on /etc directory\n");
968 if (mount("tmpfs", "/etc", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
969 errExit("mounting tmpfs on /etc");
970 selinux_relabel_path("/etc", "/etc");
971 fs_logger("tmpfs /etc");
972
973 // autowhitelist home directory if it is masked by the tmpfs
974 if (strncmp(cfg.homedir, "/etc/", 5) == 0)
975 whitelist_home(WLDIR_ETC);
976 }
977 else
978 etc_dir = 0;
979 }
980
981 // /usr/share mountpoint
982 if (share_dir) {
983 // check if /usr/share directory exists
984 if (stat("/usr/share", &s) == 0) {
985 // keep a copy of real /usr/share directory in RUN_WHITELIST_ETC_DIR
986 mkdir_attr(RUN_WHITELIST_SHARE_DIR, 0755, 0, 0);
987 if (mount("/usr/share", RUN_WHITELIST_SHARE_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
988 errExit("mount bind");
989
990 // mount tmpfs on /srv
991 if (arg_debug || arg_debug_whitelists)
992 printf("Mounting tmpfs on /usr/share directory\n");
993 if (mount("tmpfs", "/usr/share", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
994 errExit("mounting tmpfs on /usr/share");
995 selinux_relabel_path("/usr/share", "/usr/share");
996 fs_logger("tmpfs /usr/share");
997
998 // autowhitelist home directory if it is masked by the tmpfs
999 if (strncmp(cfg.homedir, "/usr/share/", 11) == 0)
1000 whitelist_home(WLDIR_SHARE);
1001 }
1002 else
1003 share_dir = 0;
1004 }
1005
1006 // /sys/module mountpoint
1007 if (module_dir) {
1008 // check if /sys/module directory exists
1009 if (stat("/sys/module", &s) == 0) {
1010 // keep a copy of real /sys/module directory in RUN_WHITELIST_MODULE_DIR
1011 mkdir_attr(RUN_WHITELIST_MODULE_DIR, 0755, 0, 0);
1012 if (mount("/sys/module", RUN_WHITELIST_MODULE_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
1013 errExit("mount bind");
1014
1015 // mount tmpfs on /sys/module
1016 if (arg_debug || arg_debug_whitelists)
1017 printf("Mounting tmpfs on /sys/module directory\n");
1018 if (mount("tmpfs", "/sys/module", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1019 errExit("mounting tmpfs on /sys/module");
1020 selinux_relabel_path("/sys/module", "/sys/module");
1021 fs_logger("tmpfs /sys/module");
1022 }
1023 else
1024 module_dir = 0;
1025 }
1026
1027 // /run/user/$uid mountpoint
1028 if (run_dir) {
1029 // check if /run/user/$uid directory exists
1030 if (stat(runuser, &s) == 0) {
1031 // keep a copy of real /run/user/$uid directory in RUN_WHITELIST_RUN_USER_DIR
1032 mkdir_attr(RUN_WHITELIST_RUN_USER_DIR, 0700, getuid(), getgid());
1033 if (mount(runuser, RUN_WHITELIST_RUN_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
1034 errExit("mount bind");
1035
1036 // mount tmpfs on /run/user/$uid
1037 if (arg_debug || arg_debug_whitelists)
1038 printf("Mounting tmpfs on %s directory\n", runuser);
1039 char *options;
1040 if (asprintf(&options, "mode=700,uid=%u,gid=%u", getuid(), getgid()) == -1)
1041 errExit("asprintf");
1042 if (mount("tmpfs", runuser, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME, options) < 0)
1043 errExit("mounting tmpfs on /run/user/<uid>");
1044 selinux_relabel_path(runuser, runuser);
1045 free(options);
1046 fs_logger2("tmpfs", runuser);
1047
1048 // autowhitelist home directory if it is masked by the tmpfs
1049 if (strncmp(cfg.homedir, runuser, runuser_len) == 0 && cfg.homedir[runuser_len] == '/')
1050 whitelist_home(WLDIR_RUN);
1051 }
1052 else
1053 run_dir = 0;
1054 }
1055
1056 // home mountpoint
1057 if (home_dir) {
1058 // check if home directory exists
1059 if (stat(cfg.homedir, &s) == 0) {
1060 // keep a copy of real home dir in RUN_WHITELIST_HOME_USER_DIR
1061 mkdir_attr(RUN_WHITELIST_HOME_USER_DIR, 0755, getuid(), getgid());
1062 int fd = safe_fd(cfg.homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
1063 if (fd == -1)
1064 errExit("safe_fd");
1065 char *proc;
1066 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
1067 errExit("asprintf");
1068 if (mount(proc, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
1069 errExit("mount bind");
1070 free(proc);
1071 close(fd);
1072
1073 // mount a tmpfs and initialize home directory
1074 fs_private();
1075 }
1076 else
1077 home_dir = 0;
1078 }
1079 714
1080 // go through profile rules again, and interpret whitelist commands 715 // go through profile rules again, and interpret whitelist commands
1081 entry = cfg.profile; 716 entry = cfg.profile;
1082 while (entry) { 717 while (entry) {
1083 // handle only whitelist commands 718 if (entry->wparam) {
1084 if (strncmp(entry->data, "whitelist ", 10)) { 719 char *file = entry->wparam->file;
1085 entry = entry->next; 720 char *link = entry->wparam->link;
1086 continue; 721 const char *topdir = entry->wparam->top->path;
1087 } 722 size_t topdir_len = strlen(topdir);
1088 723 int dirfd = entry->wparam->top->fd;
1089//printf("here %d#%s#\n", __LINE__, entry->data); 724
1090 // whitelist the real file 725 // top level directories of link and file can differ
1091 whitelist_path(entry); 726 // whitelist the file only if it is in same top level directory
1092 727 if (strncmp(file, topdir, topdir_len) == 0 && file[topdir_len] == '/') {
1093 // create the link if any 728 // get path relative to top level directory
1094 if (entry->link) { 729 const char *rel = file + topdir_len + 1;
1095 // if the link is already there, do not bother 730 whitelist_file(dirfd, topdir, rel, file);
1096 if (lstat(entry->link, &s) != 0) {
1097 // create the path if necessary
1098 // entry->link has no trailing slashes or single dots
1099 int fd = mkpath(entry->link, 0755);
1100 if (fd == -1) {
1101 if (arg_debug || arg_debug_whitelists)
1102 printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link);
1103 free(entry->link);
1104 entry->link = NULL;
1105 entry = entry->next;
1106 continue;
1107 }
1108 // get file name of symlink
1109 const char *file = gnu_basename(entry->link);
1110 // create the link
1111 int rv = symlinkat(entry->data + 10, fd, file);
1112 if (rv) {
1113 if (arg_debug || arg_debug_whitelists) {
1114 perror("symlink");
1115 printf("Debug %d: cannot create symbolic link %s\n", __LINE__, entry->link);
1116 }
1117 }
1118 else if (arg_debug || arg_debug_whitelists)
1119 printf("Created symbolic link %s -> %s\n", entry->link, entry->data + 10);
1120 close(fd);
1121 } 731 }
1122 free(entry->link);
1123 entry->link = NULL;
1124 }
1125
1126 entry = entry->next;
1127 }
1128
1129 // mask the real home directory, currently mounted on RUN_WHITELIST_HOME_DIR
1130 if (home_dir) {
1131 if (mount("tmpfs", RUN_WHITELIST_HOME_USER_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1132 errExit("mount tmpfs");
1133 fs_logger2("tmpfs", RUN_WHITELIST_HOME_USER_DIR);
1134 }
1135
1136 // mask the real /tmp directory, currently mounted on RUN_WHITELIST_TMP_DIR
1137 if (tmp_dir) {
1138 if (mount("tmpfs", RUN_WHITELIST_TMP_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1139 errExit("mount tmpfs");
1140 fs_logger2("tmpfs", RUN_WHITELIST_TMP_DIR);
1141 }
1142
1143 // mask the real /var directory, currently mounted on RUN_WHITELIST_VAR_DIR
1144 if (var_dir) {
1145 if (mount("tmpfs", RUN_WHITELIST_VAR_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1146 errExit("mount tmpfs");
1147 fs_logger2("tmpfs", RUN_WHITELIST_VAR_DIR);
1148 }
1149
1150 // mask the real /opt directory, currently mounted on RUN_WHITELIST_OPT_DIR
1151 if (opt_dir) {
1152 if (mount("tmpfs", RUN_WHITELIST_OPT_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1153 errExit("mount tmpfs");
1154 fs_logger2("tmpfs", RUN_WHITELIST_OPT_DIR);
1155 }
1156
1157 // mask the real /dev directory, currently mounted on RUN_WHITELIST_DEV_DIR
1158 if (dev_dir) {
1159 if (mount("tmpfs", RUN_WHITELIST_DEV_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1160 errExit("mount tmpfs");
1161 fs_logger2("tmpfs", RUN_WHITELIST_DEV_DIR);
1162 }
1163
1164 // mask the real /media directory, currently mounted on RUN_WHITELIST_MEDIA_DIR
1165 if (media_dir) {
1166 if (mount("tmpfs", RUN_WHITELIST_MEDIA_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1167 errExit("mount tmpfs");
1168 fs_logger2("tmpfs", RUN_WHITELIST_MEDIA_DIR);
1169 }
1170 732
1171 // mask the real /mnt directory, currently mounted on RUN_WHITELIST_MNT_DIR 733 // create the link if any
1172 if (mnt_dir) { 734 if (link)
1173 if (mount("tmpfs", RUN_WHITELIST_MNT_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0) 735 whitelist_symlink(topdir, link, file);
1174 errExit("mount tmpfs");
1175 fs_logger2("tmpfs", RUN_WHITELIST_MNT_DIR);
1176 }
1177
1178 // mask the real /srv directory, currently mounted on RUN_WHITELIST_SRV_DIR
1179 if (srv_dir) {
1180 if (mount("tmpfs", RUN_WHITELIST_SRV_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1181 errExit("mount tmpfs");
1182 fs_logger2("tmpfs", RUN_WHITELIST_SRV_DIR);
1183 }
1184
1185 // mask the real /etc directory, currently mounted on RUN_WHITELIST_ETC_DIR
1186 if (etc_dir) {
1187 if (mount("tmpfs", RUN_WHITELIST_ETC_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1188 errExit("mount tmpfs");
1189 fs_logger2("tmpfs", RUN_WHITELIST_ETC_DIR);
1190 }
1191
1192 // mask the real /usr/share directory, currently mounted on RUN_WHITELIST_SHARE_DIR
1193 if (share_dir) {
1194 if (mount("tmpfs", RUN_WHITELIST_SHARE_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1195 errExit("mount tmpfs");
1196 fs_logger2("tmpfs", RUN_WHITELIST_SHARE_DIR);
1197 }
1198 736
1199 // mask the real /sys/module directory, currently mounted on RUN_WHITELIST_MODULE_DIR 737 free(link);
1200 if (module_dir) { 738 free(file);
1201 if (mount("tmpfs", RUN_WHITELIST_MODULE_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0) 739 free(entry->wparam);
1202 errExit("mount tmpfs"); 740 entry->wparam = NULL;
1203 fs_logger2("tmpfs", RUN_WHITELIST_MODULE_DIR); 741 }
1204 }
1205 742
1206 // mask the real /run/user/$uid directory, currently mounted on RUN_WHITELIST_RUN_USER_DIR 743 entry = entry->next;
1207 if (run_dir) {
1208 if (mount("tmpfs", RUN_WHITELIST_RUN_USER_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,gid=0") < 0)
1209 errExit("mount tmpfs");
1210 fs_logger2("tmpfs", RUN_WHITELIST_RUN_USER_DIR);
1211 } 744 }
1212 745
746 // release resources
1213 free(runuser); 747 free(runuser);
1214 return;
1215 748
1216errexit: 749 size_t i;
1217 fprintf(stderr, "Error: invalid whitelist path %s\n", new_name); 750 for (i = 0; i < TOP_MAX && topdirs[i].path; i++) {
1218 exit(1); 751 free(topdirs[i].path);
752 close(topdirs[i].fd);
753 }
754 free(topdirs);
1219} 755}
diff --git a/src/firejail/pulseaudio.c b/src/firejail/pulseaudio.c
index 97c022bad..1b01a71c6 100644
--- a/src/firejail/pulseaudio.c
+++ b/src/firejail/pulseaudio.c
@@ -131,7 +131,7 @@ void pulseaudio_init(void) {
131 131
132 // if ~/.config/pulse exists and there are no symbolic links, mount the new directory 132 // if ~/.config/pulse exists and there are no symbolic links, mount the new directory
133 // else set environment variable 133 // else set environment variable
134 int fd = safe_fd(homeusercfg, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 134 int fd = safer_openat(-1, homeusercfg, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
135 if (fd == -1) { 135 if (fd == -1) {
136 pulseaudio_fallback(pulsecfg); 136 pulseaudio_fallback(pulsecfg);
137 goto out; 137 goto out;
diff --git a/src/firejail/restrict_users.c b/src/firejail/restrict_users.c
index e173c9888..53e395b89 100644
--- a/src/firejail/restrict_users.c
+++ b/src/firejail/restrict_users.c
@@ -73,7 +73,7 @@ static void sanitize_home(void) {
73 if (arg_debug) 73 if (arg_debug)
74 printf("Cleaning /home directory\n"); 74 printf("Cleaning /home directory\n");
75 // open user home directory in order to keep it around 75 // open user home directory in order to keep it around
76 int fd = safe_fd(cfg.homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 76 int fd = safer_openat(-1, cfg.homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
77 if (fd == -1) 77 if (fd == -1)
78 goto errout; 78 goto errout;
79 if (fstat(fd, &s) == -1) { // FUSE 79 if (fstat(fd, &s) == -1) { // FUSE
diff --git a/src/firejail/util.c b/src/firejail/util.c
index 2c985c0d6..2731f61dc 100644
--- a/src/firejail/util.c
+++ b/src/firejail/util.c
@@ -544,11 +544,13 @@ char *split_comma(char *str) {
544} 544}
545 545
546 546
547// remove consecutive and trailing slashes 547// simplify absolute path by removing
548// and return allocated memory 548// 1) consecutive and trailing slashes, and
549// e.g. /home//user/ -> /home/user 549// 2) segments with a single dot
550// for example /foo//./bar/ -> /foo/bar
550char *clean_pathname(const char *path) { 551char *clean_pathname(const char *path) {
551 assert(path); 552 assert(path && path[0] == '/');
553
552 size_t len = strlen(path); 554 size_t len = strlen(path);
553 char *rv = malloc(len + 1); 555 char *rv = malloc(len + 1);
554 if (!rv) 556 if (!rv)
@@ -557,15 +559,23 @@ char *clean_pathname(const char *path) {
557 size_t i = 0; 559 size_t i = 0;
558 size_t j = 0; 560 size_t j = 0;
559 while (path[i]) { 561 while (path[i]) {
560 while (path[i] == '/' && path[i+1] == '/') 562 if (path[i] == '/') {
561 i++; 563 while (path[i+1] == '/' ||
564 (path[i+1] == '.' && path[i+2] == '/'))
565 i++;
566 }
567
562 rv[j++] = path[i++]; 568 rv[j++] = path[i++];
563 } 569 }
564 rv[j] = '\0'; 570 rv[j] = '\0';
565 571
572 // remove a trailing dot
573 if (j > 1 && rv[j - 1] == '.' && rv[j - 2] == '/')
574 rv[--j] = '\0';
575
566 // remove a trailing slash 576 // remove a trailing slash
567 if (j > 1 && rv[j - 1] == '/') 577 if (j > 1 && rv[j - 1] == '/')
568 rv[j - 1] = '\0'; 578 rv[--j] = '\0';
569 579
570 return rv; 580 return rv;
571} 581}
@@ -905,9 +915,9 @@ int remove_overlay_directory(void) {
905 errExit("fork"); 915 errExit("fork");
906 if (child == 0) { 916 if (child == 0) {
907 // open ~/.firejail, fails if there is any symlink 917 // open ~/.firejail, fails if there is any symlink
908 int fd = safe_fd(path, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 918 int fd = safer_openat(-1, path, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
909 if (fd == -1) 919 if (fd == -1)
910 errExit("safe_fd"); 920 errExit("safer_openat");
911 // chdir to ~/.firejail 921 // chdir to ~/.firejail
912 if (fchdir(fd) == -1) 922 if (fchdir(fd) == -1)
913 errExit("fchdir"); 923 errExit("fchdir");
@@ -1125,13 +1135,13 @@ void disable_file_path(const char *path, const char *file) {
1125} 1135}
1126 1136
1127// open an existing file without following any symbolic link 1137// open an existing file without following any symbolic link
1128int safe_fd(const char *path, int flags) { 1138// relative paths are interpreted relative to dirfd
1139// ignore dirfd if path is absolute
1140// https://web.archive.org/web/20180419120236/https://blogs.gnome.org/jamesh/2018/04/19/secure-mounts
1141int safer_openat(int dirfd, const char *path, int flags) {
1142 assert(path && path[0]);
1129 flags |= O_NOFOLLOW; 1143 flags |= O_NOFOLLOW;
1130 assert(path); 1144
1131 if (*path != '/' || strstr(path, "..")) {
1132 fprintf(stderr, "Error: invalid path %s\n", path);
1133 exit(1);
1134 }
1135 int fd = -1; 1145 int fd = -1;
1136 1146
1137#ifdef __NR_openat2 // kernel 5.6 or better 1147#ifdef __NR_openat2 // kernel 5.6 or better
@@ -1139,7 +1149,7 @@ int safe_fd(const char *path, int flags) {
1139 memset(&oh, 0, sizeof(oh)); 1149 memset(&oh, 0, sizeof(oh));
1140 oh.flags = flags; 1150 oh.flags = flags;
1141 oh.resolve = RESOLVE_NO_SYMLINKS; 1151 oh.resolve = RESOLVE_NO_SYMLINKS;
1142 fd = syscall(__NR_openat2, -1, path, &oh, sizeof(struct open_how)); 1152 fd = syscall(__NR_openat2, dirfd, path, &oh, sizeof(struct open_how));
1143 if (fd != -1 || errno != ENOSYS) 1153 if (fd != -1 || errno != ENOSYS)
1144 return fd; 1154 return fd;
1145#endif 1155#endif
@@ -1150,18 +1160,23 @@ int safe_fd(const char *path, int flags) {
1150 if (!dup) 1160 if (!dup)
1151 errExit("strdup"); 1161 errExit("strdup");
1152 char *tok = strtok(dup, "/"); 1162 char *tok = strtok(dup, "/");
1153 if (!tok) { // root directory 1163 if (!tok) { // nothing to do, path is the root directory
1154 free(dup); 1164 free(dup);
1155 return open("/", flags); 1165 return openat(dirfd, path, flags);
1156 } 1166 }
1157 char *last_tok = EMPTY_STRING; 1167 char *last_tok = EMPTY_STRING;
1158 int parentfd = open("/", O_PATH|O_CLOEXEC); 1168
1169 int parentfd;
1170 if (path[0] == '/')
1171 parentfd = open("/", O_PATH|O_CLOEXEC);
1172 else
1173 parentfd = fcntl(dirfd, F_DUPFD_CLOEXEC, 0);
1159 if (parentfd == -1) 1174 if (parentfd == -1)
1160 errExit("open"); 1175 errExit("open/fcntl");
1161 1176
1162 while(1) { 1177 while (1) {
1163 // open path component, assuming it is a directory; this fails with ENOTDIR if it is a symbolic link 1178 // open path component, assuming it is a directory; this fails with ENOTDIR if it is a symbolic link
1164 // if token is a single dot, the previous directory is reopened 1179 // if token is a single dot, the directory referred to by parentfd is reopened
1165 fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 1180 fd = openat(parentfd, tok, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
1166 if (fd == -1) { 1181 if (fd == -1) {
1167 // if the following token is NULL, the current token is the final path component 1182 // if the following token is NULL, the current token is the final path component
@@ -1292,13 +1307,11 @@ pid_t require_pid(const char *name) {
1292// return 1 if there is a link somewhere in path of directory 1307// return 1 if there is a link somewhere in path of directory
1293static int has_link(const char *dir) { 1308static int has_link(const char *dir) {
1294 assert(dir); 1309 assert(dir);
1295 int fd = safe_fd(dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 1310 int fd = safer_openat(-1, dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
1296 if (fd == -1) { 1311 if (fd != -1)
1297 if ((errno == ELOOP || errno == ENOTDIR) && is_dir(dir))
1298 return 1;
1299 }
1300 else
1301 close(fd); 1312 close(fd);
1313 else if (errno == ELOOP || (errno == ENOTDIR && is_dir(dir)))
1314 return 1;
1302 return 0; 1315 return 0;
1303} 1316}
1304 1317
diff --git a/src/firejail/x11.c b/src/firejail/x11.c
index c0587ffc1..257d376a1 100644
--- a/src/firejail/x11.c
+++ b/src/firejail/x11.c
@@ -1239,9 +1239,9 @@ void x11_xorg(void) {
1239 } 1239 }
1240 } 1240 }
1241 // get a file descriptor for ~/.Xauthority 1241 // get a file descriptor for ~/.Xauthority
1242 int dst = safe_fd(dest, O_PATH|O_NOFOLLOW|O_CLOEXEC); 1242 int dst = safer_openat(-1, dest, O_PATH|O_NOFOLLOW|O_CLOEXEC);
1243 if (dst == -1) 1243 if (dst == -1)
1244 errExit("safe_fd"); 1244 errExit("safer_openat");
1245 // check if the actual mount destination is a user owned regular file 1245 // check if the actual mount destination is a user owned regular file
1246 if (fstat(dst, &s) == -1) 1246 if (fstat(dst, &s) == -1)
1247 errExit("fstat"); 1247 errExit("fstat");
@@ -1263,9 +1263,9 @@ void x11_xorg(void) {
1263 fs_remount(RUN_XAUTHORITY_SEC_DIR, MOUNT_NOEXEC, 0); 1263 fs_remount(RUN_XAUTHORITY_SEC_DIR, MOUNT_NOEXEC, 0);
1264 1264
1265 // get a file descriptor for the new Xauthority file 1265 // get a file descriptor for the new Xauthority file
1266 int src = safe_fd(tmpfname, O_PATH|O_NOFOLLOW|O_CLOEXEC); 1266 int src = safer_openat(-1, tmpfname, O_PATH|O_NOFOLLOW|O_CLOEXEC);
1267 if (src == -1) 1267 if (src == -1)
1268 errExit("safe_fd"); 1268 errExit("safer_openat");
1269 if (fstat(src, &s) == -1) 1269 if (fstat(src, &s) == -1)
1270 errExit("fstat"); 1270 errExit("fstat");
1271 if (!S_ISREG(s.st_mode)) { 1271 if (!S_ISREG(s.st_mode)) {
@@ -1373,7 +1373,7 @@ void fs_x11(void) {
1373 char *wx11file; 1373 char *wx11file;
1374 if (asprintf(&wx11file, "%s/X%d", RUN_WHITELIST_X11_DIR, display) == -1) 1374 if (asprintf(&wx11file, "%s/X%d", RUN_WHITELIST_X11_DIR, display) == -1)
1375 errExit("asprintf"); 1375 errExit("asprintf");
1376 fd = safe_fd(wx11file, O_PATH|O_NOFOLLOW|O_CLOEXEC); 1376 fd = safer_openat(-1, wx11file, O_PATH|O_NOFOLLOW|O_CLOEXEC);
1377 if (fd == -1) 1377 if (fd == -1)
1378 errExit("opening X11 socket"); 1378 errExit("opening X11 socket");
1379 // confirm once more we are mounting a socket 1379 // confirm once more we are mounting a socket
diff --git a/src/include/rundefs.h b/src/include/rundefs.h
index d14f6782f..a172dd511 100644
--- a/src/include/rundefs.h
+++ b/src/include/rundefs.h
@@ -84,18 +84,6 @@
84#define RUN_DEVLOG_FILE RUN_MNT_DIR "/devlog" 84#define RUN_DEVLOG_FILE RUN_MNT_DIR "/devlog"
85 85
86#define RUN_WHITELIST_X11_DIR RUN_MNT_DIR "/orig-x11" 86#define RUN_WHITELIST_X11_DIR RUN_MNT_DIR "/orig-x11"
87#define RUN_WHITELIST_HOME_USER_DIR RUN_MNT_DIR "/orig-home-user" // home directory whitelisting
88#define RUN_WHITELIST_RUN_USER_DIR RUN_MNT_DIR "/orig-run-user" // run directory whitelisting
89#define RUN_WHITELIST_TMP_DIR RUN_MNT_DIR "/orig-tmp"
90#define RUN_WHITELIST_MEDIA_DIR RUN_MNT_DIR "/orig-media"
91#define RUN_WHITELIST_MNT_DIR RUN_MNT_DIR "/orig-mnt"
92#define RUN_WHITELIST_VAR_DIR RUN_MNT_DIR "/orig-var"
93#define RUN_WHITELIST_DEV_DIR RUN_MNT_DIR "/orig-dev"
94#define RUN_WHITELIST_OPT_DIR RUN_MNT_DIR "/orig-opt"
95#define RUN_WHITELIST_SRV_DIR RUN_MNT_DIR "/orig-srv"
96#define RUN_WHITELIST_ETC_DIR RUN_MNT_DIR "/orig-etc"
97#define RUN_WHITELIST_SHARE_DIR RUN_MNT_DIR "/orig-share"
98#define RUN_WHITELIST_MODULE_DIR RUN_MNT_DIR "/orig-module"
99 87
100#define RUN_XAUTHORITY_FILE RUN_MNT_DIR "/.Xauthority" // private options 88#define RUN_XAUTHORITY_FILE RUN_MNT_DIR "/.Xauthority" // private options
101#define RUN_XAUTH_FILE RUN_MNT_DIR "/xauth" // x11=xorg 89#define RUN_XAUTH_FILE RUN_MNT_DIR "/xauth" // x11=xorg