diff options
Diffstat (limited to 'src')
46 files changed, 694 insertions, 1149 deletions
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) { | |||
146 | void appimage_clear(void) { | 146 | void 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 = ""; | |||
35 | char *netfilter_default = NULL; | 35 | char *netfilter_default = NULL; |
36 | unsigned long join_timeout = 5000000; // microseconds | 36 | unsigned long join_timeout = 5000000; // microseconds |
37 | char *config_seccomp_error_action_str = "EPERM"; | 37 | char *config_seccomp_error_action_str = "EPERM"; |
38 | char **whitelist_reject_topdirs = NULL; | ||
38 | 39 | ||
39 | int checkcfg(int val) { | 40 | int checkcfg(int val) { |
40 | assert(val < CFG_MAX); | 41 | assert(val < CFG_MAX); |
@@ -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 | ||
418 | static void socket_overlay(char *socket_path, char *proxy_path) { | 418 | static void socket_overlay(char *socket_path, char *proxy_path) { |
419 | int fd = safe_fd(proxy_path, O_PATH | O_NOFOLLOW | O_CLOEXEC); | 419 | int fd = safer_openat(-1, proxy_path, O_PATH | O_NOFOLLOW | O_CLOEXEC); |
420 | if (fd == -1) | 420 | if (fd == -1) |
421 | errExit("opening DBus proxy socket"); | 421 | errExit("opening DBus proxy socket"); |
422 | struct stat s; | 422 | struct stat s; |
diff --git a/src/firejail/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 | ||
125 | typedef struct topdir_t { | ||
126 | char *path; | ||
127 | int fd; | ||
128 | } TopDir; | ||
129 | |||
125 | typedef struct profile_entry_t { | 130 | typedef struct profile_entry_t { |
126 | struct profile_entry_t *next; | 131 | struct profile_entry_t *next; |
127 | char *data; // command | 132 | char *data; // command |
128 | 133 | ||
129 | // whitelist command parameters | 134 | // whitelist command parameters |
130 | char *link; // link name - set if the file is a link | 135 | struct wparam_t { |
131 | enum { | 136 | char *file; // resolved file path |
132 | WLDIR_HOME = 1, // whitelist in home directory | 137 | char *link; // link path |
133 | WLDIR_TMP, // whitelist in /tmp directory | 138 | TopDir *top; // top level directory |
134 | WLDIR_MEDIA, // whitelist in /media directory | 139 | } *wparam; |
135 | WLDIR_MNT, // whitelist in /mnt directory | 140 | |
136 | WLDIR_VAR, // whitelist in /var directory | ||
137 | WLDIR_DEV, // whitelist in /dev directory | ||
138 | WLDIR_OPT, // whitelist in /opt directory | ||
139 | WLDIR_SRV, // whitelist in /srv directory | ||
140 | WLDIR_ETC, // whitelist in /etc directory | ||
141 | WLDIR_SHARE, // whitelist in /usr/share directory | ||
142 | WLDIR_MODULE, // whitelist in /sys/module directory | ||
143 | WLDIR_RUN // whitelist in /run/user/$uid directory | ||
144 | } wldir; | ||
145 | } ProfileEntry; | 141 | } ProfileEntry; |
146 | 142 | ||
147 | typedef struct config_t { | 143 | typedef struct config_t { |
@@ -314,7 +310,6 @@ extern int arg_private_cwd; // private working directory | |||
314 | extern int arg_scan; // arp-scan all interfaces | 310 | extern int arg_scan; // arp-scan all interfaces |
315 | extern int arg_whitelist; // whitelist command | 311 | extern int arg_whitelist; // whitelist command |
316 | extern int arg_nosound; // disable sound | 312 | extern int arg_nosound; // disable sound |
317 | extern int arg_noautopulse; // disable automatic ~/.config/pulse init | ||
318 | extern int arg_novideo; //disable video devices in /dev | 313 | extern int arg_novideo; //disable video devices in /dev |
319 | extern int arg_no3d; // disable 3d hardware acceleration | 314 | extern int arg_no3d; // disable 3d hardware acceleration |
320 | extern int arg_quiet; // no output for scripting | 315 | extern int arg_quiet; // no output for scripting |
@@ -323,6 +318,7 @@ extern int arg_join_filesystem; // join only the mount namespace | |||
323 | extern int arg_nice; // nice value configured | 318 | extern int arg_nice; // nice value configured |
324 | extern int arg_ipc; // enable ipc namespace | 319 | extern int arg_ipc; // enable ipc namespace |
325 | extern int arg_writable_etc; // writable etc | 320 | extern int arg_writable_etc; // writable etc |
321 | extern int arg_keep_config_pulse; // disable automatic ~/.config/pulse init | ||
326 | extern int arg_writable_var; // writable var | 322 | extern int arg_writable_var; // writable var |
327 | extern int arg_keep_var_tmp; // don't overwrite /var/tmp | 323 | extern int arg_keep_var_tmp; // don't overwrite /var/tmp |
328 | extern int arg_writable_run_user; // writable /run/user | 324 | extern 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); | |||
529 | unsigned extract_timeout(const char *str); | 525 | unsigned extract_timeout(const char *str); |
530 | void disable_file_or_dir(const char *fname); | 526 | void disable_file_or_dir(const char *fname); |
531 | void disable_file_path(const char *path, const char *file); | 527 | void disable_file_path(const char *path, const char *file); |
532 | int safe_fd(const char *path, int flags); | 528 | int safer_openat(int dirfd, const char *path, int flags); |
533 | int has_handler(pid_t pid, int signal); | 529 | int has_handler(pid_t pid, int signal); |
534 | void enter_network_namespace(pid_t pid); | 530 | void enter_network_namespace(pid_t pid); |
535 | int read_pid(const char *name, pid_t *pid); | 531 | int read_pid(const char *name, pid_t *pid); |
@@ -794,6 +790,7 @@ extern char *xvfb_extra_params; | |||
794 | extern char *netfilter_default; | 790 | extern char *netfilter_default; |
795 | extern unsigned long join_timeout; | 791 | extern unsigned long join_timeout; |
796 | extern char *config_seccomp_error_action_str; | 792 | extern char *config_seccomp_error_action_str; |
793 | extern char **whitelist_reject_topdirs; | ||
797 | 794 | ||
798 | int checkcfg(int val); | 795 | int checkcfg(int val); |
799 | void print_compiletime_support(void); | 796 | void print_compiletime_support(void); |
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index fc67a15f3..09de11de9 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -453,7 +453,7 @@ void fs_tmpfs(const char *dir, unsigned check_owner) { | |||
453 | if (arg_debug) | 453 | if (arg_debug) |
454 | printf("Mounting tmpfs on %s, check owner: %s\n", dir, (check_owner)? "yes": "no"); | 454 | printf("Mounting tmpfs on %s, check owner: %s\n", dir, (check_owner)? "yes": "no"); |
455 | // get a file descriptor for dir, fails if there is any symlink | 455 | // get a file descriptor for dir, fails if there is any symlink |
456 | int fd = safe_fd(dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | 456 | int fd = safer_openat(-1, dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); |
457 | if (fd == -1) | 457 | if (fd == -1) |
458 | errExit("while opening directory"); | 458 | errExit("while opening directory"); |
459 | struct stat s; | 459 | struct stat s; |
@@ -493,7 +493,7 @@ static void fs_remount_simple(const char *path, OPERATION op) { | |||
493 | assert(path); | 493 | assert(path); |
494 | 494 | ||
495 | // open path without following symbolic links | 495 | // open path without following symbolic links |
496 | int fd1 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 496 | int fd1 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
497 | if (fd1 == -1) | 497 | if (fd1 == -1) |
498 | goto out; | 498 | goto out; |
499 | struct stat s1; | 499 | struct stat s1; |
@@ -559,7 +559,7 @@ static void fs_remount_simple(const char *path, OPERATION op) { | |||
559 | 559 | ||
560 | // mount --bind -o remount,ro path | 560 | // mount --bind -o remount,ro path |
561 | // need to open path again without following symbolic links | 561 | // need to open path again without following symbolic links |
562 | int fd2 = safe_fd(path, O_PATH|O_NOFOLLOW|O_CLOEXEC); | 562 | int fd2 = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
563 | if (fd2 == -1) | 563 | if (fd2 == -1) |
564 | errExit("open"); | 564 | errExit("open"); |
565 | struct stat s2; | 565 | struct stat s2; |
@@ -992,9 +992,9 @@ void fs_overlayfs(void) { | |||
992 | char *firejail; | 992 | char *firejail; |
993 | if (asprintf(&firejail, "%s/.firejail", cfg.homedir) == -1) | 993 | if (asprintf(&firejail, "%s/.firejail", cfg.homedir) == -1) |
994 | errExit("asprintf"); | 994 | errExit("asprintf"); |
995 | int fd = safe_fd(firejail, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | 995 | int fd = safer_openat(-1, firejail, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); |
996 | if (fd == -1) | 996 | if (fd == -1) |
997 | errExit("safe_fd"); | 997 | errExit("safer_openat"); |
998 | free(firejail); | 998 | free(firejail); |
999 | // create basedir if it doesn't exist | 999 | // create basedir if it doesn't exist |
1000 | // the new directory will be owned by root | 1000 | // the new directory will be owned by root |
diff --git a/src/firejail/fs_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 ("") | 39 | static size_t homedir_len = 0; // cache length of homedir string |
40 | static size_t homedir_len; // cache length of homedir string | 40 | static size_t runuser_len = 0; // cache length of runuser string |
41 | static size_t runuser_len; // cache length of runuser string | 41 | static char *runuser = NULL; |
42 | static char *runuser; | ||
43 | 42 | ||
44 | 43 | ||
45 | static int mkpath(const char* path, mode_t mode) { | ||
46 | assert(path && *path); | ||
47 | mode |= 0111; | ||
48 | 44 | ||
49 | // create directories with uid/gid as root, or as current user if inside home or run/user/$uid directory | 45 | static void whitelist_error(const char *path) { |
50 | int userprivs = 0; | 46 | assert(path); |
51 | if ((strncmp(path, cfg.homedir, homedir_len) == 0 && path[homedir_len] == '/') || | 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 | ||
52 | static int whitelist_mkpath(const char* path, mode_t mode) { | ||
57 | // work on a copy of the path | 53 | // work on a copy of the path |
58 | char *dup = strdup(path); | 54 | char *dup = strdup(path); |
59 | if (!dup) | 55 | if (!dup) |
60 | errExit("strdup"); | 56 | errExit("strdup"); |
61 | 57 | ||
62 | // don't create the last path element | 58 | // only create leading directories, don't create the file |
63 | char *p = strrchr(dup, '/'); | 59 | char *p = strrchr(dup, '/'); |
64 | assert(p); | 60 | assert(p); |
65 | *p = '\0'; | 61 | *p = '\0'; |
@@ -69,10 +65,10 @@ static int mkpath(const char* path, mode_t mode) { | |||
69 | errExit("open"); | 65 | errExit("open"); |
70 | 66 | ||
71 | // traverse the path, return -1 if a symlink is encountered | 67 | // traverse the path, return -1 if a symlink is encountered |
72 | int done = 0; | ||
73 | int fd = -1; | 68 | int fd = -1; |
69 | int done = 0; | ||
74 | char *tok = strtok(dup, "/"); | 70 | char *tok = strtok(dup, "/"); |
75 | assert(tok); // path is no top level directory | 71 | assert(tok); |
76 | while (tok) { | 72 | while (tok) { |
77 | // create the directory if necessary | 73 | // create the directory if necessary |
78 | if (mkdirat(parentfd, tok, mode) == -1) { | 74 | if (mkdirat(parentfd, tok, mode) == -1) { |
@@ -81,9 +77,6 @@ static int mkpath(const char* path, mode_t mode) { | |||
81 | perror("mkdir"); | 77 | perror("mkdir"); |
82 | close(parentfd); | 78 | close(parentfd); |
83 | free(dup); | 79 | free(dup); |
84 | if (userprivs) { | ||
85 | EUID_ROOT(); | ||
86 | } | ||
87 | return -1; | 80 | return -1; |
88 | } | 81 | } |
89 | } | 82 | } |
@@ -96,9 +89,6 @@ static int mkpath(const char* path, mode_t mode) { | |||
96 | perror("open"); | 89 | perror("open"); |
97 | close(parentfd); | 90 | close(parentfd); |
98 | free(dup); | 91 | free(dup); |
99 | if (userprivs) { | ||
100 | EUID_ROOT(); | ||
101 | } | ||
102 | return -1; | 92 | return -1; |
103 | } | 93 | } |
104 | // move on to next path segment | 94 | // move on to next path segment |
@@ -111,195 +101,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 | ||
120 | static void whitelist_path(ProfileEntry *entry) { | 107 | static 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 | ||
334 | static void whitelist_home(int topdir) { | 232 | static 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 | ||
349 | static void globbing(const char *pattern) { | 272 | static 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 | ||
310 | static 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 | |||
400 | static 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 | ||
414 | static 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 | |||
471 | static 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 | |||
485 | static 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 | ||
382 | void fs_whitelist(void) { | 523 | void 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 | ||
1216 | errexit: | 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 a0ee1e433..593835843 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c | |||
@@ -116,7 +116,6 @@ int arg_private_cwd = 0; // private working directory | |||
116 | int arg_scan = 0; // arp-scan all interfaces | 116 | int arg_scan = 0; // arp-scan all interfaces |
117 | int arg_whitelist = 0; // whitelist command | 117 | int arg_whitelist = 0; // whitelist command |
118 | int arg_nosound = 0; // disable sound | 118 | int arg_nosound = 0; // disable sound |
119 | int arg_noautopulse = 0; // disable automatic ~/.config/pulse init | ||
120 | int arg_novideo = 0; //disable video devices in /dev | 119 | int arg_novideo = 0; //disable video devices in /dev |
121 | int arg_no3d; // disable 3d hardware acceleration | 120 | int arg_no3d; // disable 3d hardware acceleration |
122 | int arg_quiet = 0; // no output for scripting | 121 | int arg_quiet = 0; // no output for scripting |
@@ -125,6 +124,7 @@ int arg_join_filesystem = 0; // join only the mount namespace | |||
125 | int arg_nice = 0; // nice value configured | 124 | int arg_nice = 0; // nice value configured |
126 | int arg_ipc = 0; // enable ipc namespace | 125 | int arg_ipc = 0; // enable ipc namespace |
127 | int arg_writable_etc = 0; // writable etc | 126 | int arg_writable_etc = 0; // writable etc |
127 | int arg_keep_config_pulse = 0; // disable automatic ~/.config/pulse init | ||
128 | int arg_writable_var = 0; // writable var | 128 | int arg_writable_var = 0; // writable var |
129 | int arg_keep_var_tmp = 0; // don't overwrite /var/tmp | 129 | int arg_keep_var_tmp = 0; // don't overwrite /var/tmp |
130 | int arg_writable_run_user = 0; // writable /run/user | 130 | int 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); |
@@ -1043,7 +1043,7 @@ int main(int argc, char **argv, char **envp) { | |||
1043 | preproc_build_firejail_dir(); | 1043 | preproc_build_firejail_dir(); |
1044 | const char *container_name = env_get("container"); | 1044 | const char *container_name = env_get("container"); |
1045 | if (!container_name || strcmp(container_name, "firejail")) { | 1045 | if (!container_name || strcmp(container_name, "firejail")) { |
1046 | lockfd_directory = open(RUN_DIRECTORY_LOCK_FILE, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); | 1046 | lockfd_directory = open(RUN_DIRECTORY_LOCK_FILE, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); |
1047 | if (lockfd_directory != -1) { | 1047 | if (lockfd_directory != -1) { |
1048 | int rv = fchown(lockfd_directory, 0, 0); | 1048 | int rv = fchown(lockfd_directory, 0, 0); |
1049 | (void) rv; | 1049 | (void) rv; |
@@ -1145,7 +1145,7 @@ int main(int argc, char **argv, char **envp) { | |||
1145 | 1145 | ||
1146 | #ifdef DEBUG_RESTRICTED_SHELL | 1146 | #ifdef DEBUG_RESTRICTED_SHELL |
1147 | {EUID_ROOT(); | 1147 | {EUID_ROOT(); |
1148 | FILE *fp = fopen("/firelog", "w"); | 1148 | FILE *fp = fopen("/firelog", "we"); |
1149 | if (fp) { | 1149 | if (fp) { |
1150 | int i; | 1150 | int i; |
1151 | fprintf(fp, "argc %d: ", argc); | 1151 | fprintf(fp, "argc %d: ", argc); |
@@ -1164,7 +1164,7 @@ int main(int argc, char **argv, char **envp) { | |||
1164 | strncmp(argv[2], "scp ", 4) == 0) { | 1164 | strncmp(argv[2], "scp ", 4) == 0) { |
1165 | #ifdef DEBUG_RESTRICTED_SHELL | 1165 | #ifdef DEBUG_RESTRICTED_SHELL |
1166 | {EUID_ROOT(); | 1166 | {EUID_ROOT(); |
1167 | FILE *fp = fopen("/firelog", "a"); | 1167 | FILE *fp = fopen("/firelog", "ae"); |
1168 | if (fp) { | 1168 | if (fp) { |
1169 | fprintf(fp, "run without a sandbox\n"); | 1169 | fprintf(fp, "run without a sandbox\n"); |
1170 | fclose(fp); | 1170 | fclose(fp); |
@@ -1197,7 +1197,7 @@ int main(int argc, char **argv, char **envp) { | |||
1197 | 1197 | ||
1198 | #ifdef DEBUG_RESTRICTED_SHELL | 1198 | #ifdef DEBUG_RESTRICTED_SHELL |
1199 | {EUID_ROOT(); | 1199 | {EUID_ROOT(); |
1200 | FILE *fp = fopen("/firelog", "a"); | 1200 | FILE *fp = fopen("/firelog", "ae"); |
1201 | if (fp) { | 1201 | if (fp) { |
1202 | fprintf(fp, "fullargc %d: ", fullargc); | 1202 | fprintf(fp, "fullargc %d: ", fullargc); |
1203 | int i; | 1203 | int i; |
@@ -1219,7 +1219,7 @@ int main(int argc, char **argv, char **envp) { | |||
1219 | 1219 | ||
1220 | #ifdef DEBUG_RESTRICTED_SHELL | 1220 | #ifdef DEBUG_RESTRICTED_SHELL |
1221 | {EUID_ROOT(); | 1221 | {EUID_ROOT(); |
1222 | FILE *fp = fopen("/firelog", "a"); | 1222 | FILE *fp = fopen("/firelog", "ae"); |
1223 | if (fp) { | 1223 | if (fp) { |
1224 | fprintf(fp, "argc %d: ", argc); | 1224 | fprintf(fp, "argc %d: ", argc); |
1225 | int i; | 1225 | int i; |
@@ -1824,8 +1824,8 @@ int main(int argc, char **argv, char **envp) { | |||
1824 | exit(1); | 1824 | exit(1); |
1825 | } | 1825 | } |
1826 | arg_noprofile = 1; | 1826 | arg_noprofile = 1; |
1827 | // force noautopulse in order to keep ~/.config/pulse as is | 1827 | // force keep-config-pulse in order to keep ~/.config/pulse as is |
1828 | arg_noautopulse = 1; | 1828 | arg_keep_config_pulse = 1; |
1829 | } | 1829 | } |
1830 | else if (strncmp(argv[i], "--ignore=", 9) == 0) { | 1830 | else if (strncmp(argv[i], "--ignore=", 9) == 0) { |
1831 | if (custom_profile) { | 1831 | if (custom_profile) { |
@@ -1876,6 +1876,9 @@ int main(int argc, char **argv, char **envp) { | |||
1876 | } | 1876 | } |
1877 | arg_writable_etc = 1; | 1877 | arg_writable_etc = 1; |
1878 | } | 1878 | } |
1879 | else if (strcmp(argv[i], "--keep-config-pulse") == 0) { | ||
1880 | arg_keep_config_pulse = 1; | ||
1881 | } | ||
1879 | else if (strcmp(argv[i], "--writable-var") == 0) { | 1882 | else if (strcmp(argv[i], "--writable-var") == 0) { |
1880 | arg_writable_var = 1; | 1883 | arg_writable_var = 1; |
1881 | } | 1884 | } |
@@ -2078,7 +2081,7 @@ int main(int argc, char **argv, char **envp) { | |||
2078 | else if (strcmp(argv[i], "--nosound") == 0) | 2081 | else if (strcmp(argv[i], "--nosound") == 0) |
2079 | arg_nosound = 1; | 2082 | arg_nosound = 1; |
2080 | else if (strcmp(argv[i], "--noautopulse") == 0) | 2083 | else if (strcmp(argv[i], "--noautopulse") == 0) |
2081 | arg_noautopulse = 1; | 2084 | arg_keep_config_pulse = 1; |
2082 | else if (strcmp(argv[i], "--novideo") == 0) | 2085 | else if (strcmp(argv[i], "--novideo") == 0) |
2083 | arg_novideo = 1; | 2086 | arg_novideo = 1; |
2084 | else if (strcmp(argv[i], "--no3d") == 0) | 2087 | else if (strcmp(argv[i], "--no3d") == 0) |
@@ -2852,7 +2855,7 @@ int main(int argc, char **argv, char **envp) { | |||
2852 | // check and assign an IP address - for macvlan it will be done again in the sandbox! | 2855 | // check and assign an IP address - for macvlan it will be done again in the sandbox! |
2853 | if (any_bridge_configured()) { | 2856 | if (any_bridge_configured()) { |
2854 | EUID_ROOT(); | 2857 | EUID_ROOT(); |
2855 | lockfd_network = open(RUN_NETWORK_LOCK_FILE, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); | 2858 | lockfd_network = open(RUN_NETWORK_LOCK_FILE, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); |
2856 | if (lockfd_network != -1) { | 2859 | if (lockfd_network != -1) { |
2857 | int rv = fchown(lockfd_network, 0, 0); | 2860 | int rv = fchown(lockfd_network, 0, 0); |
2858 | (void) rv; | 2861 | (void) rv; |
@@ -2874,12 +2877,6 @@ int main(int argc, char **argv, char **envp) { | |||
2874 | } | 2877 | } |
2875 | EUID_ASSERT(); | 2878 | EUID_ASSERT(); |
2876 | 2879 | ||
2877 | // create the parent-child communication pipe | ||
2878 | if (pipe(parent_to_child_fds) < 0) | ||
2879 | errExit("pipe"); | ||
2880 | if (pipe(child_to_parent_fds) < 0) | ||
2881 | errExit("pipe"); | ||
2882 | |||
2883 | if (arg_noroot && arg_overlay) { | 2880 | if (arg_noroot && arg_overlay) { |
2884 | fwarning("--overlay and --noroot are mutually exclusive, noroot disabled\n"); | 2881 | fwarning("--overlay and --noroot are mutually exclusive, noroot disabled\n"); |
2885 | arg_noroot = 0; | 2882 | arg_noroot = 0; |
@@ -2892,7 +2889,7 @@ int main(int argc, char **argv, char **envp) { | |||
2892 | 2889 | ||
2893 | // set name and x11 run files | 2890 | // set name and x11 run files |
2894 | EUID_ROOT(); | 2891 | EUID_ROOT(); |
2895 | lockfd_directory = open(RUN_DIRECTORY_LOCK_FILE, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); | 2892 | lockfd_directory = open(RUN_DIRECTORY_LOCK_FILE, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); |
2896 | if (lockfd_directory != -1) { | 2893 | if (lockfd_directory != -1) { |
2897 | int rv = fchown(lockfd_directory, 0, 0); | 2894 | int rv = fchown(lockfd_directory, 0, 0); |
2898 | (void) rv; | 2895 | (void) rv; |
@@ -2921,6 +2918,12 @@ int main(int argc, char **argv, char **envp) { | |||
2921 | } | 2918 | } |
2922 | #endif | 2919 | #endif |
2923 | 2920 | ||
2921 | // create the parent-child communication pipe | ||
2922 | if (pipe2(parent_to_child_fds, O_CLOEXEC) < 0) | ||
2923 | errExit("pipe"); | ||
2924 | if (pipe2(child_to_parent_fds, O_CLOEXEC) < 0) | ||
2925 | errExit("pipe"); | ||
2926 | |||
2924 | // clone environment | 2927 | // clone environment |
2925 | int flags = CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | SIGCHLD; | 2928 | int flags = CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | SIGCHLD; |
2926 | 2929 | ||
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 |
219 | uint32_t network_get_defaultgw(void) { | 219 | uint32_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 | ||
24 | void protocol_filter_save(void) { | 24 | void 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/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) { | |||
86 | static void seccomp_save_file_list(const char *fname) { | 86 | static 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 |
100 | static int load_file_list_flag = 0; | 100 | static int load_file_list_flag = 0; |
101 | void seccomp_load_file_list(void) { | 101 | void 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 baa015a6c..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 |
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 | ||
349 | void copy_file_from_user_to_root(const char *srcname, const char *destname, uid_t uid, gid_t gid, mode_t mode) { | 349 | void 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 | ||
550 | char *clean_pathname(const char *path) { | 551 | char *clean_pathname(const char *path) { |
551 | assert(path); | 552 | assert(path && path[0] == '/'); |
553 | |||
552 | size_t len = strlen(path); | 554 | size_t len = strlen(path); |
553 | char *rv = malloc(len + 1); | 555 | char *rv = malloc(len + 1); |
554 | if (!rv) | 556 | if (!rv) |
@@ -557,15 +559,23 @@ char *clean_pathname(const char *path) { | |||
557 | size_t i = 0; | 559 | size_t i = 0; |
558 | size_t j = 0; | 560 | size_t j = 0; |
559 | while (path[i]) { | 561 | while (path[i]) { |
560 | while (path[i] == '/' && path[i+1] == '/') | 562 | if (path[i] == '/') { |
561 | i++; | 563 | while (path[i+1] == '/' || |
564 | (path[i+1] == '.' && path[i+2] == '/')) | ||
565 | i++; | ||
566 | } | ||
567 | |||
562 | rv[j++] = path[i++]; | 568 | rv[j++] = path[i++]; |
563 | } | 569 | } |
564 | rv[j] = '\0'; | 570 | rv[j] = '\0'; |
565 | 571 | ||
572 | // remove a trailing dot | ||
573 | if (j > 1 && rv[j - 1] == '.' && rv[j - 2] == '/') | ||
574 | rv[--j] = '\0'; | ||
575 | |||
566 | // remove a trailing slash | 576 | // remove a trailing slash |
567 | if (j > 1 && rv[j - 1] == '/') | 577 | if (j > 1 && rv[j - 1] == '/') |
568 | rv[j - 1] = '\0'; | 578 | rv[--j] = '\0'; |
569 | 579 | ||
570 | return rv; | 580 | return rv; |
571 | } | 581 | } |
@@ -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 | ||
792 | void notify_other(int fd) { | 802 | void 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 |
1129 | int 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 | ||
1141 | int 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 |
1294 | static int has_link(const char *dir) { | 1308 | static 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) { | |||
84 | static int x11_abstract_sockets_present(void) { | 84 | static 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 9d59328f5..49be8d0b0 100644 --- a/src/man/firejail-profile.txt +++ b/src/man/firejail-profile.txt | |||
@@ -271,6 +271,10 @@ Mount-bind file1 on top of file2. This option is only available when running as | |||
271 | \fBdisable-mnt | 271 | \fBdisable-mnt |
272 | Disable /mnt, /media, /run/mount and /run/media access. | 272 | Disable /mnt, /media, /run/mount and /run/media access. |
273 | .TP | 273 | .TP |
274 | \fBkeep-config-pulse | ||
275 | Disable automatic ~/.config/pulse init, for complex setups such as remote | ||
276 | pulse servers or non-standard socket paths. | ||
277 | .TP | ||
274 | \fBkeep-dev-shm | 278 | \fBkeep-dev-shm |
275 | /dev/shm directory is untouched (even with private-dev). | 279 | /dev/shm directory is untouched (even with private-dev). |
276 | .TP | 280 | .TP |
@@ -718,9 +722,8 @@ name browser | |||
718 | \fBno3d | 722 | \fBno3d |
719 | Disable 3D hardware acceleration. | 723 | Disable 3D hardware acceleration. |
720 | .TP | 724 | .TP |
721 | \fBnoautopulse | 725 | \fBnoautopulse \fR(deprecated) |
722 | Disable automatic ~/.config/pulse init, for complex setups such as remote | 726 | See keep-config-pulse. |
723 | pulse servers or non-standard socket paths. | ||
724 | .TP | 727 | .TP |
725 | \fBnodvd | 728 | \fBnodvd |
726 | Disable DVD and audio CD devices. | 729 | Disable DVD and audio CD devices. |
diff --git a/src/man/firejail.txt b/src/man/firejail.txt index 397ce5e17..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 | |||
1052 | Note that in contrary to other join options there is respective profile option. | 1052 | Note that in contrary to other join options there is respective profile option. |
1053 | 1053 | ||
1054 | .TP | 1054 | .TP |
1055 | \fB\-\-keep-config-pulse | ||
1056 | Disable automatic ~/.config/pulse init, for complex setups such as remote | ||
1057 | pulse servers or non-standard socket paths. | ||
1058 | .br | ||
1059 | |||
1060 | .br | ||
1061 | Example: | ||
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) |
1464 | Disable automatic ~/.config/pulse init, for complex setups such as remote | 1475 | See --keep-config-pulse. |
1465 | pulse servers or non-standard socket paths. | ||
1466 | .br | ||
1467 | |||
1468 | .br | ||
1469 | Example: | ||
1470 | .br | ||
1471 | $ firejail \-\-noautopulse firefox | ||
1472 | 1476 | ||
1473 | .TP | 1477 | .TP |
1474 | \fB\-\-noblacklist=dirname_or_filename | 1478 | \fB\-\-noblacklist=dirname_or_filename |
diff --git a/src/zsh_completion/_firejail.in b/src/zsh_completion/_firejail.in index a9a32e9d4..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]' |