aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/fbuilder/build_profile.c44
-rw-r--r--src/fbuilder/main.c12
-rw-r--r--src/firejail/appimage.c6
-rw-r--r--src/firejail/bandwidth.c32
-rw-r--r--src/firejail/caps.c2
-rw-r--r--src/firejail/cgroup.c20
-rw-r--r--src/firejail/checkcfg.c28
-rw-r--r--src/firejail/chroot.c8
-rw-r--r--src/firejail/cpu.c6
-rw-r--r--src/firejail/dbus.c2
-rw-r--r--src/firejail/dhcp.c2
-rw-r--r--src/firejail/env.c7
-rw-r--r--src/firejail/firejail.h31
-rw-r--r--src/firejail/fs.c10
-rw-r--r--src/firejail/fs_dev.c6
-rw-r--r--src/firejail/fs_etc.c2
-rw-r--r--src/firejail/fs_home.c10
-rw-r--r--src/firejail/fs_hostname.c6
-rw-r--r--src/firejail/fs_lib.c2
-rw-r--r--src/firejail/fs_logger.c25
-rw-r--r--src/firejail/fs_trace.c9
-rw-r--r--src/firejail/fs_var.c13
-rw-r--r--src/firejail/fs_whitelist.c1336
-rw-r--r--src/firejail/join.c6
-rw-r--r--src/firejail/ls.c2
-rw-r--r--src/firejail/macros.c2
-rw-r--r--src/firejail/main.c37
-rw-r--r--src/firejail/network.c2
-rw-r--r--src/firejail/network_main.c2
-rw-r--r--src/firejail/no_sandbox.c14
-rw-r--r--src/firejail/preproc.c2
-rw-r--r--src/firejail/profile.c10
-rw-r--r--src/firejail/protocol.c4
-rw-r--r--src/firejail/pulseaudio.c4
-rw-r--r--src/firejail/restrict_users.c10
-rw-r--r--src/firejail/restricted_shell.c4
-rw-r--r--src/firejail/run_files.c6
-rw-r--r--src/firejail/sandbox.c6
-rw-r--r--src/firejail/sbox.c4
-rw-r--r--src/firejail/seccomp.c8
-rw-r--r--src/firejail/shutdown.c2
-rw-r--r--src/firejail/usage.c4
-rw-r--r--src/firejail/util.c94
-rw-r--r--src/firejail/x11.c14
-rw-r--r--src/firemon/firemon.c2
-rw-r--r--src/include/rundefs.h12
-rw-r--r--src/man/firejail-profile.txt99
-rw-r--r--src/man/firejail.txt28
-rw-r--r--src/profstats/main.c11
-rw-r--r--src/zsh_completion/_firejail.in2
50 files changed, 834 insertions, 1176 deletions
diff --git a/src/fbuilder/build_profile.c b/src/fbuilder/build_profile.c
index fb53f70a6..1726b4dbb 100644
--- a/src/fbuilder/build_profile.c
+++ b/src/fbuilder/build_profile.c
@@ -145,9 +145,9 @@ void build_profile(int argc, char **argv, int index, FILE *fp) {
145 fprintf(fp, "# program name) in ~/.config/firejail directory. Firejail will find it\n"); 145 fprintf(fp, "# program name) in ~/.config/firejail directory. Firejail will find it\n");
146 fprintf(fp, "# automatically every time you sandbox your application.\n#\n"); 146 fprintf(fp, "# automatically every time you sandbox your application.\n#\n");
147 fprintf(fp, "# Run \"firejail application\" to test it. In the file there are\n"); 147 fprintf(fp, "# Run \"firejail application\" to test it. In the file there are\n");
148 fprintf(fp, "# some other commands you can try. Enable them by removing the \"#\".\n"); 148 fprintf(fp, "# some other commands you can try. Enable them by removing the \"#\".\n\n");
149 149
150 fprintf(fp, "\n# Firejail profile for %s\n", argv[index]); 150 fprintf(fp, "# Firejail profile for %s\n", argv[index]);
151 fprintf(fp, "# Persistent local customizations\n"); 151 fprintf(fp, "# Persistent local customizations\n");
152 fprintf(fp, "#include %s.local\n", argv[index]); 152 fprintf(fp, "#include %s.local\n", argv[index]);
153 fprintf(fp, "# Persistent global definitions\n"); 153 fprintf(fp, "# Persistent global definitions\n");
@@ -164,6 +164,7 @@ void build_profile(int argc, char **argv, int index, FILE *fp) {
164 fprintf(fp, "#include disable-interpreters.inc\n"); 164 fprintf(fp, "#include disable-interpreters.inc\n");
165 fprintf(fp, "include disable-passwdmgr.inc\n"); 165 fprintf(fp, "include disable-passwdmgr.inc\n");
166 fprintf(fp, "include disable-programs.inc\n"); 166 fprintf(fp, "include disable-programs.inc\n");
167 fprintf(fp, "#include disable-shell.inc\n");
167 fprintf(fp, "#include disable-xdg.inc\n"); 168 fprintf(fp, "#include disable-xdg.inc\n");
168 fprintf(fp, "\n"); 169 fprintf(fp, "\n");
169 170
@@ -171,29 +172,27 @@ void build_profile(int argc, char **argv, int index, FILE *fp) {
171 fprintf(fp, "### If something goes wrong, this section is the first one to comment out.\n"); 172 fprintf(fp, "### If something goes wrong, this section is the first one to comment out.\n");
172 fprintf(fp, "### Instead, you'll have to relay on the basic blacklisting above.\n"); 173 fprintf(fp, "### Instead, you'll have to relay on the basic blacklisting above.\n");
173 build_home(trace_output, fp); 174 build_home(trace_output, fp);
175 fprintf(fp, "\n");
174 176
175 fprintf(fp, "\n### The Rest of the Filesystem ###\n"); 177 fprintf(fp, "### Filesystem Whitelisting ###\n");
176 build_share(trace_output, fp); 178 build_share(trace_output, fp);
179 //todo: include whitelist-runuser-common.inc
177 build_var(trace_output, fp); 180 build_var(trace_output, fp);
178 build_bin(trace_output, fp); 181 fprintf(fp, "\n");
179 build_dev(trace_output, fp);
180 fprintf(fp, "#nodvd\n");
181 fprintf(fp, "#noinput\n");
182 fprintf(fp, "#notv\n");
183 fprintf(fp, "#nou2f\n");
184 fprintf(fp, "#novideo\n");
185 build_etc(trace_output, fp);
186 build_tmp(trace_output, fp);
187 182
188 fprintf(fp, "\n### Security Filters ###\n");
189 fprintf(fp, "#apparmor\n"); 183 fprintf(fp, "#apparmor\n");
190 fprintf(fp, "caps.drop all\n"); 184 fprintf(fp, "caps.drop all\n");
185 fprintf(fp, "ipc-namespace\n");
191 fprintf(fp, "netfilter\n"); 186 fprintf(fp, "netfilter\n");
187 fprintf(fp, "#nodvd\n");
192 fprintf(fp, "#nogroups\n"); 188 fprintf(fp, "#nogroups\n");
193 fprintf(fp, "#noroot\n"); 189 fprintf(fp, "#noinput\n");
194 fprintf(fp, "nonewprivs\n"); 190 fprintf(fp, "nonewprivs\n");
191 fprintf(fp, "noroot\n");
192 fprintf(fp, "#notv\n");
193 fprintf(fp, "#nou2f\n");
194 fprintf(fp, "#novideo\n");
195 build_protocol(trace_output, fp); 195 build_protocol(trace_output, fp);
196
197 fprintf(fp, "seccomp\n"); 196 fprintf(fp, "seccomp\n");
198 if (!have_strace) { 197 if (!have_strace) {
199 fprintf(fp, "### If you install strace on your system, Firejail will also create a\n"); 198 fprintf(fp, "### If you install strace on your system, Firejail will also create a\n");
@@ -203,8 +202,21 @@ void build_profile(int argc, char **argv, int index, FILE *fp) {
203 fprintf(fp, "### Yama security module prevents creation of a whitelisted seccomp filter\n"); 202 fprintf(fp, "### Yama security module prevents creation of a whitelisted seccomp filter\n");
204 else 203 else
205 build_seccomp(strace_output, fp); 204 build_seccomp(strace_output, fp);
206 fprintf(fp, "#shell none\n"); 205 fprintf(fp, "shell none\n");
207 fprintf(fp, "#tracelog\n"); 206 fprintf(fp, "#tracelog\n");
207 fprintf(fp, "\n");
208
209 fprintf(fp, "#disable-mnt\n");
210 build_bin(trace_output, fp);
211 fprintf(fp, "#private-lib\n");
212 build_dev(trace_output, fp);
213 build_etc(trace_output, fp);
214 build_tmp(trace_output, fp);
215 fprintf(fp, "\n");
216
217 fprintf(fp, "#dbus-user none\n");
218 fprintf(fp, "#dbus-system none\n");
219 fprintf(fp, "#memory-deny-write-execute\n");
208 220
209 if (!arg_debug) { 221 if (!arg_debug) {
210 unlink(trace_output); 222 unlink(trace_output);
diff --git a/src/fbuilder/main.c b/src/fbuilder/main.c
index f4917aefc..35ec49519 100644
--- a/src/fbuilder/main.c
+++ b/src/fbuilder/main.c
@@ -58,10 +58,16 @@ printf("\n");
58 exit(1); 58 exit(1);
59 } 59 }
60 60
61 // don't run if the file exists
62 if (access(argv[i] + 8, F_OK) == 0) {
63 fprintf(stderr, "Error: the profile file already exists. Please use a different file name.\n");
64 exit(1);
65 }
66
61 // check file access 67 // check file access
62 fp = fopen(argv[i] + 8, "w"); 68 fp = fopen(argv[i] + 8, "w");
63 if (!fp) { 69 if (!fp) {
64 fprintf(stderr, "Error fbuild: cannot open profile file.\n"); 70 fprintf(stderr, "Error: cannot open profile file.\n");
65 exit(1); 71 exit(1);
66 } 72 }
67 prof_file = 1; 73 prof_file = 1;
@@ -69,7 +75,7 @@ printf("\n");
69 } 75 }
70 else { 76 else {
71 if (*argv[i] == '-') { 77 if (*argv[i] == '-') {
72 fprintf(stderr, "Error fbuilder: invalid program\n"); 78 fprintf(stderr, "Error: invalid program\n");
73 usage(); 79 usage();
74 exit(1); 80 exit(1);
75 } 81 }
@@ -79,7 +85,7 @@ printf("\n");
79 } 85 }
80 86
81 if (prog_index == 0) { 87 if (prog_index == 0) {
82 fprintf(stderr, "Error fbuilder: program and arguments required\n"); 88 fprintf(stderr, "Error : program and arguments required\n");
83 usage(); 89 usage();
84 if (prof_file) 90 if (prof_file)
85 fclose(fp); 91 fclose(fp);
diff --git a/src/firejail/appimage.c b/src/firejail/appimage.c
index 59758bf2d..6b9fed765 100644
--- a/src/firejail/appimage.c
+++ b/src/firejail/appimage.c
@@ -67,7 +67,7 @@ void appimage_set(const char *appimage) {
67 67
68 // find or allocate a free loop device to use 68 // find or allocate a free loop device to use
69 EUID_ROOT(); 69 EUID_ROOT();
70 int cfd = open("/dev/loop-control", O_RDWR); 70 int cfd = open("/dev/loop-control", O_RDWR|O_CLOEXEC);
71 if (cfd == -1) 71 if (cfd == -1)
72 err_loop(); 72 err_loop();
73 int devnr = ioctl(cfd, LOOP_CTL_GET_FREE); 73 int devnr = ioctl(cfd, LOOP_CTL_GET_FREE);
@@ -78,7 +78,7 @@ void appimage_set(const char *appimage) {
78 errExit("asprintf"); 78 errExit("asprintf");
79 79
80 // associate loop device with appimage 80 // associate loop device with appimage
81 int lfd = open(devloop, O_RDONLY); 81 int lfd = open(devloop, O_RDONLY|O_CLOEXEC);
82 if (lfd == -1) 82 if (lfd == -1)
83 err_loop(); 83 err_loop();
84 if (ioctl(lfd, LOOP_SET_FD, ffd) == -1) 84 if (ioctl(lfd, LOOP_SET_FD, ffd) == -1)
@@ -146,7 +146,7 @@ void appimage_mount(void) {
146void appimage_clear(void) { 146void appimage_clear(void) {
147 EUID_ROOT(); 147 EUID_ROOT();
148 if (devloop) { 148 if (devloop) {
149 int lfd = open(devloop, O_RDONLY); 149 int lfd = open(devloop, O_RDONLY|O_CLOEXEC);
150 if (lfd != -1) { 150 if (lfd != -1) {
151 if (ioctl(lfd, LOOP_CLR_FD, 0) != -1) 151 if (ioctl(lfd, LOOP_CLR_FD, 0) != -1)
152 fmessage("AppImage detached\n"); 152 fmessage("AppImage detached\n");
diff --git a/src/firejail/bandwidth.c b/src/firejail/bandwidth.c
index 1c952c0bc..a085f2c27 100644
--- a/src/firejail/bandwidth.c
+++ b/src/firejail/bandwidth.c
@@ -22,6 +22,7 @@
22#include <sys/types.h> 22#include <sys/types.h>
23#include <sys/stat.h> 23#include <sys/stat.h>
24#include <unistd.h> 24#include <unistd.h>
25#include <errno.h>
25#include <net/if.h> 26#include <net/if.h>
26#include "firejail.h" 27#include "firejail.h"
27 28
@@ -119,26 +120,19 @@ static void bandwidth_create_run_file(pid_t pid) {
119 if (asprintf(&fname, "%s/%d-bandwidth", RUN_FIREJAIL_BANDWIDTH_DIR, (int) pid) == -1) 120 if (asprintf(&fname, "%s/%d-bandwidth", RUN_FIREJAIL_BANDWIDTH_DIR, (int) pid) == -1)
120 errExit("asprintf"); 121 errExit("asprintf");
121 122
122 // if the file already exists, do nothing
123 struct stat s;
124 if (stat(fname, &s) == 0) {
125 free(fname);
126 return;
127 }
128
129 // create an empty file and set mod and ownership 123 // create an empty file and set mod and ownership
130 /* coverity[toctou] */ 124 // if the file already exists, do nothing
131 FILE *fp = fopen(fname, "w"); 125 FILE *fp = fopen(fname, "wxe");
132 if (fp) { 126 free(fname);
133 SET_PERMS_STREAM(fp, 0, 0, 0644); 127 if (!fp) {
134 fclose(fp); 128 if (errno == EEXIST)
135 } 129 return;
136 else {
137 fprintf(stderr, "Error: cannot create bandwidth file\n"); 130 fprintf(stderr, "Error: cannot create bandwidth file\n");
138 exit(1); 131 exit(1);
139 } 132 }
140 133
141 free(fname); 134 SET_PERMS_STREAM(fp, 0, 0, 0644);
135 fclose(fp);
142} 136}
143 137
144 138
@@ -148,7 +142,7 @@ void network_set_run_file(pid_t pid) {
148 errExit("asprintf"); 142 errExit("asprintf");
149 143
150 // create an empty file and set mod and ownership 144 // create an empty file and set mod and ownership
151 FILE *fp = fopen(fname, "w"); 145 FILE *fp = fopen(fname, "we");
152 if (fp) { 146 if (fp) {
153 if (cfg.bridge0.configured) 147 if (cfg.bridge0.configured)
154 fprintf(fp, "%s:%s\n", cfg.bridge0.dev, cfg.bridge0.devsandbox); 148 fprintf(fp, "%s:%s\n", cfg.bridge0.dev, cfg.bridge0.devsandbox);
@@ -178,7 +172,7 @@ static void read_bandwidth_file(pid_t pid) {
178 if (asprintf(&fname, "%s/%d-bandwidth", RUN_FIREJAIL_BANDWIDTH_DIR, (int) pid) == -1) 172 if (asprintf(&fname, "%s/%d-bandwidth", RUN_FIREJAIL_BANDWIDTH_DIR, (int) pid) == -1)
179 errExit("asprintf"); 173 errExit("asprintf");
180 174
181 FILE *fp = fopen(fname, "r"); 175 FILE *fp = fopen(fname, "re");
182 if (fp) { 176 if (fp) {
183 char buf[1024]; 177 char buf[1024];
184 while (fgets(buf, 1024,fp)) { 178 while (fgets(buf, 1024,fp)) {
@@ -214,7 +208,7 @@ static void write_bandwidth_file(pid_t pid) {
214 if (asprintf(&fname, "%s/%d-bandwidth", RUN_FIREJAIL_BANDWIDTH_DIR, (int) pid) == -1) 208 if (asprintf(&fname, "%s/%d-bandwidth", RUN_FIREJAIL_BANDWIDTH_DIR, (int) pid) == -1)
215 errExit("asprintf"); 209 errExit("asprintf");
216 210
217 FILE *fp = fopen(fname, "w"); 211 FILE *fp = fopen(fname, "we");
218 if (fp) { 212 if (fp) {
219 IFBW *ptr = ifbw; 213 IFBW *ptr = ifbw;
220 while (ptr) { 214 while (ptr) {
@@ -307,7 +301,7 @@ void bandwidth_pid(pid_t pid, const char *command, const char *dev, int down, in
307 char *fname; 301 char *fname;
308 if (asprintf(&fname, "%s/%d-netmap", RUN_FIREJAIL_NETWORK_DIR, (int) pid) == -1) 302 if (asprintf(&fname, "%s/%d-netmap", RUN_FIREJAIL_NETWORK_DIR, (int) pid) == -1)
309 errExit("asprintf"); 303 errExit("asprintf");
310 FILE *fp = fopen(fname, "r"); 304 FILE *fp = fopen(fname, "re");
311 if (!fp) { 305 if (!fp) {
312 fprintf(stderr, "Error: cannot read network map file %s\n", fname); 306 fprintf(stderr, "Error: cannot read network map file %s\n", fname);
313 exit(1); 307 exit(1);
diff --git a/src/firejail/caps.c b/src/firejail/caps.c
index 597f9915b..5e02b99c2 100644
--- a/src/firejail/caps.c
+++ b/src/firejail/caps.c
@@ -389,7 +389,7 @@ static uint64_t extract_caps(int pid) {
389 errExit("asprintf"); 389 errExit("asprintf");
390 390
391 EUID_ROOT(); // grsecurity 391 EUID_ROOT(); // grsecurity
392 FILE *fp = fopen(file, "r"); 392 FILE *fp = fopen(file, "re");
393 EUID_USER(); // grsecurity 393 EUID_USER(); // grsecurity
394 if (!fp) 394 if (!fp)
395 goto errexit; 395 goto errexit;
diff --git a/src/firejail/cgroup.c b/src/firejail/cgroup.c
index 986b1157d..e7ffbca36 100644
--- a/src/firejail/cgroup.c
+++ b/src/firejail/cgroup.c
@@ -26,7 +26,7 @@ void save_cgroup(void) {
26 if (cfg.cgroup == NULL) 26 if (cfg.cgroup == NULL)
27 return; 27 return;
28 28
29 FILE *fp = fopen(RUN_CGROUP_CFG, "w"); 29 FILE *fp = fopen(RUN_CGROUP_CFG, "wxe");
30 if (fp) { 30 if (fp) {
31 fprintf(fp, "%s", cfg.cgroup); 31 fprintf(fp, "%s", cfg.cgroup);
32 fflush(0); 32 fflush(0);
@@ -48,7 +48,7 @@ void load_cgroup(const char *fname) {
48 if (!fname) 48 if (!fname)
49 return; 49 return;
50 50
51 FILE *fp = fopen(fname, "r"); 51 FILE *fp = fopen(fname, "re");
52 if (fp) { 52 if (fp) {
53 char buf[MAXBUF]; 53 char buf[MAXBUF];
54 if (fgets(buf, MAXBUF, fp)) { 54 if (fgets(buf, MAXBUF, fp)) {
@@ -91,19 +91,19 @@ void set_cgroup(const char *path) {
91 goto errout; 91 goto errout;
92 92
93 // tasks file exists 93 // tasks file exists
94 struct stat s; 94 FILE *fp = fopen(path, "ae");
95 if (stat(path, &s) == -1) 95 if (!fp)
96 goto errout; 96 goto errout;
97
98 // task file belongs to the user running the sandbox 97 // task file belongs to the user running the sandbox
98 int fd = fileno(fp);
99 if (fd == -1)
100 errExit("fileno");
101 struct stat s;
102 if (fstat(fd, &s) == -1)
103 errExit("fstat");
99 if (s.st_uid != getuid() && s.st_gid != getgid()) 104 if (s.st_uid != getuid() && s.st_gid != getgid())
100 goto errout2; 105 goto errout2;
101
102 // add the task to cgroup 106 // add the task to cgroup
103 /* coverity[toctou] */
104 FILE *fp = fopen(path, "a");
105 if (!fp)
106 goto errout;
107 pid_t pid = getpid(); 107 pid_t pid = getpid();
108 int rv = fprintf(fp, "%d\n", pid); 108 int rv = fprintf(fp, "%d\n", pid);
109 (void) rv; 109 (void) rv;
diff --git a/src/firejail/checkcfg.c b/src/firejail/checkcfg.c
index e1613b325..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);
@@ -59,7 +60,7 @@ int checkcfg(int val) {
59 60
60 // open configuration file 61 // open configuration file
61 const char *fname = SYSCONFDIR "/firejail.config"; 62 const char *fname = SYSCONFDIR "/firejail.config";
62 fp = fopen(fname, "r"); 63 fp = fopen(fname, "re");
63 if (!fp) { 64 if (!fp) {
64#ifdef HAVE_GLOBALCFG 65#ifdef HAVE_GLOBALCFG
65 fprintf(stderr, "Error: Firejail configuration file %s not found\n", fname); 66 fprintf(stderr, "Error: Firejail configuration file %s not found\n", fname);
@@ -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/cpu.c b/src/firejail/cpu.c
index 3427e8ade..fe7258fb0 100644
--- a/src/firejail/cpu.c
+++ b/src/firejail/cpu.c
@@ -75,7 +75,7 @@ void save_cpu(void) {
75 if (cfg.cpus == 0) 75 if (cfg.cpus == 0)
76 return; 76 return;
77 77
78 FILE *fp = fopen(RUN_CPU_CFG, "w"); 78 FILE *fp = fopen(RUN_CPU_CFG, "wxe");
79 if (fp) { 79 if (fp) {
80 fprintf(fp, "%x\n", cfg.cpus); 80 fprintf(fp, "%x\n", cfg.cpus);
81 SET_PERMS_STREAM(fp, 0, 0, 0600); 81 SET_PERMS_STREAM(fp, 0, 0, 0600);
@@ -91,7 +91,7 @@ void load_cpu(const char *fname) {
91 if (!fname) 91 if (!fname)
92 return; 92 return;
93 93
94 FILE *fp = fopen(fname, "r"); 94 FILE *fp = fopen(fname, "re");
95 if (fp) { 95 if (fp) {
96 unsigned tmp; 96 unsigned tmp;
97 int rv = fscanf(fp, "%x", &tmp); 97 int rv = fscanf(fp, "%x", &tmp);
@@ -139,7 +139,7 @@ static void print_cpu(int pid) {
139 } 139 }
140 140
141 EUID_ROOT(); // grsecurity 141 EUID_ROOT(); // grsecurity
142 FILE *fp = fopen(file, "r"); 142 FILE *fp = fopen(file, "re");
143 EUID_USER(); // grsecurity 143 EUID_USER(); // grsecurity
144 if (!fp) { 144 if (!fp) {
145 printf(" Error: cannot open %s\n", file); 145 printf(" Error: cannot open %s\n", file);
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/dhcp.c b/src/firejail/dhcp.c
index bdbb338d5..5bcdcad37 100644
--- a/src/firejail/dhcp.c
+++ b/src/firejail/dhcp.c
@@ -93,7 +93,7 @@ static pid_t dhcp_read_pidfile(const Dhclient *client) {
93 while (found == 0 && tries < 10) { 93 while (found == 0 && tries < 10) {
94 if (tries >= 1) 94 if (tries >= 1)
95 usleep(100000); 95 usleep(100000);
96 FILE *pidfile = fopen(client->pid_file, "r"); 96 FILE *pidfile = fopen(client->pid_file, "re");
97 if (pidfile) { 97 if (pidfile) {
98 long pid; 98 long pid;
99 if (fscanf(pidfile, "%ld", &pid) == 1) 99 if (fscanf(pidfile, "%ld", &pid) == 1)
diff --git a/src/firejail/env.c b/src/firejail/env.c
index 03818df0b..f5e9dd980 100644
--- a/src/firejail/env.c
+++ b/src/firejail/env.c
@@ -59,12 +59,7 @@ void env_ibus_load(void) {
59 if (asprintf(&dirname, "%s/.config/ibus/bus", cfg.homedir) == -1) 59 if (asprintf(&dirname, "%s/.config/ibus/bus", cfg.homedir) == -1)
60 errExit("asprintf"); 60 errExit("asprintf");
61 61
62 struct stat s;
63 if (stat(dirname, &s) == -1)
64 return;
65
66 // find the file 62 // find the file
67 /* coverity[toctou] */
68 DIR *dir = opendir(dirname); 63 DIR *dir = opendir(dirname);
69 if (!dir) { 64 if (!dir) {
70 free(dirname); 65 free(dirname);
@@ -84,7 +79,7 @@ void env_ibus_load(void) {
84 char *fname; 79 char *fname;
85 if (asprintf(&fname, "%s/%s", dirname, entry->d_name) == -1) 80 if (asprintf(&fname, "%s/%s", dirname, entry->d_name) == -1)
86 errExit("asprintf"); 81 errExit("asprintf");
87 FILE *fp = fopen(fname, "r"); 82 FILE *fp = fopen(fname, "re");
88 free(fname); 83 free(fname);
89 if (!fp) 84 if (!fp)
90 continue; 85 continue;
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h
index e07035ae6..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 {
@@ -314,7 +310,6 @@ extern int arg_private_cwd; // private working directory
314extern int arg_scan; // arp-scan all interfaces 310extern int arg_scan; // arp-scan all interfaces
315extern int arg_whitelist; // whitelist command 311extern int arg_whitelist; // whitelist command
316extern int arg_nosound; // disable sound 312extern int arg_nosound; // disable sound
317extern int arg_noautopulse; // disable automatic ~/.config/pulse init
318extern int arg_novideo; //disable video devices in /dev 313extern int arg_novideo; //disable video devices in /dev
319extern int arg_no3d; // disable 3d hardware acceleration 314extern int arg_no3d; // disable 3d hardware acceleration
320extern int arg_quiet; // no output for scripting 315extern int arg_quiet; // no output for scripting
@@ -323,6 +318,7 @@ extern int arg_join_filesystem; // join only the mount namespace
323extern int arg_nice; // nice value configured 318extern int arg_nice; // nice value configured
324extern int arg_ipc; // enable ipc namespace 319extern int arg_ipc; // enable ipc namespace
325extern int arg_writable_etc; // writable etc 320extern int arg_writable_etc; // writable etc
321extern int arg_keep_config_pulse; // disable automatic ~/.config/pulse init
326extern int arg_writable_var; // writable var 322extern int arg_writable_var; // writable var
327extern int arg_keep_var_tmp; // don't overwrite /var/tmp 323extern int arg_keep_var_tmp; // don't overwrite /var/tmp
328extern int arg_writable_run_user; // writable /run/user 324extern int arg_writable_run_user; // writable /run/user
@@ -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_dev.c b/src/firejail/fs_dev.c
index 2f0067c93..8c2870a4d 100644
--- a/src/firejail/fs_dev.c
+++ b/src/firejail/fs_dev.c
@@ -122,7 +122,7 @@ static void deventry_mount(void) {
122 i++; 122 i++;
123 continue; 123 continue;
124 } 124 }
125 FILE *fp = fopen(dev[i].dev_fname, "w"); 125 FILE *fp = fopen(dev[i].dev_fname, "we");
126 if (fp) { 126 if (fp) {
127 fprintf(fp, "\n"); 127 fprintf(fp, "\n");
128 SET_PERMS_STREAM(fp, s.st_uid, s.st_gid, s.st_mode); 128 SET_PERMS_STREAM(fp, s.st_uid, s.st_gid, s.st_mode);
@@ -218,7 +218,7 @@ void fs_private_dev(void){
218 struct stat s; 218 struct stat s;
219 if (stat("/dev/log", &s) == 0) { 219 if (stat("/dev/log", &s) == 0) {
220 have_devlog = 1; 220 have_devlog = 1;
221 FILE *fp = fopen(RUN_DEVLOG_FILE, "w"); 221 FILE *fp = fopen(RUN_DEVLOG_FILE, "we");
222 if (!fp) 222 if (!fp)
223 have_devlog = 0; 223 have_devlog = 0;
224 else { 224 else {
@@ -239,7 +239,7 @@ void fs_private_dev(void){
239 239
240 // bring back /dev/log 240 // bring back /dev/log
241 if (have_devlog) { 241 if (have_devlog) {
242 FILE *fp = fopen("/dev/log", "w"); 242 FILE *fp = fopen("/dev/log", "we");
243 if (fp) { 243 if (fp) {
244 fprintf(fp, "\n"); 244 fprintf(fp, "\n");
245 fclose(fp); 245 fclose(fp);
diff --git a/src/firejail/fs_etc.c b/src/firejail/fs_etc.c
index 8cb25a1ff..b0e1e1bf1 100644
--- a/src/firejail/fs_etc.c
+++ b/src/firejail/fs_etc.c
@@ -52,7 +52,7 @@ void fs_machineid(void) {
52 mid.u8[8] = (mid.u8[8] & 0x3F) | 0x80; 52 mid.u8[8] = (mid.u8[8] & 0x3F) | 0x80;
53 53
54 // write it in a file 54 // write it in a file
55 FILE *fp = fopen(RUN_MACHINEID, "w"); 55 FILE *fp = fopen(RUN_MACHINEID, "we");
56 if (!fp) 56 if (!fp)
57 errExit("fopen"); 57 errExit("fopen");
58 fprintf(fp, "%08x%08x%08x%08x\n", mid.u32[0], mid.u32[1], mid.u32[2], mid.u32[3]); 58 fprintf(fp, "%08x%08x%08x%08x\n", mid.u32[0], mid.u32[1], mid.u32[2], mid.u32[3]);
diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c
index 46f32d7ad..4bcefa443 100644
--- a/src/firejail/fs_home.c
+++ b/src/firejail/fs_home.c
@@ -130,7 +130,7 @@ static int store_xauthority(void) {
130 } 130 }
131 131
132 // create an empty file as root, and change ownership to user 132 // create an empty file as root, and change ownership to user
133 FILE *fp = fopen(dest, "w"); 133 FILE *fp = fopen(dest, "we");
134 if (fp) { 134 if (fp) {
135 fprintf(fp, "\n"); 135 fprintf(fp, "\n");
136 SET_PERMS_STREAM(fp, getuid(), getgid(), 0600); 136 SET_PERMS_STREAM(fp, getuid(), getgid(), 0600);
@@ -178,7 +178,7 @@ static int store_asoundrc(void) {
178 } 178 }
179 179
180 // create an empty file as root, and change ownership to user 180 // create an empty file as root, and change ownership to user
181 FILE *fp = fopen(dest, "w"); 181 FILE *fp = fopen(dest, "we");
182 if (fp) { 182 if (fp) {
183 fprintf(fp, "\n"); 183 fprintf(fp, "\n");
184 SET_PERMS_STREAM(fp, getuid(), getgid(), 0644); 184 SET_PERMS_STREAM(fp, getuid(), getgid(), 0644);
@@ -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_hostname.c b/src/firejail/fs_hostname.c
index 8a3bb71ea..80046f7ae 100644
--- a/src/firejail/fs_hostname.c
+++ b/src/firejail/fs_hostname.c
@@ -47,11 +47,11 @@ void fs_hostname(const char *hostname) {
47 printf("Creating a new /etc/hosts file\n"); 47 printf("Creating a new /etc/hosts file\n");
48 // copy /etc/host into our new file, and modify it on the fly 48 // copy /etc/host into our new file, and modify it on the fly
49 /* coverity[toctou] */ 49 /* coverity[toctou] */
50 FILE *fp1 = fopen("/etc/hosts", "r"); 50 FILE *fp1 = fopen("/etc/hosts", "re");
51 if (!fp1) 51 if (!fp1)
52 goto errexit; 52 goto errexit;
53 53
54 FILE *fp2 = fopen(RUN_HOSTS_FILE, "w"); 54 FILE *fp2 = fopen(RUN_HOSTS_FILE, "we");
55 if (!fp2) { 55 if (!fp2) {
56 fclose(fp1); 56 fclose(fp1);
57 goto errexit; 57 goto errexit;
@@ -165,7 +165,7 @@ void fs_resolvconf(void) {
165 165
166 if (arg_debug) 166 if (arg_debug)
167 printf("Creating a new /etc/resolv.conf file\n"); 167 printf("Creating a new /etc/resolv.conf file\n");
168 FILE *fp = fopen("/etc/resolv.conf", "w"); 168 FILE *fp = fopen("/etc/resolv.conf", "wxe");
169 if (!fp) { 169 if (!fp) {
170 fprintf(stderr, "Error: cannot create /etc/resolv.conf file\n"); 170 fprintf(stderr, "Error: cannot create /etc/resolv.conf file\n");
171 exit(1); 171 exit(1);
diff --git a/src/firejail/fs_lib.c b/src/firejail/fs_lib.c
index 85fb70854..5df356d04 100644
--- a/src/firejail/fs_lib.c
+++ b/src/firejail/fs_lib.c
@@ -221,7 +221,7 @@ void fslib_mount_libs(const char *full_path, unsigned user) {
221 sbox_run(mask | SBOX_SECCOMP | SBOX_CAPS_NONE, 3, PATH_FLDD, full_path, RUN_LIB_FILE); 221 sbox_run(mask | SBOX_SECCOMP | SBOX_CAPS_NONE, 3, PATH_FLDD, full_path, RUN_LIB_FILE);
222 222
223 // open the list of libraries and install them on by one 223 // open the list of libraries and install them on by one
224 FILE *fp = fopen(RUN_LIB_FILE, "r"); 224 FILE *fp = fopen(RUN_LIB_FILE, "re");
225 if (!fp) 225 if (!fp)
226 errExit("fopen"); 226 errExit("fopen");
227 227
diff --git a/src/firejail/fs_logger.c b/src/firejail/fs_logger.c
index 67ad4b52e..604e297b1 100644
--- a/src/firejail/fs_logger.c
+++ b/src/firejail/fs_logger.c
@@ -92,7 +92,7 @@ void fs_logger_print(void) {
92 if (!head) 92 if (!head)
93 return; 93 return;
94 94
95 FILE *fp = fopen(RUN_FSLOGGER_FILE, "a"); 95 FILE *fp = fopen(RUN_FSLOGGER_FILE, "ae");
96 if (!fp) { 96 if (!fp) {
97 perror("fopen"); 97 perror("fopen");
98 return; 98 return;
@@ -123,15 +123,8 @@ void fs_logger_print_log(pid_t pid) {
123 // in case the pid is that of a firejail process, use the pid of the first child process 123 // in case the pid is that of a firejail process, use the pid of the first child process
124 pid = switch_to_child(pid); 124 pid = switch_to_child(pid);
125 125
126 // check privileges for non-root users 126 // exit if no permission to join the sandbox
127 uid_t uid = getuid(); 127 check_join_permission(pid);
128 if (uid != 0) {
129 uid_t sandbox_uid = pid_get_uid(pid);
130 if (uid != sandbox_uid) {
131 fprintf(stderr, "Error: permission denied\n");
132 exit(1);
133 }
134 }
135 128
136 // print RUN_FSLOGGER_FILE 129 // print RUN_FSLOGGER_FILE
137 char *fname; 130 char *fname;
@@ -139,24 +132,16 @@ void fs_logger_print_log(pid_t pid) {
139 errExit("asprintf"); 132 errExit("asprintf");
140 133
141 EUID_ROOT(); 134 EUID_ROOT();
142 struct stat s; 135 FILE *fp = fopen(fname, "re");
143 if (stat(fname, &s) == -1 || s.st_uid != 0) { 136 free(fname);
144 fprintf(stderr, "Error: Cannot access filesystem log\n");
145 exit(1);
146 }
147
148 /* coverity[toctou] */
149 FILE *fp = fopen(fname, "r");
150 if (!fp) { 137 if (!fp) {
151 fprintf(stderr, "Error: Cannot open filesystem log\n"); 138 fprintf(stderr, "Error: Cannot open filesystem log\n");
152 exit(1); 139 exit(1);
153 } 140 }
154
155 char buf[MAXBUF]; 141 char buf[MAXBUF];
156 while (fgets(buf, MAXBUF, fp)) 142 while (fgets(buf, MAXBUF, fp))
157 printf("%s", buf); 143 printf("%s", buf);
158 fclose(fp); 144 fclose(fp);
159 free(fname);
160 145
161 exit(0); 146 exit(0);
162} 147}
diff --git a/src/firejail/fs_trace.c b/src/firejail/fs_trace.c
index 8f939b5f5..1fc38361e 100644
--- a/src/firejail/fs_trace.c
+++ b/src/firejail/fs_trace.c
@@ -33,8 +33,7 @@ void fs_trace_preload(void) {
33 if (stat("/etc/ld.so.preload", &s)) { 33 if (stat("/etc/ld.so.preload", &s)) {
34 if (arg_debug) 34 if (arg_debug)
35 printf("Creating an empty /etc/ld.so.preload file\n"); 35 printf("Creating an empty /etc/ld.so.preload file\n");
36 /* coverity[toctou] */ 36 FILE *fp = fopen("/etc/ld.so.preload", "wxe");
37 FILE *fp = fopen("/etc/ld.so.preload", "w");
38 if (!fp) 37 if (!fp)
39 errExit("fopen"); 38 errExit("fopen");
40 SET_PERMS_STREAM(fp, 0, 0, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); 39 SET_PERMS_STREAM(fp, 0, 0, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH);
@@ -64,11 +63,11 @@ void fs_tracefile(void) {
64 if (ftruncate(fd, 0) == -1) 63 if (ftruncate(fd, 0) == -1)
65 errExit("ftruncate"); 64 errExit("ftruncate");
66 EUID_ROOT(); 65 EUID_ROOT();
67 FILE *fp = fopen(RUN_TRACE_FILE, "w"); 66 FILE *fp = fopen(RUN_TRACE_FILE, "we");
68 if (!fp) 67 if (!fp)
69 errExit("fopen " RUN_TRACE_FILE); 68 errExit("fopen " RUN_TRACE_FILE);
70 fclose(fp); 69 fclose(fp);
71 fs_logger2("touch ", arg_tracefile); 70 fs_logger2("touch", arg_tracefile);
72 // mount using the symbolic link in /proc/self/fd 71 // mount using the symbolic link in /proc/self/fd
73 if (arg_debug) 72 if (arg_debug)
74 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);
@@ -88,7 +87,7 @@ void fs_trace(void) {
88 if (arg_debug) 87 if (arg_debug)
89 printf("Create the new ld.so.preload file\n"); 88 printf("Create the new ld.so.preload file\n");
90 89
91 FILE *fp = fopen(RUN_LDPRELOAD_FILE, "w"); 90 FILE *fp = fopen(RUN_LDPRELOAD_FILE, "we");
92 if (!fp) 91 if (!fp)
93 errExit("fopen"); 92 errExit("fopen");
94 const char *prefix = RUN_FIREJAIL_LIB_DIR; 93 const char *prefix = RUN_FIREJAIL_LIB_DIR;
diff --git a/src/firejail/fs_var.c b/src/firejail/fs_var.c
index f07581cd8..bae3d6df0 100644
--- a/src/firejail/fs_var.c
+++ b/src/firejail/fs_var.c
@@ -127,7 +127,7 @@ void fs_var_log(void) {
127 127
128 // create an empty /var/log/wtmp file 128 // create an empty /var/log/wtmp file
129 /* coverity[toctou] */ 129 /* coverity[toctou] */
130 FILE *fp = fopen("/var/log/wtmp", "w"); 130 FILE *fp = fopen("/var/log/wtmp", "wxe");
131 if (fp) { 131 if (fp) {
132 SET_PERMS_STREAM(fp, 0, wtmp_group, S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH); 132 SET_PERMS_STREAM(fp, 0, wtmp_group, S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH);
133 fclose(fp); 133 fclose(fp);
@@ -135,7 +135,7 @@ void fs_var_log(void) {
135 fs_logger("touch /var/log/wtmp"); 135 fs_logger("touch /var/log/wtmp");
136 136
137 // create an empty /var/log/btmp file 137 // create an empty /var/log/btmp file
138 fp = fopen("/var/log/btmp", "w"); 138 fp = fopen("/var/log/btmp", "wxe");
139 if (fp) { 139 if (fp) {
140 SET_PERMS_STREAM(fp, 0, wtmp_group, S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP); 140 SET_PERMS_STREAM(fp, 0, wtmp_group, S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP);
141 fclose(fp); 141 fclose(fp);
@@ -158,8 +158,7 @@ void fs_var_lib(void) {
158 fs_logger("tmpfs /var/lib/dhcp"); 158 fs_logger("tmpfs /var/lib/dhcp");
159 159
160 // isc dhcp server requires a /var/lib/dhcp/dhcpd.leases file 160 // isc dhcp server requires a /var/lib/dhcp/dhcpd.leases file
161 FILE *fp = fopen("/var/lib/dhcp/dhcpd.leases", "w"); 161 FILE *fp = fopen("/var/lib/dhcp/dhcpd.leases", "wxe");
162
163 if (fp) { 162 if (fp) {
164 fprintf(fp, "\n"); 163 fprintf(fp, "\n");
165 SET_PERMS_STREAM(fp, 0, 0, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 164 SET_PERMS_STREAM(fp, 0, 0, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
@@ -287,7 +286,7 @@ void fs_var_utmp(void) {
287 if (stat(UTMP_FILE, &s) == 0) 286 if (stat(UTMP_FILE, &s) == 0)
288 utmp_group = s.st_gid; 287 utmp_group = s.st_gid;
289 else { 288 else {
290 fwarning("cannot find /var/run/utmp\n"); 289 fwarning("cannot find %s\n", UTMP_FILE);
291 return; 290 return;
292 } 291 }
293 292
@@ -296,7 +295,7 @@ void fs_var_utmp(void) {
296 printf("Create the new utmp file\n"); 295 printf("Create the new utmp file\n");
297 296
298 /* coverity[toctou] */ 297 /* coverity[toctou] */
299 FILE *fp = fopen(RUN_UTMP_FILE, "w"); 298 FILE *fp = fopen(RUN_UTMP_FILE, "we");
300 if (!fp) 299 if (!fp)
301 errExit("fopen"); 300 errExit("fopen");
302 301
@@ -323,5 +322,5 @@ void fs_var_utmp(void) {
323 printf("Mount the new utmp file\n"); 322 printf("Mount the new utmp file\n");
324 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)
325 errExit("mount bind utmp"); 324 errExit("mount bind utmp");
326 fs_logger("create /var/run/utmp"); 325 fs_logger2("create", UTMP_FILE);
327} 326}
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/join.c b/src/firejail/join.c
index 1575a7469..bab4b830f 100644
--- a/src/firejail/join.c
+++ b/src/firejail/join.c
@@ -103,7 +103,7 @@ static void extract_x11_display(pid_t pid) {
103 if (asprintf(&fname, "%s/%d", RUN_FIREJAIL_X11_DIR, pid) == -1) 103 if (asprintf(&fname, "%s/%d", RUN_FIREJAIL_X11_DIR, pid) == -1)
104 errExit("asprintf"); 104 errExit("asprintf");
105 105
106 FILE *fp = fopen(fname, "r"); 106 FILE *fp = fopen(fname, "re");
107 free(fname); 107 free(fname);
108 if (!fp) 108 if (!fp)
109 return; 109 return;
@@ -219,7 +219,7 @@ static void extract_caps(pid_t pid) {
219 perror("asprintf"); 219 perror("asprintf");
220 exit(1); 220 exit(1);
221 } 221 }
222 FILE *fp = fopen(file, "r"); 222 FILE *fp = fopen(file, "re");
223 if (!fp) 223 if (!fp)
224 goto errexit; 224 goto errexit;
225 225
@@ -266,7 +266,7 @@ static void extract_user_namespace(pid_t pid) {
266 char *uidmap; 266 char *uidmap;
267 if (asprintf(&uidmap, "/proc/%u/uid_map", pid) == -1) 267 if (asprintf(&uidmap, "/proc/%u/uid_map", pid) == -1)
268 errExit("asprintf"); 268 errExit("asprintf");
269 FILE *fp = fopen(uidmap, "r"); 269 FILE *fp = fopen(uidmap, "re");
270 if (!fp) { 270 if (!fp) {
271 free(uidmap); 271 free(uidmap);
272 return; 272 return;
diff --git a/src/firejail/ls.c b/src/firejail/ls.c
index 63ef2309b..796c42290 100644
--- a/src/firejail/ls.c
+++ b/src/firejail/ls.c
@@ -221,7 +221,7 @@ void cat(const char *path) {
221 221
222 if (arg_debug) 222 if (arg_debug)
223 printf("cat %s\n", path); 223 printf("cat %s\n", path);
224 FILE *fp = fopen(path, "r"); 224 FILE *fp = fopen(path, "re");
225 if (!fp) { 225 if (!fp) {
226 fprintf(stderr, "Error: cannot read %s\n", path); 226 fprintf(stderr, "Error: cannot read %s\n", path);
227 exit(1); 227 exit(1);
diff --git a/src/firejail/macros.c b/src/firejail/macros.c
index 7f2f6dbf3..bcac1feb4 100644
--- a/src/firejail/macros.c
+++ b/src/firejail/macros.c
@@ -99,7 +99,7 @@ static char *resolve_xdg(const char *var) {
99 99
100 if (asprintf(&fname, "%s/.config/user-dirs.dirs", cfg.homedir) == -1) 100 if (asprintf(&fname, "%s/.config/user-dirs.dirs", cfg.homedir) == -1)
101 errExit("asprintf"); 101 errExit("asprintf");
102 FILE *fp = fopen(fname, "r"); 102 FILE *fp = fopen(fname, "re");
103 if (!fp) { 103 if (!fp) {
104 free(fname); 104 free(fname);
105 return NULL; 105 return NULL;
diff --git a/src/firejail/main.c b/src/firejail/main.c
index f5797a2d8..7cfa58078 100644
--- a/src/firejail/main.c
+++ b/src/firejail/main.c
@@ -116,7 +116,6 @@ int arg_private_cwd = 0; // private working directory
116int arg_scan = 0; // arp-scan all interfaces 116int arg_scan = 0; // arp-scan all interfaces
117int arg_whitelist = 0; // whitelist command 117int arg_whitelist = 0; // whitelist command
118int arg_nosound = 0; // disable sound 118int arg_nosound = 0; // disable sound
119int arg_noautopulse = 0; // disable automatic ~/.config/pulse init
120int arg_novideo = 0; //disable video devices in /dev 119int arg_novideo = 0; //disable video devices in /dev
121int arg_no3d; // disable 3d hardware acceleration 120int arg_no3d; // disable 3d hardware acceleration
122int arg_quiet = 0; // no output for scripting 121int arg_quiet = 0; // no output for scripting
@@ -125,6 +124,7 @@ int arg_join_filesystem = 0; // join only the mount namespace
125int arg_nice = 0; // nice value configured 124int arg_nice = 0; // nice value configured
126int arg_ipc = 0; // enable ipc namespace 125int arg_ipc = 0; // enable ipc namespace
127int arg_writable_etc = 0; // writable etc 126int arg_writable_etc = 0; // writable etc
127int arg_keep_config_pulse = 0; // disable automatic ~/.config/pulse init
128int arg_writable_var = 0; // writable var 128int arg_writable_var = 0; // writable var
129int arg_keep_var_tmp = 0; // don't overwrite /var/tmp 129int arg_keep_var_tmp = 0; // don't overwrite /var/tmp
130int arg_writable_run_user = 0; // writable /run/user 130int arg_writable_run_user = 0; // writable /run/user
@@ -535,7 +535,7 @@ static void run_cmd_and_exit(int i, int argc, char **argv) {
535 char *fname; 535 char *fname;
536 if (asprintf(&fname, RUN_FIREJAIL_PROFILE_DIR "/%d", pid) == -1) 536 if (asprintf(&fname, RUN_FIREJAIL_PROFILE_DIR "/%d", pid) == -1)
537 errExit("asprintf"); 537 errExit("asprintf");
538 FILE *fp = fopen(fname, "r"); 538 FILE *fp = fopen(fname, "re");
539 if (!fp) { 539 if (!fp) {
540 fprintf(stderr, "Error: sandbox %s not found\n", argv[i] + 16); 540 fprintf(stderr, "Error: sandbox %s not found\n", argv[i] + 16);
541 exit(1); 541 exit(1);
@@ -1051,7 +1051,7 @@ int main(int argc, char **argv, char **envp) {
1051 preproc_build_firejail_dir(); 1051 preproc_build_firejail_dir();
1052 const char *container_name = env_get("container"); 1052 const char *container_name = env_get("container");
1053 if (!container_name || strcmp(container_name, "firejail")) { 1053 if (!container_name || strcmp(container_name, "firejail")) {
1054 lockfd_directory = open(RUN_DIRECTORY_LOCK_FILE, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); 1054 lockfd_directory = open(RUN_DIRECTORY_LOCK_FILE, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
1055 if (lockfd_directory != -1) { 1055 if (lockfd_directory != -1) {
1056 int rv = fchown(lockfd_directory, 0, 0); 1056 int rv = fchown(lockfd_directory, 0, 0);
1057 (void) rv; 1057 (void) rv;
@@ -1153,7 +1153,7 @@ int main(int argc, char **argv, char **envp) {
1153 1153
1154#ifdef DEBUG_RESTRICTED_SHELL 1154#ifdef DEBUG_RESTRICTED_SHELL
1155 {EUID_ROOT(); 1155 {EUID_ROOT();
1156 FILE *fp = fopen("/firelog", "w"); 1156 FILE *fp = fopen("/firelog", "we");
1157 if (fp) { 1157 if (fp) {
1158 int i; 1158 int i;
1159 fprintf(fp, "argc %d: ", argc); 1159 fprintf(fp, "argc %d: ", argc);
@@ -1172,7 +1172,7 @@ int main(int argc, char **argv, char **envp) {
1172 strncmp(argv[2], "scp ", 4) == 0) { 1172 strncmp(argv[2], "scp ", 4) == 0) {
1173#ifdef DEBUG_RESTRICTED_SHELL 1173#ifdef DEBUG_RESTRICTED_SHELL
1174 {EUID_ROOT(); 1174 {EUID_ROOT();
1175 FILE *fp = fopen("/firelog", "a"); 1175 FILE *fp = fopen("/firelog", "ae");
1176 if (fp) { 1176 if (fp) {
1177 fprintf(fp, "run without a sandbox\n"); 1177 fprintf(fp, "run without a sandbox\n");
1178 fclose(fp); 1178 fclose(fp);
@@ -1205,7 +1205,7 @@ int main(int argc, char **argv, char **envp) {
1205 1205
1206#ifdef DEBUG_RESTRICTED_SHELL 1206#ifdef DEBUG_RESTRICTED_SHELL
1207 {EUID_ROOT(); 1207 {EUID_ROOT();
1208 FILE *fp = fopen("/firelog", "a"); 1208 FILE *fp = fopen("/firelog", "ae");
1209 if (fp) { 1209 if (fp) {
1210 fprintf(fp, "fullargc %d: ", fullargc); 1210 fprintf(fp, "fullargc %d: ", fullargc);
1211 int i; 1211 int i;
@@ -1227,7 +1227,7 @@ int main(int argc, char **argv, char **envp) {
1227 1227
1228#ifdef DEBUG_RESTRICTED_SHELL 1228#ifdef DEBUG_RESTRICTED_SHELL
1229 {EUID_ROOT(); 1229 {EUID_ROOT();
1230 FILE *fp = fopen("/firelog", "a"); 1230 FILE *fp = fopen("/firelog", "ae");
1231 if (fp) { 1231 if (fp) {
1232 fprintf(fp, "argc %d: ", argc); 1232 fprintf(fp, "argc %d: ", argc);
1233 int i; 1233 int i;
@@ -1832,6 +1832,8 @@ int main(int argc, char **argv, char **envp) {
1832 exit(1); 1832 exit(1);
1833 } 1833 }
1834 arg_noprofile = 1; 1834 arg_noprofile = 1;
1835 // force keep-config-pulse in order to keep ~/.config/pulse as is
1836 arg_keep_config_pulse = 1;
1835 } 1837 }
1836 else if (strncmp(argv[i], "--ignore=", 9) == 0) { 1838 else if (strncmp(argv[i], "--ignore=", 9) == 0) {
1837 if (custom_profile) { 1839 if (custom_profile) {
@@ -1882,6 +1884,9 @@ int main(int argc, char **argv, char **envp) {
1882 } 1884 }
1883 arg_writable_etc = 1; 1885 arg_writable_etc = 1;
1884 } 1886 }
1887 else if (strcmp(argv[i], "--keep-config-pulse") == 0) {
1888 arg_keep_config_pulse = 1;
1889 }
1885 else if (strcmp(argv[i], "--writable-var") == 0) { 1890 else if (strcmp(argv[i], "--writable-var") == 0) {
1886 arg_writable_var = 1; 1891 arg_writable_var = 1;
1887 } 1892 }
@@ -2084,7 +2089,7 @@ int main(int argc, char **argv, char **envp) {
2084 else if (strcmp(argv[i], "--nosound") == 0) 2089 else if (strcmp(argv[i], "--nosound") == 0)
2085 arg_nosound = 1; 2090 arg_nosound = 1;
2086 else if (strcmp(argv[i], "--noautopulse") == 0) 2091 else if (strcmp(argv[i], "--noautopulse") == 0)
2087 arg_noautopulse = 1; 2092 arg_keep_config_pulse = 1;
2088 else if (strcmp(argv[i], "--novideo") == 0) 2093 else if (strcmp(argv[i], "--novideo") == 0)
2089 arg_novideo = 1; 2094 arg_novideo = 1;
2090 else if (strcmp(argv[i], "--no3d") == 0) 2095 else if (strcmp(argv[i], "--no3d") == 0)
@@ -2858,7 +2863,7 @@ int main(int argc, char **argv, char **envp) {
2858 // check and assign an IP address - for macvlan it will be done again in the sandbox! 2863 // check and assign an IP address - for macvlan it will be done again in the sandbox!
2859 if (any_bridge_configured()) { 2864 if (any_bridge_configured()) {
2860 EUID_ROOT(); 2865 EUID_ROOT();
2861 lockfd_network = open(RUN_NETWORK_LOCK_FILE, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); 2866 lockfd_network = open(RUN_NETWORK_LOCK_FILE, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
2862 if (lockfd_network != -1) { 2867 if (lockfd_network != -1) {
2863 int rv = fchown(lockfd_network, 0, 0); 2868 int rv = fchown(lockfd_network, 0, 0);
2864 (void) rv; 2869 (void) rv;
@@ -2880,12 +2885,6 @@ int main(int argc, char **argv, char **envp) {
2880 } 2885 }
2881 EUID_ASSERT(); 2886 EUID_ASSERT();
2882 2887
2883 // create the parent-child communication pipe
2884 if (pipe(parent_to_child_fds) < 0)
2885 errExit("pipe");
2886 if (pipe(child_to_parent_fds) < 0)
2887 errExit("pipe");
2888
2889 if (arg_noroot && arg_overlay) { 2888 if (arg_noroot && arg_overlay) {
2890 fwarning("--overlay and --noroot are mutually exclusive, noroot disabled\n"); 2889 fwarning("--overlay and --noroot are mutually exclusive, noroot disabled\n");
2891 arg_noroot = 0; 2890 arg_noroot = 0;
@@ -2898,7 +2897,7 @@ int main(int argc, char **argv, char **envp) {
2898 2897
2899 // set name and x11 run files 2898 // set name and x11 run files
2900 EUID_ROOT(); 2899 EUID_ROOT();
2901 lockfd_directory = open(RUN_DIRECTORY_LOCK_FILE, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); 2900 lockfd_directory = open(RUN_DIRECTORY_LOCK_FILE, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
2902 if (lockfd_directory != -1) { 2901 if (lockfd_directory != -1) {
2903 int rv = fchown(lockfd_directory, 0, 0); 2902 int rv = fchown(lockfd_directory, 0, 0);
2904 (void) rv; 2903 (void) rv;
@@ -2927,6 +2926,12 @@ int main(int argc, char **argv, char **envp) {
2927 } 2926 }
2928#endif 2927#endif
2929 2928
2929 // create the parent-child communication pipe
2930 if (pipe2(parent_to_child_fds, O_CLOEXEC) < 0)
2931 errExit("pipe");
2932 if (pipe2(child_to_parent_fds, O_CLOEXEC) < 0)
2933 errExit("pipe");
2934
2930 // clone environment 2935 // clone environment
2931 int flags = CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | SIGCHLD; 2936 int flags = CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | SIGCHLD;
2932 2937
diff --git a/src/firejail/network.c b/src/firejail/network.c
index f7142cefd..289e164c6 100644
--- a/src/firejail/network.c
+++ b/src/firejail/network.c
@@ -217,7 +217,7 @@ int net_add_route(uint32_t ip, uint32_t mask, uint32_t gw) {
217 217
218#define BUFSIZE 1024 218#define BUFSIZE 1024
219uint32_t network_get_defaultgw(void) { 219uint32_t network_get_defaultgw(void) {
220 FILE *fp = fopen("/proc/self/net/route", "r"); 220 FILE *fp = fopen("/proc/self/net/route", "re");
221 if (!fp) 221 if (!fp)
222 errExit("fopen"); 222 errExit("fopen");
223 223
diff --git a/src/firejail/network_main.c b/src/firejail/network_main.c
index ee3c00872..d3e75bbed 100644
--- a/src/firejail/network_main.c
+++ b/src/firejail/network_main.c
@@ -292,7 +292,7 @@ void net_dns_print(pid_t pid) {
292 errExit("chdir"); 292 errExit("chdir");
293 293
294 // access /etc/resolv.conf 294 // access /etc/resolv.conf
295 FILE *fp = fopen("/etc/resolv.conf", "r"); 295 FILE *fp = fopen("/etc/resolv.conf", "re");
296 if (!fp) { 296 if (!fp) {
297 fprintf(stderr, "Error: cannot access /etc/resolv.conf\n"); 297 fprintf(stderr, "Error: cannot access /etc/resolv.conf\n");
298 exit(1); 298 exit(1);
diff --git a/src/firejail/no_sandbox.c b/src/firejail/no_sandbox.c
index 60a82821e..0e153c47b 100644
--- a/src/firejail/no_sandbox.c
+++ b/src/firejail/no_sandbox.c
@@ -20,6 +20,7 @@
20#include "firejail.h" 20#include "firejail.h"
21#include <sys/types.h> 21#include <sys/types.h>
22#include <sys/stat.h> 22#include <sys/stat.h>
23#include <errno.h>
23#include <unistd.h> 24#include <unistd.h>
24#include <grp.h> 25#include <grp.h>
25 26
@@ -47,7 +48,7 @@ int check_namespace_virt(void) {
47 48
48 // check PID 1 container environment variable 49 // check PID 1 container environment variable
49 EUID_ROOT(); 50 EUID_ROOT();
50 FILE *fp = fopen("/proc/1/environ", "r"); 51 FILE *fp = fopen("/proc/1/environ", "re");
51 if (fp) { 52 if (fp) {
52 int c = 0; 53 int c = 0;
53 while (c != EOF) { 54 while (c != EOF) {
@@ -105,20 +106,15 @@ int check_kernel_procs(void) {
105 // look at the first 10 processes 106 // look at the first 10 processes
106 // if a kernel process is found, return 1 107 // if a kernel process is found, return 1
107 for (i = 1; i <= 10; i++) { 108 for (i = 1; i <= 10; i++) {
108 struct stat s;
109 char *fname; 109 char *fname;
110 if (asprintf(&fname, "/proc/%d/comm", i) == -1) 110 if (asprintf(&fname, "/proc/%d/comm", i) == -1)
111 errExit("asprintf"); 111 errExit("asprintf");
112 if (stat(fname, &s) == -1) {
113 free(fname);
114 continue;
115 }
116 112
117 // open file 113 // open file
118 /* coverity[toctou] */ 114 FILE *fp = fopen(fname, "re");
119 FILE *fp = fopen(fname, "r");
120 if (!fp) { 115 if (!fp) {
121 fwarning("cannot open %s\n", fname); 116 if (errno != ENOENT)
117 fwarning("cannot open %s\n", fname);
122 free(fname); 118 free(fname);
123 continue; 119 continue;
124 } 120 }
diff --git a/src/firejail/preproc.c b/src/firejail/preproc.c
index 7f602545d..1aafd1ca2 100644
--- a/src/firejail/preproc.c
+++ b/src/firejail/preproc.c
@@ -164,7 +164,7 @@ void preproc_clean_run(void) {
164 int max_pids=32769; 164 int max_pids=32769;
165 int start_pid = 100; 165 int start_pid = 100;
166 // extract real max_pids 166 // extract real max_pids
167 FILE *fp = fopen("/proc/sys/kernel/pid_max", "r"); 167 FILE *fp = fopen("/proc/sys/kernel/pid_max", "re");
168 if (fp) { 168 if (fp) {
169 int val; 169 int val;
170 if (fscanf(fp, "%d", &val) == 1) { 170 if (fscanf(fp, "%d", &val) == 1) {
diff --git a/src/firejail/profile.c b/src/firejail/profile.c
index 2ea32b665..dd4506ac1 100644
--- a/src/firejail/profile.c
+++ b/src/firejail/profile.c
@@ -423,7 +423,7 @@ int profile_check_line(char *ptr, int lineno, const char *fname) {
423 return 0; 423 return 0;
424 } 424 }
425 else if (strcmp(ptr, "noautopulse") == 0) { 425 else if (strcmp(ptr, "noautopulse") == 0) {
426 arg_noautopulse = 1; 426 arg_keep_config_pulse = 1;
427 return 0; 427 return 0;
428 } 428 }
429 else if (strcmp(ptr, "notv") == 0) { 429 else if (strcmp(ptr, "notv") == 0) {
@@ -1143,6 +1143,12 @@ int profile_check_line(char *ptr, int lineno, const char *fname) {
1143 arg_machineid = 1; 1143 arg_machineid = 1;
1144 return 0; 1144 return 0;
1145 } 1145 }
1146
1147 if (strcmp(ptr, "keep-config-pulse") == 0) {
1148 arg_keep_config_pulse = 1;
1149 return 0;
1150 }
1151
1146 // writable-var 1152 // writable-var
1147 if (strcmp(ptr, "writable-var") == 0) { 1153 if (strcmp(ptr, "writable-var") == 0) {
1148 arg_writable_var = 1; 1154 arg_writable_var = 1;
@@ -1691,7 +1697,7 @@ void profile_read(const char *fname) {
1691 } 1697 }
1692 1698
1693 // open profile file: 1699 // open profile file:
1694 FILE *fp = fopen(fname, "r"); 1700 FILE *fp = fopen(fname, "re");
1695 if (fp == NULL) { 1701 if (fp == NULL) {
1696 fprintf(stderr, "Error: cannot open profile file %s\n", fname); 1702 fprintf(stderr, "Error: cannot open profile file %s\n", fname);
1697 exit(1); 1703 exit(1);
diff --git a/src/firejail/protocol.c b/src/firejail/protocol.c
index 926af7967..f21f8c96e 100644
--- a/src/firejail/protocol.c
+++ b/src/firejail/protocol.c
@@ -23,7 +23,7 @@
23 23
24void protocol_filter_save(void) { 24void protocol_filter_save(void) {
25 // save protocol filter configuration in PROTOCOL_CFG 25 // save protocol filter configuration in PROTOCOL_CFG
26 FILE *fp = fopen(RUN_PROTOCOL_CFG, "w"); 26 FILE *fp = fopen(RUN_PROTOCOL_CFG, "wxe");
27 if (!fp) 27 if (!fp)
28 errExit("fopen"); 28 errExit("fopen");
29 fprintf(fp, "%s\n", cfg.protocol); 29 fprintf(fp, "%s\n", cfg.protocol);
@@ -35,7 +35,7 @@ void protocol_filter_load(const char *fname) {
35 assert(fname); 35 assert(fname);
36 36
37 // read protocol filter configuration from PROTOCOL_CFG 37 // read protocol filter configuration from PROTOCOL_CFG
38 FILE *fp = fopen(fname, "r"); 38 FILE *fp = fopen(fname, "re");
39 if (!fp) 39 if (!fp)
40 return; 40 return;
41 41
diff --git a/src/firejail/pulseaudio.c b/src/firejail/pulseaudio.c
index 4b9203c36..1b01a71c6 100644
--- a/src/firejail/pulseaudio.c
+++ b/src/firejail/pulseaudio.c
@@ -106,7 +106,7 @@ void pulseaudio_init(void) {
106 errExit("asprintf"); 106 errExit("asprintf");
107 if (copy_file(PULSE_CLIENT_SYSCONF, pulsecfg, -1, -1, 0644)) // root needed 107 if (copy_file(PULSE_CLIENT_SYSCONF, pulsecfg, -1, -1, 0644)) // root needed
108 errExit("copy_file"); 108 errExit("copy_file");
109 FILE *fp = fopen(pulsecfg, "a"); 109 FILE *fp = fopen(pulsecfg, "ae");
110 if (!fp) 110 if (!fp)
111 errExit("fopen"); 111 errExit("fopen");
112 fprintf(fp, "%s", "\nenable-shm = no\n"); 112 fprintf(fp, "%s", "\nenable-shm = no\n");
@@ -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 a0ca4c02c..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
@@ -183,10 +183,10 @@ static void sanitize_passwd(void) {
183 183
184 // open files 184 // open files
185 /* coverity[toctou] */ 185 /* coverity[toctou] */
186 fpin = fopen("/etc/passwd", "r"); 186 fpin = fopen("/etc/passwd", "re");
187 if (!fpin) 187 if (!fpin)
188 goto errout; 188 goto errout;
189 fpout = fopen(RUN_PASSWD_FILE, "w"); 189 fpout = fopen(RUN_PASSWD_FILE, "we");
190 if (!fpout) 190 if (!fpout)
191 goto errout; 191 goto errout;
192 192
@@ -318,10 +318,10 @@ static void sanitize_group(void) {
318 318
319 // open files 319 // open files
320 /* coverity[toctou] */ 320 /* coverity[toctou] */
321 fpin = fopen("/etc/group", "r"); 321 fpin = fopen("/etc/group", "re");
322 if (!fpin) 322 if (!fpin)
323 goto errout; 323 goto errout;
324 fpout = fopen(RUN_GROUP_FILE, "w"); 324 fpout = fopen(RUN_GROUP_FILE, "we");
325 if (!fpout) 325 if (!fpout)
326 goto errout; 326 goto errout;
327 327
diff --git a/src/firejail/restricted_shell.c b/src/firejail/restricted_shell.c
index ae453f4f1..ed66903b5 100644
--- a/src/firejail/restricted_shell.c
+++ b/src/firejail/restricted_shell.c
@@ -32,7 +32,7 @@ int restricted_shell(const char *user) {
32 char *fname; 32 char *fname;
33 if (asprintf(&fname, "%s/login.users", SYSCONFDIR) == -1) 33 if (asprintf(&fname, "%s/login.users", SYSCONFDIR) == -1)
34 errExit("asprintf"); 34 errExit("asprintf");
35 FILE *fp = fopen(fname, "r"); 35 FILE *fp = fopen(fname, "re");
36 free(fname); 36 free(fname);
37 if (fp == NULL) 37 if (fp == NULL)
38 return 0; 38 return 0;
@@ -96,7 +96,7 @@ int restricted_shell(const char *user) {
96 fullargv[i] = ptr; 96 fullargv[i] = ptr;
97#ifdef DEBUG_RESTRICTED_SHELL 97#ifdef DEBUG_RESTRICTED_SHELL
98 {EUID_ROOT(); 98 {EUID_ROOT();
99 FILE *fp = fopen("/firelog", "a"); 99 FILE *fp = fopen("/firelog", "ae");
100 if (fp) { 100 if (fp) {
101 fprintf(fp, "i %d ptr #%s#\n", i, fullargv[i]); 101 fprintf(fp, "i %d ptr #%s#\n", i, fullargv[i]);
102 fclose(fp); 102 fclose(fp);
diff --git a/src/firejail/run_files.c b/src/firejail/run_files.c
index cd44f745f..c28c3e01b 100644
--- a/src/firejail/run_files.c
+++ b/src/firejail/run_files.c
@@ -101,7 +101,7 @@ void set_name_run_file(pid_t pid) {
101 errExit("asprintf"); 101 errExit("asprintf");
102 102
103 // the file is deleted first 103 // the file is deleted first
104 FILE *fp = fopen(fname, "w"); 104 FILE *fp = fopen(fname, "we");
105 if (!fp) { 105 if (!fp) {
106 fprintf(stderr, "Error: cannot create %s\n", fname); 106 fprintf(stderr, "Error: cannot create %s\n", fname);
107 exit(1); 107 exit(1);
@@ -120,7 +120,7 @@ void set_x11_run_file(pid_t pid, int display) {
120 errExit("asprintf"); 120 errExit("asprintf");
121 121
122 // the file is deleted first 122 // the file is deleted first
123 FILE *fp = fopen(fname, "w"); 123 FILE *fp = fopen(fname, "we");
124 if (!fp) { 124 if (!fp) {
125 fprintf(stderr, "Error: cannot create %s\n", fname); 125 fprintf(stderr, "Error: cannot create %s\n", fname);
126 exit(1); 126 exit(1);
@@ -139,7 +139,7 @@ void set_profile_run_file(pid_t pid, const char *fname) {
139 139
140 EUID_ROOT(); 140 EUID_ROOT();
141 // the file is deleted first 141 // the file is deleted first
142 FILE *fp = fopen(runfile, "w"); 142 FILE *fp = fopen(runfile, "we");
143 if (!fp) { 143 if (!fp) {
144 fprintf(stderr, "Error: cannot create %s\n", runfile); 144 fprintf(stderr, "Error: cannot create %s\n", runfile);
145 exit(1); 145 exit(1);
diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c
index 3af828ede..08f0f32c9 100644
--- a/src/firejail/sandbox.c
+++ b/src/firejail/sandbox.c
@@ -67,7 +67,7 @@ static void sandbox_handler(int sig){
67 if (asprintf(&monfile, "/proc/%d/cmdline", monitored_pid) == -1) 67 if (asprintf(&monfile, "/proc/%d/cmdline", monitored_pid) == -1)
68 errExit("asprintf"); 68 errExit("asprintf");
69 while (monsec) { 69 while (monsec) {
70 FILE *fp = fopen(monfile, "r"); 70 FILE *fp = fopen(monfile, "re");
71 if (!fp) 71 if (!fp)
72 break; 72 break;
73 73
@@ -162,7 +162,7 @@ static void save_nogroups(void) {
162 if (arg_nogroups == 0) 162 if (arg_nogroups == 0)
163 return; 163 return;
164 164
165 FILE *fp = fopen(RUN_GROUPS_CFG, "w"); 165 FILE *fp = fopen(RUN_GROUPS_CFG, "wxe");
166 if (fp) { 166 if (fp) {
167 fprintf(fp, "\n"); 167 fprintf(fp, "\n");
168 SET_PERMS_STREAM(fp, 0, 0, 0644); // assume mode 0644 168 SET_PERMS_STREAM(fp, 0, 0, 0644); // assume mode 0644
@@ -1015,7 +1015,7 @@ int sandbox(void* sandbox_arg) {
1015 // disable /dev/snd 1015 // disable /dev/snd
1016 fs_dev_disable_sound(); 1016 fs_dev_disable_sound();
1017 } 1017 }
1018 else if (!arg_noautopulse) 1018 else if (!arg_keep_config_pulse)
1019 pulseaudio_init(); 1019 pulseaudio_init();
1020 1020
1021 if (arg_no3d) 1021 if (arg_no3d)
diff --git a/src/firejail/sbox.c b/src/firejail/sbox.c
index f9c41f661..4a8dd1bf7 100644
--- a/src/firejail/sbox.c
+++ b/src/firejail/sbox.c
@@ -248,7 +248,9 @@ int sbox_run(unsigned filtermask, int num, ...) {
248 va_start(valist, num); 248 va_start(valist, num);
249 249
250 // build argument list 250 // build argument list
251 char **arg = malloc((num + 1) * sizeof(char *)); 251 char **arg = calloc(num + 1, sizeof(char *));
252 if (!arg)
253 errExit("calloc");
252 int i; 254 int i;
253 for (i = 0; i < num; i++) 255 for (i = 0; i < num; i++)
254 arg[i] = va_arg(valist, char *); 256 arg[i] = va_arg(valist, char *);
diff --git a/src/firejail/seccomp.c b/src/firejail/seccomp.c
index 785c29517..9670fe816 100644
--- a/src/firejail/seccomp.c
+++ b/src/firejail/seccomp.c
@@ -86,7 +86,7 @@ int seccomp_install_filters(void) {
86static void seccomp_save_file_list(const char *fname) { 86static void seccomp_save_file_list(const char *fname) {
87 assert(fname); 87 assert(fname);
88 88
89 FILE *fp = fopen(RUN_SECCOMP_LIST, "a+"); 89 FILE *fp = fopen(RUN_SECCOMP_LIST, "ae");
90 if (!fp) 90 if (!fp)
91 errExit("fopen"); 91 errExit("fopen");
92 92
@@ -99,7 +99,7 @@ static void seccomp_save_file_list(const char *fname) {
99#define MAXBUF 4096 99#define MAXBUF 4096
100static int load_file_list_flag = 0; 100static int load_file_list_flag = 0;
101void seccomp_load_file_list(void) { 101void seccomp_load_file_list(void) {
102 FILE *fp = fopen(RUN_SECCOMP_LIST, "r"); 102 FILE *fp = fopen(RUN_SECCOMP_LIST, "re");
103 if (!fp) 103 if (!fp)
104 return; // no seccomp configuration whatsoever 104 return; // no seccomp configuration whatsoever
105 105
@@ -122,7 +122,7 @@ int seccomp_load(const char *fname) {
122 assert(fname); 122 assert(fname);
123 123
124 // open filter file 124 // open filter file
125 int fd = open(fname, O_RDONLY); 125 int fd = open(fname, O_RDONLY|O_CLOEXEC);
126 if (fd == -1) 126 if (fd == -1)
127 goto errexit; 127 goto errexit;
128 128
@@ -438,7 +438,7 @@ void seccomp_print_filter(pid_t pid) {
438 if (stat(fname, &s) == -1) 438 if (stat(fname, &s) == -1)
439 goto errexit; 439 goto errexit;
440 440
441 FILE *fp = fopen(fname, "r"); 441 FILE *fp = fopen(fname, "re");
442 if (!fp) 442 if (!fp)
443 goto errexit; 443 goto errexit;
444 free(fname); 444 free(fname);
diff --git a/src/firejail/shutdown.c b/src/firejail/shutdown.c
index 8fb03d0a6..fbfe1765b 100644
--- a/src/firejail/shutdown.c
+++ b/src/firejail/shutdown.c
@@ -64,7 +64,7 @@ void shut(pid_t pid) {
64 monsec--; 64 monsec--;
65 65
66 EUID_ROOT(); 66 EUID_ROOT();
67 FILE *fp = fopen(monfile, "r"); 67 FILE *fp = fopen(monfile, "re");
68 EUID_USER(); 68 EUID_USER();
69 if (!fp) { 69 if (!fp) {
70 killdone = 1; 70 killdone = 1;
diff --git a/src/firejail/usage.c b/src/firejail/usage.c
index 397150158..888a6ffed 100644
--- a/src/firejail/usage.c
+++ b/src/firejail/usage.c
@@ -114,7 +114,8 @@ static char *usage_str =
114 " --join-network=name|pid - join the network namespace.\n" 114 " --join-network=name|pid - join the network namespace.\n"
115#endif 115#endif
116 " --join-or-start=name|pid - join the sandbox or start a new one.\n" 116 " --join-or-start=name|pid - join the sandbox or start a new one.\n"
117 " --keep-dev-shm - /dev/shm directory is untouched (even with --private-dev).\n" 117 " --keep-config-pulse - disable automatic ~/.config/pulse init.\n"
118 " --keep-dev-shm - /dev/shm directory is untouched (even with --private-dev).\n"
118 " --keep-var-tmp - /var/tmp directory is untouched.\n" 119 " --keep-var-tmp - /var/tmp directory is untouched.\n"
119 " --list - list all sandboxes.\n" 120 " --list - list all sandboxes.\n"
120#ifdef HAVE_FILE_TRANSFER 121#ifdef HAVE_FILE_TRANSFER
@@ -154,6 +155,7 @@ static char *usage_str =
154 " --nodvd - disable DVD and audio CD devices.\n" 155 " --nodvd - disable DVD and audio CD devices.\n"
155 " --noexec=filename - remount the file or directory noexec nosuid and nodev.\n" 156 " --noexec=filename - remount the file or directory noexec nosuid and nodev.\n"
156 " --nogroups - disable supplementary groups.\n" 157 " --nogroups - disable supplementary groups.\n"
158 " --noinput - disable input devices.\n"
157 " --nonewprivs - sets the NO_NEW_PRIVS prctl.\n" 159 " --nonewprivs - sets the NO_NEW_PRIVS prctl.\n"
158 " --noprofile - do not use a security profile.\n" 160 " --noprofile - do not use a security profile.\n"
159#ifdef HAVE_USERNS 161#ifdef HAVE_USERNS
diff --git a/src/firejail/util.c b/src/firejail/util.c
index 2ad85acd6..2731f61dc 100644
--- a/src/firejail/util.c
+++ b/src/firejail/util.c
@@ -298,14 +298,14 @@ int copy_file(const char *srcname, const char *destname, uid_t uid, gid_t gid, m
298 assert(destname); 298 assert(destname);
299 299
300 // open source 300 // open source
301 int src = open(srcname, O_RDONLY); 301 int src = open(srcname, O_RDONLY|O_CLOEXEC);
302 if (src < 0) { 302 if (src < 0) {
303 fwarning("cannot open source file %s, file not copied\n", srcname); 303 fwarning("cannot open source file %s, file not copied\n", srcname);
304 return -1; 304 return -1;
305 } 305 }
306 306
307 // open destination 307 // open destination
308 int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 308 int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
309 if (dst < 0) { 309 if (dst < 0) {
310 fwarning("cannot open destination file %s, file not copied\n", destname); 310 fwarning("cannot open destination file %s, file not copied\n", destname);
311 close(src); 311 close(src);
@@ -348,7 +348,7 @@ void copy_file_as_user(const char *srcname, const char *destname, uid_t uid, gid
348 348
349void copy_file_from_user_to_root(const char *srcname, const char *destname, uid_t uid, gid_t gid, mode_t mode) { 349void copy_file_from_user_to_root(const char *srcname, const char *destname, uid_t uid, gid_t gid, mode_t mode) {
350 // open destination 350 // open destination
351 int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 351 int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
352 if (dst < 0) { 352 if (dst < 0) {
353 fwarning("cannot open destination file %s, file not copied\n", destname); 353 fwarning("cannot open destination file %s, file not copied\n", destname);
354 return; 354 return;
@@ -361,7 +361,7 @@ void copy_file_from_user_to_root(const char *srcname, const char *destname, uid_
361 // drop privileges 361 // drop privileges
362 drop_privs(0); 362 drop_privs(0);
363 363
364 int src = open(srcname, O_RDONLY); 364 int src = open(srcname, O_RDONLY|O_CLOEXEC);
365 if (src < 0) { 365 if (src < 0) {
366 fwarning("cannot open source file %s, file not copied\n", srcname); 366 fwarning("cannot open source file %s, file not copied\n", srcname);
367 } else { 367 } else {
@@ -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}
@@ -616,7 +626,7 @@ int find_child(pid_t parent, pid_t *child) {
616 perror("asprintf"); 626 perror("asprintf");
617 exit(1); 627 exit(1);
618 } 628 }
619 FILE *fp = fopen(file, "r"); 629 FILE *fp = fopen(file, "re");
620 if (!fp) { 630 if (!fp) {
621 free(file); 631 free(file);
622 continue; 632 continue;
@@ -722,7 +732,7 @@ void update_map(char *mapping, char *map_file) {
722 if (mapping[j] == ',') 732 if (mapping[j] == ',')
723 mapping[j] = '\n'; 733 mapping[j] = '\n';
724 734
725 fd = open(map_file, O_RDWR); 735 fd = open(map_file, O_RDWR|O_CLOEXEC);
726 if (fd == -1) { 736 if (fd == -1) {
727 fprintf(stderr, "Error: cannot open %s: %s\n", map_file, strerror(errno)); 737 fprintf(stderr, "Error: cannot open %s: %s\n", map_file, strerror(errno));
728 exit(EXIT_FAILURE); 738 exit(EXIT_FAILURE);
@@ -742,9 +752,9 @@ void wait_for_other(int fd) {
742 // wait for the parent to be initialized 752 // wait for the parent to be initialized
743 //**************************** 753 //****************************
744 char childstr[BUFLEN + 1]; 754 char childstr[BUFLEN + 1];
745 int newfd = dup(fd); 755 int newfd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
746 if (newfd == -1) 756 if (newfd == -1)
747 errExit("dup"); 757 errExit("fcntl");
748 FILE* stream; 758 FILE* stream;
749 stream = fdopen(newfd, "r"); 759 stream = fdopen(newfd, "r");
750 *childstr = '\0'; 760 *childstr = '\0';
@@ -791,9 +801,9 @@ void wait_for_other(int fd) {
791 801
792void notify_other(int fd) { 802void notify_other(int fd) {
793 FILE* stream; 803 FILE* stream;
794 int newfd = dup(fd); 804 int newfd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
795 if (newfd == -1) 805 if (newfd == -1)
796 errExit("dup"); 806 errExit("fcntl");
797 stream = fdopen(newfd, "w"); 807 stream = fdopen(newfd, "w");
798 fprintf(stream, "arg_noroot=%d\n", arg_noroot); 808 fprintf(stream, "arg_noroot=%d\n", arg_noroot);
799 fflush(stream); 809 fflush(stream);
@@ -811,7 +821,7 @@ uid_t pid_get_uid(pid_t pid) {
811 exit(1); 821 exit(1);
812 } 822 }
813 EUID_ROOT(); // grsecurity fix 823 EUID_ROOT(); // grsecurity fix
814 FILE *fp = fopen(file, "r"); 824 FILE *fp = fopen(file, "re");
815 if (!fp) { 825 if (!fp) {
816 free(file); 826 free(file);
817 fprintf(stderr, "Error: cannot open /proc file\n"); 827 fprintf(stderr, "Error: cannot open /proc file\n");
@@ -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");
@@ -1021,8 +1031,7 @@ void create_empty_file_as_root(const char *fname, mode_t mode) {
1021 if (arg_debug) 1031 if (arg_debug)
1022 printf("Creating empty %s file\n", fname); 1032 printf("Creating empty %s file\n", fname);
1023 1033
1024 /* coverity[toctou] */ 1034 FILE *fp = fopen(fname, "wxe");
1025 FILE *fp = fopen(fname, "w");
1026 if (!fp) 1035 if (!fp)
1027 errExit("fopen"); 1036 errExit("fopen");
1028 SET_PERMS_STREAM(fp, 0, 0, mode); 1037 SET_PERMS_STREAM(fp, 0, 0, mode);
@@ -1126,13 +1135,13 @@ void disable_file_path(const char *path, const char *file) {
1126} 1135}
1127 1136
1128// open an existing file without following any symbolic link 1137// open an existing file without following any symbolic link
1129int 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]);
1130 flags |= O_NOFOLLOW; 1143 flags |= O_NOFOLLOW;
1131 assert(path); 1144
1132 if (*path != '/' || strstr(path, "..")) {
1133 fprintf(stderr, "Error: invalid path %s\n", path);
1134 exit(1);
1135 }
1136 int fd = -1; 1145 int fd = -1;
1137 1146
1138#ifdef __NR_openat2 // kernel 5.6 or better 1147#ifdef __NR_openat2 // kernel 5.6 or better
@@ -1140,7 +1149,7 @@ int safe_fd(const char *path, int flags) {
1140 memset(&oh, 0, sizeof(oh)); 1149 memset(&oh, 0, sizeof(oh));
1141 oh.flags = flags; 1150 oh.flags = flags;
1142 oh.resolve = RESOLVE_NO_SYMLINKS; 1151 oh.resolve = RESOLVE_NO_SYMLINKS;
1143 fd = syscall(__NR_openat2, -1, path, &oh, sizeof(struct open_how)); 1152 fd = syscall(__NR_openat2, dirfd, path, &oh, sizeof(struct open_how));
1144 if (fd != -1 || errno != ENOSYS) 1153 if (fd != -1 || errno != ENOSYS)
1145 return fd; 1154 return fd;
1146#endif 1155#endif
@@ -1151,18 +1160,23 @@ int safe_fd(const char *path, int flags) {
1151 if (!dup) 1160 if (!dup)
1152 errExit("strdup"); 1161 errExit("strdup");
1153 char *tok = strtok(dup, "/"); 1162 char *tok = strtok(dup, "/");
1154 if (!tok) { // root directory 1163 if (!tok) { // nothing to do, path is the root directory
1155 free(dup); 1164 free(dup);
1156 return open("/", flags); 1165 return openat(dirfd, path, flags);
1157 } 1166 }
1158 char *last_tok = EMPTY_STRING; 1167 char *last_tok = EMPTY_STRING;
1159 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);
1160 if (parentfd == -1) 1174 if (parentfd == -1)
1161 errExit("open"); 1175 errExit("open/fcntl");
1162 1176
1163 while(1) { 1177 while (1) {
1164 // 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
1165 // 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
1166 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);
1167 if (fd == -1) { 1181 if (fd == -1) {
1168 // 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
@@ -1293,13 +1307,11 @@ pid_t require_pid(const char *name) {
1293// 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
1294static int has_link(const char *dir) { 1308static int has_link(const char *dir) {
1295 assert(dir); 1309 assert(dir);
1296 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);
1297 if (fd == -1) { 1311 if (fd != -1)
1298 if ((errno == ELOOP || errno == ENOTDIR) && is_dir(dir))
1299 return 1;
1300 }
1301 else
1302 close(fd); 1312 close(fd);
1313 else if (errno == ELOOP || (errno == ENOTDIR && is_dir(dir)))
1314 return 1;
1303 return 0; 1315 return 0;
1304} 1316}
1305 1317
diff --git a/src/firejail/x11.c b/src/firejail/x11.c
index 1dabf272e..257d376a1 100644
--- a/src/firejail/x11.c
+++ b/src/firejail/x11.c
@@ -84,7 +84,7 @@ int x11_display(void) {
84static int x11_abstract_sockets_present(void) { 84static int x11_abstract_sockets_present(void) {
85 85
86 EUID_ROOT(); // grsecurity fix 86 EUID_ROOT(); // grsecurity fix
87 FILE *fp = fopen("/proc/net/unix", "r"); 87 FILE *fp = fopen("/proc/net/unix", "re");
88 if (!fp) 88 if (!fp)
89 errExit("fopen"); 89 errExit("fopen");
90 EUID_USER(); 90 EUID_USER();
@@ -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)) {
@@ -1363,7 +1363,7 @@ void fs_x11(void) {
1363 fs_logger("tmpfs /tmp/.X11-unix"); 1363 fs_logger("tmpfs /tmp/.X11-unix");
1364 1364
1365 // create an empty root-owned file which will have the desired socket bind-mounted over it 1365 // create an empty root-owned file which will have the desired socket bind-mounted over it
1366 int fd = open(x11file, O_RDONLY|O_CREAT|O_EXCL, S_IRUSR | S_IWUSR); 1366 int fd = open(x11file, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR | S_IWUSR);
1367 if (fd < 0) 1367 if (fd < 0)
1368 errExit(x11file); 1368 errExit(x11file);
1369 close(fd); 1369 close(fd);
@@ -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/firemon/firemon.c b/src/firemon/firemon.c
index 37870747d..6c34cd411 100644
--- a/src/firemon/firemon.c
+++ b/src/firemon/firemon.c
@@ -52,7 +52,7 @@ static void my_handler(int s){
52 52
53 if (terminal_set) 53 if (terminal_set)
54 tcsetattr(0, TCSANOW, &tlocal); 54 tcsetattr(0, TCSANOW, &tlocal);
55 exit(0); 55 _exit(0);
56} 56}
57 57
58// find the second child process for the specified pid 58// find the second child process for the specified pid
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
diff --git a/src/man/firejail-profile.txt b/src/man/firejail-profile.txt
index 9d11add06..49be8d0b0 100644
--- a/src/man/firejail-profile.txt
+++ b/src/man/firejail-profile.txt
@@ -1,12 +1,78 @@
1.TH FIREJAIL-PROFILE 5 "MONTH YEAR" "VERSION" "firejail profiles man page" 1.TH FIREJAIL-PROFILE 5 "MONTH YEAR" "VERSION" "firejail profiles man page"
2.SH NAME 2.SH NAME
3profile \- Security profile file syntax for Firejail 3profile \- Security profile file syntax, and information about building new application profiles.
4 4
5.SH USAGE 5.SH SYNOPSIS
6
7Using a specific profile:
8.PP
9.RS
6.TP 10.TP
7firejail \-\-profile=filename.profile 11\fBfirejail \-\-profile=filename.profile
12.br
13
14.br
15Example:
16.br
17$ firejail --profile=/etc/firejail/kdenlive.profile --appimage kdenlive.appimage
18.br
19
20.br
21.TP
22\fBfirejail \-\-profile=profile_name
23.br
24
25.br
26Example:
27.br
28$ firejail --profile=kdenlive --appimage kdenlive.appimage
29.br
30
31.br
8.RE 32.RE
9firejail \-\-profile=profile_name 33.PP
34
35
36
37Building a profile manually:
38.PP
39.RS
40Start with the template in /usr/share/doc/firejail/profile.template and modify it in a text editor.
41To integrate the program in your desktop environment copy the profile file in ~/.config/firejail
42directory and run "sudo firecfg".
43.RE
44.PP
45
46Aliases and redirections:
47.PP
48.RS
49In some cases the same profile can be used for several applications.
50One such example is LibreOffice.
51Build a regular profile for the main application, and for the rest use
52/usr/share/doc/firejail/redirect_alias-profile.template.
53.RE
54.PP
55
56Running the profile builder:
57.PP
58.RS
59.TP
60\fBfirejail \-\-build=appname.profile appname
61.br
62
63.br
64Example:
65.br
66$ firejail --build=blobby.profile blobby
67.br
68
69.br
70Run the program in "firejail \-\-build" and try to exercise as many program features as possible.
71The profile is extracted and saved in the current directory. Open it in a text editor and add or remove
72sandboxing options as necessary. Test again after modifying the profile. To integrate the program
73in your desktop environment copy the profile file in ~/.config/firejail directory and run "sudo firecfg".
74.RE
75.PP
10 76
11.SH DESCRIPTION 77.SH DESCRIPTION
12Several command line options can be passed to the program using 78Several command line options can be passed to the program using
@@ -205,6 +271,10 @@ Mount-bind file1 on top of file2. This option is only available when running as
205\fBdisable-mnt 271\fBdisable-mnt
206Disable /mnt, /media, /run/mount and /run/media access. 272Disable /mnt, /media, /run/mount and /run/media access.
207.TP 273.TP
274\fBkeep-config-pulse
275Disable automatic ~/.config/pulse init, for complex setups such as remote
276pulse servers or non-standard socket paths.
277.TP
208\fBkeep-dev-shm 278\fBkeep-dev-shm
209/dev/shm directory is untouched (even with private-dev). 279/dev/shm directory is untouched (even with private-dev).
210.TP 280.TP
@@ -652,9 +722,8 @@ name browser
652\fBno3d 722\fBno3d
653Disable 3D hardware acceleration. 723Disable 3D hardware acceleration.
654.TP 724.TP
655\fBnoautopulse 725\fBnoautopulse \fR(deprecated)
656Disable automatic ~/.config/pulse init, for complex setups such as remote 726See keep-config-pulse.
657pulse servers or non-standard socket paths.
658.TP 727.TP
659\fBnodvd 728\fBnodvd
660Disable DVD and audio CD devices. 729Disable DVD and audio CD devices.
@@ -891,7 +960,21 @@ Join the sandbox identified by name or start a new one.
891Same as "firejail --join=sandboxname" command if sandbox with specified name exists, otherwise same as "name sandboxname". 960Same as "firejail --join=sandboxname" command if sandbox with specified name exists, otherwise same as "name sandboxname".
892 961
893.SH FILES 962.SH FILES
894/etc/firejail/filename.profile, $HOME/.config/firejail/filename.profile 963.TP
964\fB/etc/firejail/appname.profile
965Global Firejail configuration consisting mainly of profiles for each application supported by default.
966
967.TP
968\fB$HOME/.config/firejail/appname.profile
969User application profiles, will take precedence over the global profiles.
970
971.TP
972\fB/usr/share/doc/firejail/profile.template
973Template for building new profiles.
974
975.TP
976\fB/usr/share/doc/firejail/redirect_alias-profile.template
977Template for aliasing/redirecting profiles.
895 978
896.SH LICENSE 979.SH LICENSE
897Firejail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 980Firejail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
diff --git a/src/man/firejail.txt b/src/man/firejail.txt
index 23ec23fb1..68aea5857 100644
--- a/src/man/firejail.txt
+++ b/src/man/firejail.txt
@@ -1052,6 +1052,17 @@ Same as "firejail --join=name" if sandbox with specified name exists, otherwise
1052Note that in contrary to other join options there is respective profile option. 1052Note that in contrary to other join options there is respective profile option.
1053 1053
1054.TP 1054.TP
1055\fB\-\-keep-config-pulse
1056Disable automatic ~/.config/pulse init, for complex setups such as remote
1057pulse servers or non-standard socket paths.
1058.br
1059
1060.br
1061Example:
1062.br
1063$ firejail \-\-keep-config-pulse firefox
1064
1065.TP
1055\fB\-\-keep-dev-shm 1066\fB\-\-keep-dev-shm
1056/dev/shm directory is untouched (even with --private-dev) 1067/dev/shm directory is untouched (even with --private-dev)
1057.br 1068.br
@@ -1460,15 +1471,8 @@ Example:
1460$ firejail --no3d firefox 1471$ firejail --no3d firefox
1461 1472
1462.TP 1473.TP
1463\fB\-\-noautopulse 1474\fB\-\-noautopulse \fR(deprecated)
1464Disable automatic ~/.config/pulse init, for complex setups such as remote 1475See --keep-config-pulse.
1465pulse servers or non-standard socket paths.
1466.br
1467
1468.br
1469Example:
1470.br
1471$ firejail \-\-noautopulse firefox
1472 1476
1473.TP 1477.TP
1474\fB\-\-noblacklist=dirname_or_filename 1478\fB\-\-noblacklist=dirname_or_filename
@@ -1892,9 +1896,7 @@ $
1892Build a new /etc in a temporary 1896Build a new /etc in a temporary
1893filesystem, and copy the files and directories in the list. 1897filesystem, and copy the files and directories in the list.
1894The files and directories in the list must be expressed as relative to 1898The files and directories in the list must be expressed as relative to
1895the /etc directory, and must not contain the / character 1899the /etc directory (e.g., /etc/foo must be expressed as foo).
1896(e.g., /etc/foo must be expressed as foo, but /etc/foo/bar --
1897expressed as foo/bar -- is disallowed).
1898If no listed file is found, /etc directory will be empty. 1900If no listed file is found, /etc directory will be empty.
1899All modifications are discarded when the sandbox is closed. 1901All modifications are discarded when the sandbox is closed.
1900.br 1902.br
@@ -1997,7 +1999,7 @@ Build a new /srv in a temporary
1997filesystem, and copy the files and directories in the list. 1999filesystem, and copy the files and directories in the list.
1998The files and directories in the list must be expressed as relative to 2000The files and directories in the list must be expressed as relative to
1999the /srv directory, and must not contain the / character 2001the /srv directory, and must not contain the / character
2000(e.g., /opt/srv must be expressed as foo, but /srv/foo/bar -- 2002(e.g., /srv/foo must be expressed as foo, but /srv/foo/bar --
2001expressed as srv/bar -- is disallowed). 2003expressed as srv/bar -- is disallowed).
2002If no listed file is found, /srv directory will be empty. 2004If no listed file is found, /srv directory will be empty.
2003All modifications are discarded when the sandbox is closed. 2005All modifications are discarded when the sandbox is closed.
diff --git a/src/profstats/main.c b/src/profstats/main.c
index 5035280b1..10e44bd65 100644
--- a/src/profstats/main.c
+++ b/src/profstats/main.c
@@ -46,6 +46,7 @@ static int cnt_whitelistusrshare = 0; // include whitelist-usr-share-common.inc
46static int cnt_ssh = 0; 46static int cnt_ssh = 0;
47static int cnt_mdwx = 0; 47static int cnt_mdwx = 0;
48static int cnt_whitelisthome = 0; 48static int cnt_whitelisthome = 0;
49static int cnt_noroot = 0;
49 50
50static int level = 0; 51static int level = 0;
51static int arg_debug = 0; 52static int arg_debug = 0;
@@ -65,6 +66,7 @@ static int arg_mdwx = 0;
65static int arg_dbus_system_none = 0; 66static int arg_dbus_system_none = 0;
66static int arg_dbus_user_none = 0; 67static int arg_dbus_user_none = 0;
67static int arg_whitelisthome = 0; 68static int arg_whitelisthome = 0;
69static int arg_noroot = 0;
68 70
69 71
70static char *profile = NULL; 72static char *profile = NULL;
@@ -80,6 +82,7 @@ static void usage(void) {
80 printf(" --dbus-user-none - profiles without \"dbus-user none\"\n"); 82 printf(" --dbus-user-none - profiles without \"dbus-user none\"\n");
81 printf(" --ssh - print profiles without \"include disable-common.inc\"\n"); 83 printf(" --ssh - print profiles without \"include disable-common.inc\"\n");
82 printf(" --noexec - print profiles without \"include disable-exec.inc\"\n"); 84 printf(" --noexec - print profiles without \"include disable-exec.inc\"\n");
85 printf(" --noroot - print profiles without \"noroot\"\n");
83 printf(" --private-bin - print profiles without private-bin\n"); 86 printf(" --private-bin - print profiles without private-bin\n");
84 printf(" --private-dev - print profiles without private-dev\n"); 87 printf(" --private-dev - print profiles without private-dev\n");
85 printf(" --private-etc - print profiles without private-etc\n"); 88 printf(" --private-etc - print profiles without private-etc\n");
@@ -128,6 +131,8 @@ void process_file(const char *fname) {
128 cnt_caps++; 131 cnt_caps++;
129 else if (strncmp(ptr, "include disable-exec.inc", 24) == 0) 132 else if (strncmp(ptr, "include disable-exec.inc", 24) == 0)
130 cnt_noexec++; 133 cnt_noexec++;
134 else if (strncmp(ptr, "noroot", 6) == 0)
135 cnt_noroot++;
131 else if (strncmp(ptr, "include whitelist-var-common.inc", 32) == 0) 136 else if (strncmp(ptr, "include whitelist-var-common.inc", 32) == 0)
132 cnt_whitelistvar++; 137 cnt_whitelistvar++;
133 else if (strncmp(ptr, "include whitelist-runuser-common.inc", 36) == 0 || 138 else if (strncmp(ptr, "include whitelist-runuser-common.inc", 36) == 0 ||
@@ -212,6 +217,8 @@ int main(int argc, char **argv) {
212 arg_mdwx = 1; 217 arg_mdwx = 1;
213 else if (strcmp(argv[i], "--noexec") == 0) 218 else if (strcmp(argv[i], "--noexec") == 0)
214 arg_noexec = 1; 219 arg_noexec = 1;
220 else if (strcmp(argv[i], "--noroot") == 0)
221 arg_noroot = 1;
215 else if (strcmp(argv[i], "--private-bin") == 0) 222 else if (strcmp(argv[i], "--private-bin") == 0)
216 arg_privatebin = 1; 223 arg_privatebin = 1;
217 else if (strcmp(argv[i], "--private-dev") == 0) 224 else if (strcmp(argv[i], "--private-dev") == 0)
@@ -256,6 +263,7 @@ int main(int argc, char **argv) {
256 int caps = cnt_caps; 263 int caps = cnt_caps;
257 int apparmor = cnt_apparmor; 264 int apparmor = cnt_apparmor;
258 int noexec = cnt_noexec; 265 int noexec = cnt_noexec;
266 int noroot = cnt_noroot;
259 int privatebin = cnt_privatebin; 267 int privatebin = cnt_privatebin;
260 int privatetmp = cnt_privatetmp; 268 int privatetmp = cnt_privatetmp;
261 int privatedev = cnt_privatedev; 269 int privatedev = cnt_privatedev;
@@ -313,6 +321,8 @@ int main(int argc, char **argv) {
313 printf("No seccomp found in %s\n", argv[i]); 321 printf("No seccomp found in %s\n", argv[i]);
314 if (arg_noexec && noexec == cnt_noexec) 322 if (arg_noexec && noexec == cnt_noexec)
315 printf("No include disable-exec.inc found in %s\n", argv[i]); 323 printf("No include disable-exec.inc found in %s\n", argv[i]);
324 if (arg_noroot && noroot == cnt_noroot)
325 printf("No noroot found in %s\n", argv[i]);
316 if (arg_privatedev && privatedev == cnt_privatedev) 326 if (arg_privatedev && privatedev == cnt_privatedev)
317 printf("No private-dev found in %s\n", argv[i]); 327 printf("No private-dev found in %s\n", argv[i]);
318 if (arg_privatebin && privatebin == cnt_privatebin) 328 if (arg_privatebin && privatebin == cnt_privatebin)
@@ -346,6 +356,7 @@ int main(int argc, char **argv) {
346 printf(" seccomp\t\t\t%d\n", cnt_seccomp); 356 printf(" seccomp\t\t\t%d\n", cnt_seccomp);
347 printf(" capabilities\t\t%d\n", cnt_caps); 357 printf(" capabilities\t\t%d\n", cnt_caps);
348 printf(" noexec\t\t\t%d (include disable-exec.inc)\n", cnt_noexec); 358 printf(" noexec\t\t\t%d (include disable-exec.inc)\n", cnt_noexec);
359 printf(" noroot\t\t\t%d\n", cnt_noroot);
349 printf(" memory-deny-write-execute\t%d\n", cnt_mdwx); 360 printf(" memory-deny-write-execute\t%d\n", cnt_mdwx);
350 printf(" apparmor\t\t\t%d\n", cnt_apparmor); 361 printf(" apparmor\t\t\t%d\n", cnt_apparmor);
351 printf(" private-bin\t\t\t%d\n", cnt_privatebin); 362 printf(" private-bin\t\t\t%d\n", cnt_privatebin);
diff --git a/src/zsh_completion/_firejail.in b/src/zsh_completion/_firejail.in
index fd27bb35f..f1a19b86d 100644
--- a/src/zsh_completion/_firejail.in
+++ b/src/zsh_completion/_firejail.in
@@ -98,6 +98,7 @@ _firejail_args=(
98 '*--ignore=-[ignore command in profile files]: :' 98 '*--ignore=-[ignore command in profile files]: :'
99 '--ipc-namespace[enable a new IPC namespace]' 99 '--ipc-namespace[enable a new IPC namespace]'
100 '--join-or-start=-[join the sandbox or start a new one name|pid]: :_all_firejails' 100 '--join-or-start=-[join the sandbox or start a new one name|pid]: :_all_firejails'
101 '--keep-config-pulse[disable automatic ~/.config/pulse init]'
101 '--keep-dev-shm[/dev/shm directory is untouched (even with --private-dev)]' 102 '--keep-dev-shm[/dev/shm directory is untouched (even with --private-dev)]'
102 '--keep-var-tmp[/var/tmp directory is untouched]' 103 '--keep-var-tmp[/var/tmp directory is untouched]'
103 '--machine-id[preserve /etc/machine-id]' 104 '--machine-id[preserve /etc/machine-id]'
@@ -116,6 +117,7 @@ _firejail_args=(
116 '--nodvd[disable DVD and audio CD devices]' 117 '--nodvd[disable DVD and audio CD devices]'
117 '*--noexec=-[remount the file or directory noexec nosuid and nodev]: :_files' 118 '*--noexec=-[remount the file or directory noexec nosuid and nodev]: :_files'
118 '--nogroups[disable supplementary groups]' 119 '--nogroups[disable supplementary groups]'
120 '--noinput[disable input devices]'
119 '--nonewprivs[sets the NO_NEW_PRIVS prctl]' 121 '--nonewprivs[sets the NO_NEW_PRIVS prctl]'
120 '--nosound[disable sound system]' 122 '--nosound[disable sound system]'
121 '--nou2f[disable U2F devices]' 123 '--nou2f[disable U2F devices]'