diff options
Diffstat (limited to 'src/firejail/fs.c')
-rw-r--r-- | src/firejail/fs.c | 857 |
1 files changed, 410 insertions, 447 deletions
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index 7ee76d096..7ff7e3c59 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -27,198 +27,9 @@ | |||
27 | #include <fcntl.h> | 27 | #include <fcntl.h> |
28 | #include <errno.h> | 28 | #include <errno.h> |
29 | 29 | ||
30 | static void create_empty_dir(void) { | 30 | static void fs_rdwr(const char *dir); |
31 | struct stat s; | ||
32 | |||
33 | if (stat(RUN_RO_DIR, &s)) { | ||
34 | /* coverity[toctou] */ | ||
35 | int rv = mkdir(RUN_RO_DIR, S_IRUSR | S_IXUSR); | ||
36 | if (rv == -1) | ||
37 | errExit("mkdir"); | ||
38 | if (chown(RUN_RO_DIR, 0, 0) < 0) | ||
39 | errExit("chown"); | ||
40 | } | ||
41 | } | ||
42 | |||
43 | static void create_empty_file(void) { | ||
44 | struct stat s; | ||
45 | |||
46 | if (stat(RUN_RO_FILE, &s)) { | ||
47 | /* coverity[toctou] */ | ||
48 | FILE *fp = fopen(RUN_RO_FILE, "w"); | ||
49 | if (!fp) | ||
50 | errExit("fopen"); | ||
51 | fclose(fp); | ||
52 | if (chown(RUN_RO_FILE, 0, 0) < 0) | ||
53 | errExit("chown"); | ||
54 | if (chmod(RUN_RO_FILE, S_IRUSR) < 0) | ||
55 | errExit("chown"); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | // build /run/firejail directory | ||
60 | void fs_build_firejail_dir(void) { | ||
61 | struct stat s; | ||
62 | |||
63 | // CentOS 6 doesn't have /run directory | ||
64 | if (stat(RUN_FIREJAIL_BASEDIR, &s)) { | ||
65 | if (arg_debug) | ||
66 | printf("Creating %s directory\n", RUN_FIREJAIL_BASEDIR); | ||
67 | /* coverity[toctou] */ | ||
68 | int rv = mkdir(RUN_FIREJAIL_BASEDIR, 0755); | ||
69 | if (rv == -1) | ||
70 | errExit("mkdir"); | ||
71 | if (chown(RUN_FIREJAIL_BASEDIR, 0, 0) < 0) | ||
72 | errExit("chown"); | ||
73 | if (chmod(RUN_FIREJAIL_BASEDIR, 0755) < 0) | ||
74 | errExit("chmod"); | ||
75 | } | ||
76 | else { // check /tmp/firejail directory belongs to root end exit if doesn't! | ||
77 | if (s.st_uid != 0 || s.st_gid != 0) { | ||
78 | fprintf(stderr, "Error: non-root %s directory, exiting...\n", RUN_FIREJAIL_DIR); | ||
79 | exit(1); | ||
80 | } | ||
81 | } | ||
82 | |||
83 | if (stat(RUN_FIREJAIL_DIR, &s)) { | ||
84 | if (arg_debug) | ||
85 | printf("Creating %s directory\n", RUN_FIREJAIL_DIR); | ||
86 | /* coverity[toctou] */ | ||
87 | int rv = mkdir(RUN_FIREJAIL_DIR, 0755); | ||
88 | if (rv == -1) | ||
89 | errExit("mkdir"); | ||
90 | if (chown(RUN_FIREJAIL_DIR, 0, 0) < 0) | ||
91 | errExit("chown"); | ||
92 | if (chmod(RUN_FIREJAIL_DIR, 0755) < 0) | ||
93 | errExit("chmod"); | ||
94 | } | ||
95 | |||
96 | if (stat(RUN_FIREJAIL_NETWORK_DIR, &s)) { | ||
97 | if (arg_debug) | ||
98 | printf("Creating %s directory\n", RUN_FIREJAIL_NETWORK_DIR); | ||
99 | |||
100 | if (mkdir(RUN_FIREJAIL_NETWORK_DIR, 0755) == -1) | ||
101 | errExit("mkdir"); | ||
102 | if (chown(RUN_FIREJAIL_NETWORK_DIR, 0, 0) < 0) | ||
103 | errExit("chown"); | ||
104 | if (chmod(RUN_FIREJAIL_NETWORK_DIR, 0755) < 0) | ||
105 | errExit("chmod"); | ||
106 | } | ||
107 | |||
108 | if (stat(RUN_FIREJAIL_BANDWIDTH_DIR, &s)) { | ||
109 | if (arg_debug) | ||
110 | printf("Creating %s directory\n", RUN_FIREJAIL_BANDWIDTH_DIR); | ||
111 | if (mkdir(RUN_FIREJAIL_BANDWIDTH_DIR, 0755) == -1) | ||
112 | errExit("mkdir"); | ||
113 | if (chown(RUN_FIREJAIL_BANDWIDTH_DIR, 0, 0) < 0) | ||
114 | errExit("chown"); | ||
115 | if (chmod(RUN_FIREJAIL_BANDWIDTH_DIR, 0755) < 0) | ||
116 | errExit("chmod"); | ||
117 | } | ||
118 | |||
119 | if (stat(RUN_FIREJAIL_NAME_DIR, &s)) { | ||
120 | if (arg_debug) | ||
121 | printf("Creating %s directory\n", RUN_FIREJAIL_NAME_DIR); | ||
122 | if (mkdir(RUN_FIREJAIL_NAME_DIR, 0755) == -1) | ||
123 | errExit("mkdir"); | ||
124 | if (chown(RUN_FIREJAIL_NAME_DIR, 0, 0) < 0) | ||
125 | errExit("chown"); | ||
126 | if (chmod(RUN_FIREJAIL_NAME_DIR, 0755) < 0) | ||
127 | errExit("chmod"); | ||
128 | } | ||
129 | |||
130 | if (stat(RUN_FIREJAIL_X11_DIR, &s)) { | ||
131 | if (arg_debug) | ||
132 | printf("Creating %s directory\n", RUN_FIREJAIL_X11_DIR); | ||
133 | if (mkdir(RUN_FIREJAIL_X11_DIR, 0755) == -1) | ||
134 | errExit("mkdir"); | ||
135 | if (chown(RUN_FIREJAIL_X11_DIR, 0, 0) < 0) | ||
136 | errExit("chown"); | ||
137 | if (chmod(RUN_FIREJAIL_X11_DIR, 0755) < 0) | ||
138 | errExit("chmod"); | ||
139 | } | ||
140 | |||
141 | create_empty_dir(); | ||
142 | create_empty_file(); | ||
143 | } | ||
144 | 31 | ||
145 | 32 | ||
146 | // build /tmp/firejail/mnt directory | ||
147 | static int tmpfs_mounted = 0; | ||
148 | #ifdef HAVE_CHROOT | ||
149 | static void fs_build_remount_mnt_dir(void) { | ||
150 | tmpfs_mounted = 0; | ||
151 | fs_build_mnt_dir(); | ||
152 | } | ||
153 | #endif | ||
154 | |||
155 | void fs_build_mnt_dir(void) { | ||
156 | struct stat s; | ||
157 | fs_build_firejail_dir(); | ||
158 | |||
159 | // create /run/firejail/mnt directory | ||
160 | if (stat(RUN_MNT_DIR, &s)) { | ||
161 | if (arg_debug) | ||
162 | printf("Creating %s directory\n", RUN_MNT_DIR); | ||
163 | /* coverity[toctou] */ | ||
164 | int rv = mkdir(RUN_MNT_DIR, 0755); | ||
165 | if (rv == -1) | ||
166 | errExit("mkdir"); | ||
167 | if (chown(RUN_MNT_DIR, 0, 0) < 0) | ||
168 | errExit("chown"); | ||
169 | if (chmod(RUN_MNT_DIR, 0755) < 0) | ||
170 | errExit("chmod"); | ||
171 | } | ||
172 | |||
173 | // ... and mount tmpfs on top of it | ||
174 | if (!tmpfs_mounted) { | ||
175 | // mount tmpfs on top of /run/firejail/mnt | ||
176 | if (arg_debug) | ||
177 | printf("Mounting tmpfs on %s directory\n", RUN_MNT_DIR); | ||
178 | if (mount("tmpfs", RUN_MNT_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | ||
179 | errExit("mounting /tmp/firejail/mnt"); | ||
180 | tmpfs_mounted = 1; | ||
181 | fs_logger2("tmpfs", RUN_MNT_DIR); | ||
182 | } | ||
183 | } | ||
184 | |||
185 | // grab a copy of cp command | ||
186 | void fs_build_cp_command(void) { | ||
187 | struct stat s; | ||
188 | fs_build_mnt_dir(); | ||
189 | if (stat(RUN_CP_COMMAND, &s)) { | ||
190 | char* fname = realpath("/bin/cp", NULL); | ||
191 | if (fname == NULL) { | ||
192 | fprintf(stderr, "Error: /bin/cp not found\n"); | ||
193 | exit(1); | ||
194 | } | ||
195 | if (stat(fname, &s)) { | ||
196 | fprintf(stderr, "Error: /bin/cp not found\n"); | ||
197 | exit(1); | ||
198 | } | ||
199 | if (is_link(fname)) { | ||
200 | fprintf(stderr, "Error: invalid /bin/cp file\n"); | ||
201 | exit(1); | ||
202 | } | ||
203 | int rv = copy_file(fname, RUN_CP_COMMAND); | ||
204 | if (rv) { | ||
205 | fprintf(stderr, "Error: cannot access /bin/cp\n"); | ||
206 | exit(1); | ||
207 | } | ||
208 | /* coverity[toctou] */ | ||
209 | if (chown(RUN_CP_COMMAND, 0, 0)) | ||
210 | errExit("chown"); | ||
211 | if (chmod(RUN_CP_COMMAND, 0755)) | ||
212 | errExit("chmod"); | ||
213 | |||
214 | free(fname); | ||
215 | } | ||
216 | } | ||
217 | |||
218 | // delete the temporary cp command | ||
219 | void fs_delete_cp_command(void) { | ||
220 | unlink(RUN_CP_COMMAND); | ||
221 | } | ||
222 | 33 | ||
223 | //*********************************************** | 34 | //*********************************************** |
224 | // process profile file | 35 | // process profile file |
@@ -228,6 +39,8 @@ typedef enum { | |||
228 | BLACKLIST_NOLOG, | 39 | BLACKLIST_NOLOG, |
229 | MOUNT_READONLY, | 40 | MOUNT_READONLY, |
230 | MOUNT_TMPFS, | 41 | MOUNT_TMPFS, |
42 | MOUNT_NOEXEC, | ||
43 | MOUNT_RDWR, | ||
231 | OPERATION_MAX | 44 | OPERATION_MAX |
232 | } OPERATION; | 45 | } OPERATION; |
233 | 46 | ||
@@ -242,14 +55,9 @@ static void disable_file(OPERATION op, const char *filename) { | |||
242 | assert(op <OPERATION_MAX); | 55 | assert(op <OPERATION_MAX); |
243 | last_disable = UNSUCCESSFUL; | 56 | last_disable = UNSUCCESSFUL; |
244 | 57 | ||
245 | // rebuild /run/firejail directory in case tmpfs was mounted on top of /run | ||
246 | fs_build_firejail_dir(); | ||
247 | |||
248 | // Resolve all symlinks | 58 | // Resolve all symlinks |
249 | char* fname = realpath(filename, NULL); | 59 | char* fname = realpath(filename, NULL); |
250 | if (fname == NULL && errno != EACCES) { | 60 | if (fname == NULL && errno != EACCES) { |
251 | if (arg_debug) | ||
252 | printf("Warning (realpath): %s is an invalid file, skipping...\n", filename); | ||
253 | return; | 61 | return; |
254 | } | 62 | } |
255 | if (fname == NULL && errno == EACCES) { | 63 | if (fname == NULL && errno == EACCES) { |
@@ -298,9 +106,10 @@ static void disable_file(OPERATION op, const char *filename) { | |||
298 | // some distros put all executables under /usr/bin and make /bin a symbolic link | 106 | // some distros put all executables under /usr/bin and make /bin a symbolic link |
299 | if ((strcmp(fname, "/bin") == 0 || strcmp(fname, "/usr/bin") == 0) && | 107 | if ((strcmp(fname, "/bin") == 0 || strcmp(fname, "/usr/bin") == 0) && |
300 | is_link(filename) && | 108 | is_link(filename) && |
301 | S_ISDIR(s.st_mode)) | 109 | S_ISDIR(s.st_mode)) { |
302 | fprintf(stderr, "Warning: %s directory link was not blacklisted\n", filename); | 110 | if (!arg_quiet) |
303 | 111 | fprintf(stderr, "Warning: %s directory link was not blacklisted\n", filename); | |
112 | } | ||
304 | else { | 113 | else { |
305 | if (arg_debug) | 114 | if (arg_debug) |
306 | printf("Disable %s\n", fname); | 115 | printf("Disable %s\n", fname); |
@@ -332,6 +141,18 @@ static void disable_file(OPERATION op, const char *filename) { | |||
332 | fs_rdonly(fname); | 141 | fs_rdonly(fname); |
333 | // todo: last_disable = SUCCESSFUL; | 142 | // todo: last_disable = SUCCESSFUL; |
334 | } | 143 | } |
144 | else if (op == MOUNT_RDWR) { | ||
145 | if (arg_debug) | ||
146 | printf("Mounting read-only %s\n", fname); | ||
147 | fs_rdwr(fname); | ||
148 | // todo: last_disable = SUCCESSFUL; | ||
149 | } | ||
150 | else if (op == MOUNT_NOEXEC) { | ||
151 | if (arg_debug) | ||
152 | printf("Mounting noexec %s\n", fname); | ||
153 | fs_noexec(fname); | ||
154 | // todo: last_disable = SUCCESSFUL; | ||
155 | } | ||
335 | else if (op == MOUNT_TMPFS) { | 156 | else if (op == MOUNT_TMPFS) { |
336 | if (S_ISDIR(s.st_mode)) { | 157 | if (S_ISDIR(s.st_mode)) { |
337 | if (arg_debug) | 158 | if (arg_debug) |
@@ -361,7 +182,7 @@ static void globbing(OPERATION op, const char *pattern, const char *noblacklist[ | |||
361 | glob_t globbuf; | 182 | glob_t globbuf; |
362 | // Profiles contain blacklists for files that might not exist on a user's machine. | 183 | // Profiles contain blacklists for files that might not exist on a user's machine. |
363 | // GLOB_NOCHECK makes that okay. | 184 | // GLOB_NOCHECK makes that okay. |
364 | int globerr = glob(pattern, GLOB_NOCHECK | GLOB_NOSORT, NULL, &globbuf); | 185 | int globerr = glob(pattern, GLOB_NOCHECK | GLOB_NOSORT | GLOB_PERIOD, NULL, &globbuf); |
365 | if (globerr) { | 186 | if (globerr) { |
366 | fprintf(stderr, "Error: failed to glob pattern %s\n", pattern); | 187 | fprintf(stderr, "Error: failed to glob pattern %s\n", pattern); |
367 | exit(1); | 188 | exit(1); |
@@ -426,21 +247,13 @@ void fs_blacklist(void) { | |||
426 | 247 | ||
427 | // process bind command | 248 | // process bind command |
428 | if (strncmp(entry->data, "bind ", 5) == 0) { | 249 | if (strncmp(entry->data, "bind ", 5) == 0) { |
250 | struct stat s; | ||
429 | char *dname1 = entry->data + 5; | 251 | char *dname1 = entry->data + 5; |
430 | char *dname2 = split_comma(dname1); | 252 | char *dname2 = split_comma(dname1); |
431 | if (dname2 == NULL) { | 253 | if (dname2 == NULL || |
432 | fprintf(stderr, "Error: second directory missing in bind command\n"); | 254 | stat(dname1, &s) == -1 || |
433 | entry = entry->next; | 255 | stat(dname2, &s) == -1) { |
434 | continue; | 256 | fprintf(stderr, "Error: invalid bind command, directory missing\n"); |
435 | } | ||
436 | struct stat s; | ||
437 | if (stat(dname1, &s) == -1) { | ||
438 | fprintf(stderr, "Error: cannot find %s for bind command\n", dname1); | ||
439 | entry = entry->next; | ||
440 | continue; | ||
441 | } | ||
442 | if (stat(dname2, &s) == -1) { | ||
443 | fprintf(stderr, "Error: cannot find %s for bind command\n", dname2); | ||
444 | entry = entry->next; | 257 | entry = entry->next; |
445 | continue; | 258 | continue; |
446 | } | 259 | } |
@@ -452,11 +265,8 @@ void fs_blacklist(void) { | |||
452 | if (mount(dname1, dname2, NULL, MS_BIND|MS_REC, NULL) < 0) | 265 | if (mount(dname1, dname2, NULL, MS_BIND|MS_REC, NULL) < 0) |
453 | errExit("mount bind"); | 266 | errExit("mount bind"); |
454 | /* coverity[toctou] */ | 267 | /* coverity[toctou] */ |
455 | if (chown(dname2, s.st_uid, s.st_gid) == -1) | 268 | if (set_perms(dname2, s.st_uid, s.st_gid,s.st_mode)) |
456 | errExit("mount-bind chown"); | 269 | errExit("set_perms"); |
457 | /* coverity[toctou] */ | ||
458 | if (chmod(dname2, s.st_mode) == -1) | ||
459 | errExit("mount-bind chmod"); | ||
460 | 270 | ||
461 | entry = entry->next; | 271 | entry = entry->next; |
462 | continue; | 272 | continue; |
@@ -464,12 +274,40 @@ void fs_blacklist(void) { | |||
464 | 274 | ||
465 | // Process noblacklist command | 275 | // Process noblacklist command |
466 | if (strncmp(entry->data, "noblacklist ", 12) == 0) { | 276 | if (strncmp(entry->data, "noblacklist ", 12) == 0) { |
467 | if (noblacklist_c >= noblacklist_m) { | 277 | char **paths = build_paths(); |
468 | noblacklist_m *= 2; | 278 | |
469 | noblacklist = realloc(noblacklist, sizeof(*noblacklist) * noblacklist_m); | 279 | char *enames[sizeof(paths)+1] = {0}; |
470 | if (noblacklist == NULL) | 280 | int i = 0; |
471 | errExit("failed increasing memory for noblacklist entries");} | 281 | |
472 | noblacklist[noblacklist_c++] = expand_home(entry->data + 12, homedir); | 282 | if (strncmp(entry->data + 12, "${PATH}", 7) == 0) { |
283 | // expand ${PATH} macro | ||
284 | while (paths[i] != NULL) { | ||
285 | if (asprintf(&enames[i], "%s%s", paths[i], entry->data + 19) == -1) | ||
286 | errExit("asprintf"); | ||
287 | i++; | ||
288 | } | ||
289 | } else { | ||
290 | // expand ${HOME} macro if found or pass as is | ||
291 | enames[0] = expand_home(entry->data + 12, homedir); | ||
292 | enames[1] = NULL; | ||
293 | } | ||
294 | |||
295 | i = 0; | ||
296 | while (enames[i] != NULL) { | ||
297 | if (noblacklist_c >= noblacklist_m) { | ||
298 | noblacklist_m *= 2; | ||
299 | noblacklist = realloc(noblacklist, sizeof(*noblacklist) * noblacklist_m); | ||
300 | if (noblacklist == NULL) | ||
301 | errExit("failed increasing memory for noblacklist entries"); | ||
302 | } | ||
303 | noblacklist[noblacklist_c++] = enames[i]; | ||
304 | i++; | ||
305 | } | ||
306 | |||
307 | while (enames[i] != NULL) { | ||
308 | free(enames[i]); | ||
309 | } | ||
310 | |||
473 | entry = entry->next; | 311 | entry = entry->next; |
474 | continue; | 312 | continue; |
475 | } | 313 | } |
@@ -487,10 +325,32 @@ void fs_blacklist(void) { | |||
487 | ptr = entry->data + 10; | 325 | ptr = entry->data + 10; |
488 | op = MOUNT_READONLY; | 326 | op = MOUNT_READONLY; |
489 | } | 327 | } |
328 | else if (strncmp(entry->data, "read-write ", 11) == 0) { | ||
329 | ptr = entry->data + 11; | ||
330 | op = MOUNT_RDWR; | ||
331 | } | ||
332 | else if (strncmp(entry->data, "noexec ", 7) == 0) { | ||
333 | ptr = entry->data + 7; | ||
334 | op = MOUNT_NOEXEC; | ||
335 | } | ||
490 | else if (strncmp(entry->data, "tmpfs ", 6) == 0) { | 336 | else if (strncmp(entry->data, "tmpfs ", 6) == 0) { |
491 | ptr = entry->data + 6; | 337 | ptr = entry->data + 6; |
492 | op = MOUNT_TMPFS; | 338 | op = MOUNT_TMPFS; |
493 | } | 339 | } |
340 | else if (strncmp(entry->data, "mkdir ", 6) == 0) { | ||
341 | EUID_USER(); | ||
342 | fs_mkdir(entry->data + 6); | ||
343 | EUID_ROOT(); | ||
344 | entry = entry->next; | ||
345 | continue; | ||
346 | } | ||
347 | else if (strncmp(entry->data, "mkfile ", 7) == 0) { | ||
348 | EUID_USER(); | ||
349 | fs_mkfile(entry->data + 7); | ||
350 | EUID_ROOT(); | ||
351 | entry = entry->next; | ||
352 | continue; | ||
353 | } | ||
494 | else { | 354 | else { |
495 | fprintf(stderr, "Error: invalid profile line %s\n", entry->data); | 355 | fprintf(stderr, "Error: invalid profile line %s\n", entry->data); |
496 | entry = entry->next; | 356 | entry = entry->next; |
@@ -542,38 +402,56 @@ void fs_rdonly(const char *dir) { | |||
542 | int rv = stat(dir, &s); | 402 | int rv = stat(dir, &s); |
543 | if (rv == 0) { | 403 | if (rv == 0) { |
544 | // mount --bind /bin /bin | 404 | // mount --bind /bin /bin |
545 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
546 | errExit("mount read-only"); | ||
547 | // mount --bind -o remount,ro /bin | 405 | // mount --bind -o remount,ro /bin |
548 | if (mount(NULL, dir, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_REC, NULL) < 0) | 406 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || |
407 | mount(NULL, dir, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_REC, NULL) < 0) | ||
549 | errExit("mount read-only"); | 408 | errExit("mount read-only"); |
550 | fs_logger2("read-only", dir); | 409 | fs_logger2("read-only", dir); |
551 | } | 410 | } |
552 | } | 411 | } |
553 | void fs_rdonly_noexit(const char *dir) { | 412 | |
413 | static void fs_rdwr(const char *dir) { | ||
414 | assert(dir); | ||
415 | // check directory exists | ||
416 | struct stat s; | ||
417 | int rv = stat(dir, &s); | ||
418 | if (rv == 0) { | ||
419 | // if the file is outside /home directory, allow only root user | ||
420 | uid_t u = getuid(); | ||
421 | if (u != 0 && s.st_uid != u) { | ||
422 | if (!arg_quiet) | ||
423 | fprintf(stderr, "Warning: you are not allowed to change %s to read-write\n", dir); | ||
424 | return; | ||
425 | } | ||
426 | |||
427 | // mount --bind /bin /bin | ||
428 | // mount --bind -o remount,rw /bin | ||
429 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || | ||
430 | mount(NULL, dir, NULL, MS_BIND|MS_REMOUNT|MS_REC, NULL) < 0) | ||
431 | errExit("mount read-write"); | ||
432 | fs_logger2("read-write", dir); | ||
433 | } | ||
434 | } | ||
435 | |||
436 | void fs_noexec(const char *dir) { | ||
554 | assert(dir); | 437 | assert(dir); |
555 | // check directory exists | 438 | // check directory exists |
556 | struct stat s; | 439 | struct stat s; |
557 | int rv = stat(dir, &s); | 440 | int rv = stat(dir, &s); |
558 | if (rv == 0) { | 441 | if (rv == 0) { |
559 | int merr = 0; | ||
560 | // mount --bind /bin /bin | 442 | // mount --bind /bin /bin |
561 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
562 | merr = 1; | ||
563 | // mount --bind -o remount,ro /bin | 443 | // mount --bind -o remount,ro /bin |
564 | if (mount(NULL, dir, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_REC, NULL) < 0) | 444 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || |
565 | merr = 1; | 445 | mount(NULL, dir, NULL, MS_BIND|MS_REMOUNT|MS_NOEXEC|MS_NODEV|MS_NOSUID|MS_REC, NULL) < 0) |
566 | if (merr) | 446 | errExit("mount noexec"); |
567 | fprintf(stderr, "Warning: cannot mount %s read-only\n", dir); | 447 | fs_logger2("noexec", dir); |
568 | else | ||
569 | fs_logger2("read-only", dir); | ||
570 | } | 448 | } |
571 | } | 449 | } |
572 | 450 | ||
451 | |||
452 | |||
573 | // mount /proc and /sys directories | 453 | // mount /proc and /sys directories |
574 | void fs_proc_sys_dev_boot(void) { | 454 | void fs_proc_sys_dev_boot(void) { |
575 | struct stat s; | ||
576 | |||
577 | if (arg_debug) | 455 | if (arg_debug) |
578 | printf("Remounting /proc and /proc/sys filesystems\n"); | 456 | printf("Remounting /proc and /proc/sys filesystems\n"); |
579 | if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) | 457 | if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) |
@@ -581,10 +459,8 @@ void fs_proc_sys_dev_boot(void) { | |||
581 | fs_logger("remount /proc"); | 459 | fs_logger("remount /proc"); |
582 | 460 | ||
583 | // remount /proc/sys readonly | 461 | // remount /proc/sys readonly |
584 | if (mount("/proc/sys", "/proc/sys", NULL, MS_BIND | MS_REC, NULL) < 0) | 462 | if (mount("/proc/sys", "/proc/sys", NULL, MS_BIND | MS_REC, NULL) < 0 || |
585 | errExit("mounting /proc/sys"); | 463 | mount(NULL, "/proc/sys", NULL, MS_BIND | MS_REMOUNT | MS_RDONLY | MS_REC, NULL) < 0) |
586 | |||
587 | if (mount(NULL, "/proc/sys", NULL, MS_BIND | MS_REMOUNT | MS_RDONLY | MS_REC, NULL) < 0) | ||
588 | errExit("mounting /proc/sys"); | 464 | errExit("mounting /proc/sys"); |
589 | fs_logger("read-only /proc/sys"); | 465 | fs_logger("read-only /proc/sys"); |
590 | 466 | ||
@@ -601,114 +477,71 @@ void fs_proc_sys_dev_boot(void) { | |||
601 | fs_logger("remount /sys"); | 477 | fs_logger("remount /sys"); |
602 | } | 478 | } |
603 | 479 | ||
604 | if (stat("/sys/firmware", &s) == 0) { | 480 | disable_file(BLACKLIST_FILE, "/sys/firmware"); |
605 | disable_file(BLACKLIST_FILE, "/sys/firmware"); | 481 | disable_file(BLACKLIST_FILE, "/sys/hypervisor"); |
606 | } | 482 | { // allow user access to /sys/fs if "--noblacklist=/sys/fs" is present on the command line |
607 | 483 | EUID_USER(); | |
608 | if (stat("/sys/hypervisor", &s) == 0) { | 484 | profile_add("blacklist /sys/fs"); |
609 | disable_file(BLACKLIST_FILE, "/sys/hypervisor"); | 485 | EUID_ROOT(); |
610 | } | ||
611 | |||
612 | if (stat("/sys/fs", &s) == 0) { | ||
613 | disable_file(BLACKLIST_FILE, "/sys/fs"); | ||
614 | } | ||
615 | |||
616 | if (stat("/sys/module", &s) == 0) { | ||
617 | disable_file(BLACKLIST_FILE, "/sys/module"); | ||
618 | } | 486 | } |
619 | 487 | disable_file(BLACKLIST_FILE, "/sys/module"); | |
620 | if (stat("/sys/power", &s) == 0) { | 488 | disable_file(BLACKLIST_FILE, "/sys/power"); |
621 | disable_file(BLACKLIST_FILE, "/sys/power"); | 489 | disable_file(BLACKLIST_FILE, "/sys/kernel/debug"); |
622 | } | 490 | disable_file(BLACKLIST_FILE, "/sys/kernel/vmcoreinfo"); |
623 | 491 | disable_file(BLACKLIST_FILE, "/sys/kernel/uevent_helper"); | |
624 | // if (mount("sysfs", "/sys", "sysfs", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REC, NULL) < 0) | 492 | |
625 | // errExit("mounting /sys"); | 493 | // various /proc/sys files |
626 | 494 | disable_file(BLACKLIST_FILE, "/proc/sys/security"); | |
627 | // Disable SysRq | 495 | disable_file(BLACKLIST_FILE, "/proc/sys/efi/vars"); |
628 | // a linux box can be shut down easily using the following commands (as root): | 496 | disable_file(BLACKLIST_FILE, "/proc/sys/fs/binfmt_misc"); |
629 | // # echo 1 > /proc/sys/kernel/sysrq | 497 | disable_file(BLACKLIST_FILE, "/proc/sys/kernel/core_pattern"); |
630 | // #echo b > /proc/sysrq-trigger | 498 | disable_file(BLACKLIST_FILE, "/proc/sys/kernel/modprobe"); |
631 | // for more information see https://www.kernel.org/doc/Documentation/sysrq.txt | 499 | disable_file(BLACKLIST_FILE, "/proc/sysrq-trigger"); |
632 | if (arg_debug) | 500 | disable_file(BLACKLIST_FILE, "/proc/sys/kernel/hotplug"); |
633 | printf("Disable /proc/sysrq-trigger\n"); | 501 | disable_file(BLACKLIST_FILE, "/proc/sys/vm/panic_on_oom"); |
634 | fs_rdonly_noexit("/proc/sysrq-trigger"); | 502 | |
635 | 503 | // various /proc files | |
636 | // disable hotplug and uevent_helper | 504 | disable_file(BLACKLIST_FILE, "/proc/irq"); |
637 | if (arg_debug) | 505 | disable_file(BLACKLIST_FILE, "/proc/bus"); |
638 | printf("Disable /proc/sys/kernel/hotplug\n"); | 506 | disable_file(BLACKLIST_FILE, "/proc/config.gz"); |
639 | fs_rdonly_noexit("/proc/sys/kernel/hotplug"); | 507 | disable_file(BLACKLIST_FILE, "/proc/sched_debug"); |
640 | if (arg_debug) | 508 | disable_file(BLACKLIST_FILE, "/proc/timer_list"); |
641 | printf("Disable /sys/kernel/uevent_helper\n"); | 509 | disable_file(BLACKLIST_FILE, "/proc/timer_stats"); |
642 | fs_rdonly_noexit("/sys/kernel/uevent_helper"); | ||
643 | |||
644 | // read-only /proc/irq and /proc/bus | ||
645 | if (arg_debug) | ||
646 | printf("Disable /proc/irq\n"); | ||
647 | fs_rdonly_noexit("/proc/irq"); | ||
648 | if (arg_debug) | ||
649 | printf("Disable /proc/bus\n"); | ||
650 | fs_rdonly_noexit("/proc/bus"); | ||
651 | |||
652 | // disable /proc/kcore | ||
653 | disable_file(BLACKLIST_FILE, "/proc/kcore"); | 510 | disable_file(BLACKLIST_FILE, "/proc/kcore"); |
654 | |||
655 | // disable /proc/kallsyms | ||
656 | disable_file(BLACKLIST_FILE, "/proc/kallsyms"); | 511 | disable_file(BLACKLIST_FILE, "/proc/kallsyms"); |
512 | disable_file(BLACKLIST_FILE, "/proc/mem"); | ||
513 | disable_file(BLACKLIST_FILE, "/proc/kmem"); | ||
657 | 514 | ||
658 | // disable /boot | 515 | // remove kernel symbol information |
659 | if (stat("/boot", &s) == 0) { | 516 | if (!arg_allow_debuggers) { |
660 | if (arg_debug) | 517 | disable_file(BLACKLIST_FILE, "/usr/src/linux"); |
661 | printf("Disable /boot directory\n"); | 518 | disable_file(BLACKLIST_FILE, "/lib/modules"); |
519 | disable_file(BLACKLIST_FILE, "/usr/lib/debug"); | ||
662 | disable_file(BLACKLIST_FILE, "/boot"); | 520 | disable_file(BLACKLIST_FILE, "/boot"); |
663 | } | 521 | } |
664 | 522 | ||
665 | // disable /selinux | 523 | // disable /selinux |
666 | if (stat("/selinux", &s) == 0) { | 524 | disable_file(BLACKLIST_FILE, "/selinux"); |
667 | if (arg_debug) | ||
668 | printf("Disable /selinux directory\n"); | ||
669 | disable_file(BLACKLIST_FILE, "/selinux"); | ||
670 | } | ||
671 | 525 | ||
672 | // disable /dev/port | 526 | // disable /dev/port |
673 | if (stat("/dev/port", &s) == 0) { | 527 | disable_file(BLACKLIST_FILE, "/dev/port"); |
674 | disable_file(BLACKLIST_FILE, "/dev/port"); | ||
675 | } | ||
676 | 528 | ||
677 | if (getuid() != 0) { | 529 | if (getuid() != 0) { |
678 | // disable /dev/kmsg | 530 | // disable /dev/kmsg and /proc/kmsg |
679 | if (stat("/dev/kmsg", &s) == 0) { | 531 | disable_file(BLACKLIST_FILE, "/dev/kmsg"); |
680 | disable_file(BLACKLIST_FILE, "/dev/kmsg"); | 532 | disable_file(BLACKLIST_FILE, "/proc/kmsg"); |
681 | } | ||
682 | |||
683 | // disable /proc/kmsg | ||
684 | if (stat("/proc/kmsg", &s) == 0) { | ||
685 | disable_file(BLACKLIST_FILE, "/proc/kmsg"); | ||
686 | } | ||
687 | } | 533 | } |
688 | } | 534 | } |
689 | 535 | ||
690 | // disable firejail configuration in /etc/firejail and in ~/.config/firejail | 536 | // disable firejail configuration in /etc/firejail and in ~/.config/firejail |
691 | static void disable_firejail_config(void) { | 537 | static void disable_config(void) { |
692 | struct stat s; | 538 | struct stat s; |
693 | if (stat("/etc/firejail", &s) == 0) | ||
694 | disable_file(BLACKLIST_FILE, "/etc/firejail"); | ||
695 | 539 | ||
696 | char *fname; | 540 | char *fname; |
697 | if (asprintf(&fname, "%s/.config/firejail", cfg.homedir) == -1) | 541 | if (asprintf(&fname, "%s/.config/firejail", cfg.homedir) == -1) |
698 | errExit("asprintf"); | 542 | errExit("asprintf"); |
699 | if (stat(fname, &s) == 0) | 543 | if (stat(fname, &s) == 0) |
700 | disable_file(BLACKLIST_FILE, fname); | 544 | disable_file(BLACKLIST_FILE, fname); |
701 | |||
702 | if (stat("/usr/local/etc/firejail", &s) == 0) | ||
703 | disable_file(BLACKLIST_FILE, "/usr/local/etc/firejail"); | ||
704 | |||
705 | if (strcmp(PREFIX, "/usr/local")) { | ||
706 | if (asprintf(&fname, "%s/etc/firejail", PREFIX) == -1) | ||
707 | errExit("asprintf"); | ||
708 | if (stat(fname, &s) == 0) | ||
709 | disable_file(BLACKLIST_FILE, fname); | ||
710 | } | ||
711 | |||
712 | free(fname); | 545 | free(fname); |
713 | 546 | ||
714 | // disable run time information | 547 | // disable run time information |
@@ -725,8 +558,23 @@ static void disable_firejail_config(void) { | |||
725 | 558 | ||
726 | // build a basic read-only filesystem | 559 | // build a basic read-only filesystem |
727 | void fs_basic_fs(void) { | 560 | void fs_basic_fs(void) { |
561 | uid_t uid = getuid(); | ||
562 | |||
728 | if (arg_debug) | 563 | if (arg_debug) |
729 | printf("Mounting read-only /bin, /sbin, /lib, /lib32, /lib64, /usr, /etc, /var\n"); | 564 | printf("Mounting read-only /bin, /sbin, /lib, /lib32, /lib64, /usr"); |
565 | if (!arg_writable_etc) { | ||
566 | fs_rdonly("/etc"); | ||
567 | if (uid) | ||
568 | fs_noexec("/etc"); | ||
569 | if (arg_debug) printf(", /etc"); | ||
570 | } | ||
571 | if (!arg_writable_var) { | ||
572 | fs_rdonly("/var"); | ||
573 | if (uid) | ||
574 | fs_noexec("/var"); | ||
575 | if (arg_debug) printf(", /var"); | ||
576 | } | ||
577 | if (arg_debug) printf("\n"); | ||
730 | fs_rdonly("/bin"); | 578 | fs_rdonly("/bin"); |
731 | fs_rdonly("/sbin"); | 579 | fs_rdonly("/sbin"); |
732 | fs_rdonly("/lib"); | 580 | fs_rdonly("/lib"); |
@@ -734,12 +582,10 @@ void fs_basic_fs(void) { | |||
734 | fs_rdonly("/lib32"); | 582 | fs_rdonly("/lib32"); |
735 | fs_rdonly("/libx32"); | 583 | fs_rdonly("/libx32"); |
736 | fs_rdonly("/usr"); | 584 | fs_rdonly("/usr"); |
737 | fs_rdonly("/etc"); | ||
738 | fs_rdonly("/var"); | ||
739 | 585 | ||
740 | // update /var directory in order to support multiple sandboxes running on the same root directory | 586 | // update /var directory in order to support multiple sandboxes running on the same root directory |
741 | if (!arg_private_dev) | 587 | // if (!arg_private_dev) |
742 | fs_dev_shm(); | 588 | // fs_dev_shm(); |
743 | fs_var_lock(); | 589 | fs_var_lock(); |
744 | fs_var_tmp(); | 590 | fs_var_tmp(); |
745 | fs_var_log(); | 591 | fs_var_log(); |
@@ -750,10 +596,51 @@ void fs_basic_fs(void) { | |||
750 | // don't leak user information | 596 | // don't leak user information |
751 | restrict_users(); | 597 | restrict_users(); |
752 | 598 | ||
753 | disable_firejail_config(); | 599 | // when starting as root, firejail config is not disabled; |
600 | // this mode could be used to install and test new software by chaining | ||
601 | // firejail sandboxes (firejail --force) | ||
602 | if (uid) | ||
603 | disable_config(); | ||
754 | } | 604 | } |
755 | 605 | ||
756 | 606 | ||
607 | |||
608 | #ifdef HAVE_OVERLAYFS | ||
609 | char *fs_check_overlay_dir(const char *subdirname, int allow_reuse) { | ||
610 | struct stat s; | ||
611 | char *dirname; | ||
612 | |||
613 | // create ~/.firejail directory | ||
614 | if (asprintf(&dirname, "%s/.firejail", cfg.homedir) == -1) | ||
615 | errExit("asprintf"); | ||
616 | if (stat(dirname, &s) == -1) { | ||
617 | mkdir_attr(dirname, 0700, 0, 0); | ||
618 | } | ||
619 | else if (is_link(dirname)) { | ||
620 | fprintf(stderr, "Error: invalid ~/.firejail directory\n"); | ||
621 | exit(1); | ||
622 | } | ||
623 | free(dirname); | ||
624 | |||
625 | // check overlay directory | ||
626 | if (asprintf(&dirname, "%s/.firejail/%s", cfg.homedir, subdirname) == -1) | ||
627 | errExit("asprintf"); | ||
628 | if (is_link(dirname)) { | ||
629 | fprintf(stderr, "Error: overlay directory is a symbolic link\n"); | ||
630 | exit(1); | ||
631 | } | ||
632 | if (allow_reuse == 0) { | ||
633 | if (stat(dirname, &s) == 0) { | ||
634 | fprintf(stderr, "Error: overlay directory already exists: %s\n", dirname); | ||
635 | exit(1); | ||
636 | } | ||
637 | } | ||
638 | |||
639 | return dirname; | ||
640 | } | ||
641 | |||
642 | |||
643 | |||
757 | // mount overlayfs on top of / directory | 644 | // mount overlayfs on top of / directory |
758 | // mounting an overlay and chrooting into it: | 645 | // mounting an overlay and chrooting into it: |
759 | // | 646 | // |
@@ -806,48 +693,53 @@ void fs_overlayfs(void) { | |||
806 | if (major == 3 && minor < 18) | 693 | if (major == 3 && minor < 18) |
807 | oldkernel = 1; | 694 | oldkernel = 1; |
808 | 695 | ||
809 | // build overlay directories | ||
810 | fs_build_mnt_dir(); | ||
811 | |||
812 | char *oroot; | 696 | char *oroot; |
813 | if(asprintf(&oroot, "%s/oroot", RUN_MNT_DIR) == -1) | 697 | if(asprintf(&oroot, "%s/oroot", RUN_MNT_DIR) == -1) |
814 | errExit("asprintf"); | 698 | errExit("asprintf"); |
815 | if (mkdir(oroot, 0755)) | 699 | mkdir_attr(oroot, 0755, 0, 0); |
816 | errExit("mkdir"); | ||
817 | if (chown(oroot, 0, 0) < 0) | ||
818 | errExit("chown"); | ||
819 | if (chmod(oroot, 0755) < 0) | ||
820 | errExit("chmod"); | ||
821 | 700 | ||
701 | struct stat s; | ||
822 | char *basedir = RUN_MNT_DIR; | 702 | char *basedir = RUN_MNT_DIR; |
823 | if (arg_overlay_keep) { | 703 | if (arg_overlay_keep) { |
824 | // set base for working and diff directories | 704 | // set base for working and diff directories |
825 | basedir = cfg.overlay_dir; | 705 | basedir = cfg.overlay_dir; |
826 | if (mkdir(basedir, 0755) != 0) { | 706 | |
827 | fprintf(stderr, "Error: cannot create overlay directory\n"); | 707 | // does the overlay exist? |
828 | exit(1); | 708 | if (stat(basedir, &s) == 0) { |
709 | if (arg_overlay_reuse == 0) { | ||
710 | fprintf(stderr, "Error: overlay directory exists, but reuse is not allowed\n"); | ||
711 | exit(1); | ||
712 | } | ||
713 | } | ||
714 | else { | ||
715 | if (mkdir(basedir, 0755) != 0) { | ||
716 | fprintf(stderr, "Error: cannot create overlay directory\n"); | ||
717 | exit(1); | ||
718 | } | ||
829 | } | 719 | } |
830 | } | 720 | } |
831 | 721 | ||
832 | char *odiff; | 722 | char *odiff; |
833 | if(asprintf(&odiff, "%s/odiff", basedir) == -1) | 723 | if(asprintf(&odiff, "%s/odiff", basedir) == -1) |
834 | errExit("asprintf"); | 724 | errExit("asprintf"); |
835 | if (mkdir(odiff, 0755)) | 725 | |
836 | errExit("mkdir"); | 726 | // no need to check arg_overlay_reuse |
837 | if (chown(odiff, 0, 0) < 0) | 727 | if (stat(odiff, &s) != 0) { |
838 | errExit("chown"); | 728 | mkdir_attr(odiff, 0755, 0, 0); |
839 | if (chmod(odiff, 0755) < 0) | 729 | } |
840 | errExit("chmod"); | 730 | else if (set_perms(odiff, 0, 0, 0755)) |
731 | errExit("set_perms"); | ||
841 | 732 | ||
842 | char *owork; | 733 | char *owork; |
843 | if(asprintf(&owork, "%s/owork", basedir) == -1) | 734 | if(asprintf(&owork, "%s/owork", basedir) == -1) |
844 | errExit("asprintf"); | 735 | errExit("asprintf"); |
845 | if (mkdir(owork, 0755)) | 736 | |
846 | errExit("mkdir"); | 737 | // no need to check arg_overlay_reuse |
847 | if (chown(owork, 0, 0) < 0) | 738 | if (stat(owork, &s) != 0) { |
739 | mkdir_attr(owork, 0755, 0, 0); | ||
740 | } | ||
741 | else if (set_perms(owork, 0, 0, 0755)) | ||
848 | errExit("chown"); | 742 | errExit("chown"); |
849 | if (chmod(owork, 0755) < 0) | ||
850 | errExit("chmod"); | ||
851 | 743 | ||
852 | // mount overlayfs | 744 | // mount overlayfs |
853 | if (arg_debug) | 745 | if (arg_debug) |
@@ -899,21 +791,23 @@ void fs_overlayfs(void) { | |||
899 | 791 | ||
900 | if(asprintf(&hdiff, "%s/hdiff", basedir) == -1) | 792 | if(asprintf(&hdiff, "%s/hdiff", basedir) == -1) |
901 | errExit("asprintf"); | 793 | errExit("asprintf"); |
902 | if (mkdir(hdiff, S_IRWXU | S_IRWXG | S_IRWXO)) | 794 | |
903 | errExit("mkdir"); | 795 | // no need to check arg_overlay_reuse |
904 | if (chown(hdiff, 0, 0) < 0) | 796 | if (stat(hdiff, &s) != 0) { |
905 | errExit("chown"); | 797 | mkdir_attr(hdiff, S_IRWXU | S_IRWXG | S_IRWXO, 0, 0); |
906 | if (chmod(hdiff, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) | 798 | } |
907 | errExit("chmod"); | 799 | else if (set_perms(hdiff, 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) |
800 | errExit("set_perms"); | ||
908 | 801 | ||
909 | if(asprintf(&hwork, "%s/hwork", basedir) == -1) | 802 | if(asprintf(&hwork, "%s/hwork", basedir) == -1) |
910 | errExit("asprintf"); | 803 | errExit("asprintf"); |
911 | if (mkdir(hwork, S_IRWXU | S_IRWXG | S_IRWXO)) | 804 | |
912 | errExit("mkdir"); | 805 | // no need to check arg_overlay_reuse |
913 | if (chown(hwork, 0, 0) < 0) | 806 | if (stat(hwork, &s) != 0) { |
914 | errExit("chown"); | 807 | mkdir_attr(hwork, S_IRWXU | S_IRWXG | S_IRWXO, 0, 0); |
915 | if (chmod(hwork, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) | 808 | } |
916 | errExit("chmod"); | 809 | else if (set_perms(hwork, 0, 0, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) |
810 | errExit("set_perms"); | ||
917 | 811 | ||
918 | // no homedir in overlay so now mount another overlay for /home | 812 | // no homedir in overlay so now mount another overlay for /home |
919 | if (asprintf(&option, "lowerdir=/home,upperdir=%s,workdir=%s", hdiff, hwork) == -1) | 813 | if (asprintf(&option, "lowerdir=/home,upperdir=%s,workdir=%s", hdiff, hwork) == -1) |
@@ -950,13 +844,30 @@ void fs_overlayfs(void) { | |||
950 | errExit("mounting /run"); | 844 | errExit("mounting /run"); |
951 | fs_logger("whitelist /run"); | 845 | fs_logger("whitelist /run"); |
952 | 846 | ||
847 | // mount-bind /tmp/.X11-unix directory | ||
848 | if (stat("/tmp/.X11-unix", &s) == 0) { | ||
849 | if (arg_debug) | ||
850 | printf("Mounting /tmp/.X11-unix\n"); | ||
851 | char *x11; | ||
852 | if (asprintf(&x11, "%s/tmp/.X11-unix", oroot) == -1) | ||
853 | errExit("asprintf"); | ||
854 | if (mount("/tmp/.X11-unix", x11, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
855 | fprintf(stderr, "Warning: cannot mount /tmp/.X11-unix in overlay\n"); | ||
856 | else | ||
857 | fs_logger("whitelist /tmp/.X11-unix"); | ||
858 | free(x11); | ||
859 | } | ||
860 | |||
953 | // chroot in the new filesystem | 861 | // chroot in the new filesystem |
862 | #ifdef HAVE_GCOV | ||
863 | __gcov_flush(); | ||
864 | #endif | ||
954 | if (chroot(oroot) == -1) | 865 | if (chroot(oroot) == -1) |
955 | errExit("chroot"); | 866 | errExit("chroot"); |
956 | 867 | ||
957 | // update /var directory in order to support multiple sandboxes running on the same root directory | 868 | // update /var directory in order to support multiple sandboxes running on the same root directory |
958 | if (!arg_private_dev) | 869 | // if (!arg_private_dev) |
959 | fs_dev_shm(); | 870 | // fs_dev_shm(); |
960 | fs_var_lock(); | 871 | fs_var_lock(); |
961 | fs_var_tmp(); | 872 | fs_var_tmp(); |
962 | fs_var_log(); | 873 | fs_var_log(); |
@@ -967,20 +878,18 @@ void fs_overlayfs(void) { | |||
967 | // don't leak user information | 878 | // don't leak user information |
968 | restrict_users(); | 879 | restrict_users(); |
969 | 880 | ||
970 | // when starting as root in overlay mode, firejail config is not disabled; | 881 | // when starting as root, firejail config is not disabled; |
971 | // this mode could be used to install and test new software by chaining | 882 | // this mode could be used to install and test new software by chaining |
972 | // firejail sandboxes (firejail --force) | 883 | // firejail sandboxes (firejail --force) |
973 | if (getuid() != 0) | 884 | if (getuid() != 0) |
974 | disable_firejail_config(); | 885 | disable_config(); |
975 | else | ||
976 | fprintf(stderr, "Warning: masking /etc/firejail disabled when starting the sandbox as root using --overlay option\n"); | ||
977 | 886 | ||
978 | // cleanup and exit | 887 | // cleanup and exit |
979 | free(option); | 888 | free(option); |
980 | free(oroot); | 889 | free(oroot); |
981 | free(odiff); | 890 | free(odiff); |
982 | } | 891 | } |
983 | 892 | #endif | |
984 | 893 | ||
985 | 894 | ||
986 | #ifdef HAVE_CHROOT | 895 | #ifdef HAVE_CHROOT |
@@ -991,6 +900,16 @@ int fs_check_chroot_dir(const char *rootdir) { | |||
991 | struct stat s; | 900 | struct stat s; |
992 | char *name; | 901 | char *name; |
993 | 902 | ||
903 | // rootdir has to be owned by root | ||
904 | if (stat(rootdir, &s) != 0) { | ||
905 | fprintf(stderr, "Error: cannot find chroot directory\n"); | ||
906 | return 1; | ||
907 | } | ||
908 | if (s.st_uid != 0) { | ||
909 | fprintf(stderr, "Error: chroot directory should be owned by root\n"); | ||
910 | return 1; | ||
911 | } | ||
912 | |||
994 | // check /dev | 913 | // check /dev |
995 | if (asprintf(&name, "%s/dev", rootdir) == -1) | 914 | if (asprintf(&name, "%s/dev", rootdir) == -1) |
996 | errExit("asprintf"); | 915 | errExit("asprintf"); |
@@ -1018,7 +937,7 @@ int fs_check_chroot_dir(const char *rootdir) { | |||
1018 | } | 937 | } |
1019 | free(name); | 938 | free(name); |
1020 | 939 | ||
1021 | // check /proc | 940 | // check /tmp |
1022 | if (asprintf(&name, "%s/tmp", rootdir) == -1) | 941 | if (asprintf(&name, "%s/tmp", rootdir) == -1) |
1023 | errExit("asprintf"); | 942 | errExit("asprintf"); |
1024 | if (stat(name, &s) == -1) { | 943 | if (stat(name, &s) == -1) { |
@@ -1026,16 +945,29 @@ int fs_check_chroot_dir(const char *rootdir) { | |||
1026 | return 1; | 945 | return 1; |
1027 | } | 946 | } |
1028 | free(name); | 947 | free(name); |
1029 | 948 | ||
1030 | // check /bin/bash | 949 | // check /bin/bash |
1031 | if (asprintf(&name, "%s/bin/bash", rootdir) == -1) | 950 | // if (asprintf(&name, "%s/bin/bash", rootdir) == -1) |
1032 | errExit("asprintf"); | 951 | // errExit("asprintf"); |
1033 | if (stat(name, &s) == -1) { | 952 | // if (stat(name, &s) == -1) { |
1034 | fprintf(stderr, "Error: cannot find /bin/bash in chroot directory\n"); | 953 | // fprintf(stderr, "Error: cannot find /bin/bash in chroot directory\n"); |
1035 | return 1; | 954 | // return 1; |
955 | // } | ||
956 | // free(name); | ||
957 | |||
958 | // check x11 socket directory | ||
959 | if (getenv("FIREJAIL_X11")) { | ||
960 | mask_x11_abstract_socket = 1; | ||
961 | char *name; | ||
962 | if (asprintf(&name, "%s/tmp/.X11-unix", rootdir) == -1) | ||
963 | errExit("asprintf"); | ||
964 | if (stat(name, &s) == -1) { | ||
965 | fprintf(stderr, "Error: cannot find /tmp/.X11-unix in chroot directory\n"); | ||
966 | return 1; | ||
967 | } | ||
968 | free(name); | ||
1036 | } | 969 | } |
1037 | free(name); | 970 | |
1038 | |||
1039 | return 0; | 971 | return 0; |
1040 | } | 972 | } |
1041 | 973 | ||
@@ -1043,77 +975,108 @@ int fs_check_chroot_dir(const char *rootdir) { | |||
1043 | void fs_chroot(const char *rootdir) { | 975 | void fs_chroot(const char *rootdir) { |
1044 | assert(rootdir); | 976 | assert(rootdir); |
1045 | 977 | ||
1046 | //*********************************** | 978 | if (checkcfg(CFG_CHROOT_DESKTOP)) { |
1047 | // mount-bind a /dev in rootdir | 979 | // mount-bind a /dev in rootdir |
1048 | //*********************************** | 980 | char *newdev; |
1049 | // mount /dev | 981 | if (asprintf(&newdev, "%s/dev", rootdir) == -1) |
1050 | char *newdev; | 982 | errExit("asprintf"); |
1051 | if (asprintf(&newdev, "%s/dev", rootdir) == -1) | 983 | if (arg_debug) |
1052 | errExit("asprintf"); | 984 | printf("Mounting /dev on %s\n", newdev); |
1053 | if (arg_debug) | 985 | if (mount("/dev", newdev, NULL, MS_BIND|MS_REC, NULL) < 0) |
1054 | printf("Mounting /dev on %s\n", newdev); | 986 | errExit("mounting /dev"); |
1055 | if (mount("/dev", newdev, NULL, MS_BIND|MS_REC, NULL) < 0) | 987 | free(newdev); |
1056 | errExit("mounting /dev"); | 988 | |
1057 | 989 | // x11 | |
1058 | // some older distros don't have a /run directory | 990 | if (getenv("FIREJAIL_X11")) { |
1059 | // create one by default | 991 | mask_x11_abstract_socket = 1; |
1060 | // no exit on error, let the user deal with any problems | 992 | char *newx11; |
1061 | char *rundir; | 993 | if (asprintf(&newx11, "%s/tmp/.X11-unix", rootdir) == -1) |
1062 | if (asprintf(&rundir, "%s/run", rootdir) == -1) | 994 | errExit("asprintf"); |
1063 | errExit("asprintf"); | 995 | if (arg_debug) |
1064 | if (!is_dir(rundir)) { | 996 | printf("Mounting /tmp/.X11-unix on %s\n", newx11); |
1065 | int rv = mkdir(rundir, 0755); | 997 | if (mount("/tmp/.X11-unix", newx11, NULL, MS_BIND|MS_REC, NULL) < 0) |
1066 | (void) rv; | 998 | errExit("mounting /tmp/.X11-unix"); |
1067 | rv = chown(rundir, 0, 0); | 999 | free(newx11); |
1068 | (void) rv; | 1000 | } |
1001 | |||
1002 | // create /run/firejail directory in chroot | ||
1003 | char *rundir; | ||
1004 | if (asprintf(&rundir, "%s/run", rootdir) == -1) | ||
1005 | errExit("asprintf"); | ||
1006 | create_empty_dir_as_root(rundir, 0755); | ||
1007 | free(rundir); | ||
1008 | if (asprintf(&rundir, "%s/run/firejail", rootdir) == -1) | ||
1009 | errExit("asprintf"); | ||
1010 | create_empty_dir_as_root(rundir, 0755); | ||
1011 | free(rundir); | ||
1012 | |||
1013 | // create /run/firejail/mnt directory in chroot and mount a tmpfs | ||
1014 | if (asprintf(&rundir, "%s/run/firejail/mnt", rootdir) == -1) | ||
1015 | errExit("asprintf"); | ||
1016 | create_empty_dir_as_root(rundir, 0755); | ||
1017 | if (mount("tmpfs", rundir, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | ||
1018 | errExit("mounting /run/firejail/mnt"); | ||
1019 | fs_logger2("tmpfs", RUN_MNT_DIR); | ||
1020 | free(rundir); | ||
1021 | |||
1022 | // retrieve seccomp.protocol | ||
1023 | struct stat s; | ||
1024 | if (stat(RUN_SECCOMP_PROTOCOL, &s) == 0) { | ||
1025 | if (asprintf(&rundir, "%s%s", rootdir, RUN_SECCOMP_PROTOCOL) == -1) | ||
1026 | errExit("asprintf"); | ||
1027 | copy_file(RUN_SECCOMP_PROTOCOL, rundir, getuid(), getgid(), 0644); | ||
1028 | free(rundir); | ||
1029 | } | ||
1030 | |||
1031 | // copy /etc/resolv.conf in chroot directory | ||
1032 | // if resolv.conf in chroot is a symbolic link, this will fail | ||
1033 | // no exit on error, let the user deal with the problem | ||
1034 | char *fname; | ||
1035 | if (asprintf(&fname, "%s/etc/resolv.conf", rootdir) == -1) | ||
1036 | errExit("asprintf"); | ||
1037 | if (arg_debug) | ||
1038 | printf("Updating /etc/resolv.conf in %s\n", fname); | ||
1039 | if (is_link(fname)) { | ||
1040 | fprintf(stderr, "Error: invalid %s file\n", fname); | ||
1041 | exit(1); | ||
1042 | } | ||
1043 | if (copy_file("/etc/resolv.conf", fname, 0, 0, 0644) == -1) | ||
1044 | fprintf(stderr, "Warning: /etc/resolv.conf not initialized\n"); | ||
1069 | } | 1045 | } |
1070 | 1046 | ||
1071 | // copy /etc/resolv.conf in chroot directory | ||
1072 | // if resolv.conf in chroot is a symbolic link, this will fail | ||
1073 | // no exit on error, let the user deal with the problem | ||
1074 | char *fname; | ||
1075 | if (asprintf(&fname, "%s/etc/resolv.conf", rootdir) == -1) | ||
1076 | errExit("asprintf"); | ||
1077 | if (arg_debug) | ||
1078 | printf("Updating /etc/resolv.conf in %s\n", fname); | ||
1079 | if (is_link(fname)) { | ||
1080 | fprintf(stderr, "Error: invalid %s file\n", fname); | ||
1081 | exit(1); | ||
1082 | } | ||
1083 | if (copy_file("/etc/resolv.conf", fname) == -1) | ||
1084 | fprintf(stderr, "Warning: /etc/resolv.conf not initialized\n"); | ||
1085 | |||
1086 | // chroot into the new directory | 1047 | // chroot into the new directory |
1048 | #ifdef HAVE_GCOV | ||
1049 | __gcov_flush(); | ||
1050 | #endif | ||
1087 | if (arg_debug) | 1051 | if (arg_debug) |
1088 | printf("Chrooting into %s\n", rootdir); | 1052 | printf("Chrooting into %s\n", rootdir); |
1089 | if (chroot(rootdir) < 0) | 1053 | if (chroot(rootdir) < 0) |
1090 | errExit("chroot"); | 1054 | errExit("chroot"); |
1091 | // mount a new tmpfs in /run/firejail/mnt - the old one was lost in chroot | ||
1092 | fs_build_remount_mnt_dir(); | ||
1093 | |||
1094 | // update /var directory in order to support multiple sandboxes running on the same root directory | ||
1095 | if (!arg_private_dev) | ||
1096 | fs_dev_shm(); | ||
1097 | fs_var_lock(); | ||
1098 | fs_var_tmp(); | ||
1099 | fs_var_log(); | ||
1100 | fs_var_lib(); | ||
1101 | fs_var_cache(); | ||
1102 | fs_var_utmp(); | ||
1103 | 1055 | ||
1104 | // don't leak user information | 1056 | // create all other /run/firejail files and directories |
1105 | restrict_users(); | 1057 | preproc_build_firejail_dir(); |
1106 | 1058 | ||
1107 | disable_firejail_config(); | 1059 | if (checkcfg(CFG_CHROOT_DESKTOP)) { |
1060 | // update /var directory in order to support multiple sandboxes running on the same root directory | ||
1061 | // if (!arg_private_dev) | ||
1062 | // fs_dev_shm(); | ||
1063 | fs_var_lock(); | ||
1064 | fs_var_tmp(); | ||
1065 | fs_var_log(); | ||
1066 | fs_var_lib(); | ||
1067 | fs_var_cache(); | ||
1068 | fs_var_utmp(); | ||
1069 | |||
1070 | // don't leak user information | ||
1071 | restrict_users(); | ||
1072 | |||
1073 | // when starting as root, firejail config is not disabled; | ||
1074 | // this mode could be used to install and test new software by chaining | ||
1075 | // firejail sandboxes (firejail --force) | ||
1076 | if (getuid() != 0) | ||
1077 | disable_config(); | ||
1078 | } | ||
1108 | } | 1079 | } |
1109 | #endif | 1080 | #endif |
1110 | 1081 | ||
1111 | void fs_private_tmp(void) { | ||
1112 | // mount tmpfs on top of /run/firejail/mnt | ||
1113 | if (arg_debug) | ||
1114 | printf("Mounting tmpfs on /tmp directory\n"); | ||
1115 | if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=1777,gid=0") < 0) | ||
1116 | errExit("mounting /tmp/firejail/mnt"); | ||
1117 | fs_logger2("tmpfs", "/tmp"); | ||
1118 | } | ||
1119 | 1082 | ||