diff options
Diffstat (limited to 'src/firejail/fs.c')
-rw-r--r-- | src/firejail/fs.c | 825 |
1 files changed, 825 insertions, 0 deletions
diff --git a/src/firejail/fs.c b/src/firejail/fs.c new file mode 100644 index 000000000..1fc1c0942 --- /dev/null +++ b/src/firejail/fs.c | |||
@@ -0,0 +1,825 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014, 2015 netblue30 (netblue30@yahoo.com) | ||
3 | * | ||
4 | * This file is part of firejail project | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | */ | ||
20 | #include "firejail.h" | ||
21 | #include <sys/mount.h> | ||
22 | #include <sys/stat.h> | ||
23 | #include <linux/limits.h> | ||
24 | #include <glob.h> | ||
25 | #include <dirent.h> | ||
26 | #include <fcntl.h> | ||
27 | #include <errno.h> | ||
28 | |||
29 | // build /tmp/firejail directory | ||
30 | void fs_build_firejail_dir(void) { | ||
31 | struct stat s; | ||
32 | |||
33 | if (stat(FIREJAIL_DIR, &s)) { | ||
34 | if (arg_debug) | ||
35 | printf("Creating %s directory\n", FIREJAIL_DIR); | ||
36 | /* coverity[toctou] */ | ||
37 | int rv = mkdir(FIREJAIL_DIR, S_IRWXU | S_IRWXG | S_IRWXO); | ||
38 | if (rv == -1) | ||
39 | errExit("mkdir"); | ||
40 | if (chown(FIREJAIL_DIR, 0, 0) < 0) | ||
41 | errExit("chown"); | ||
42 | if (chmod(FIREJAIL_DIR, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) | ||
43 | errExit("chmod"); | ||
44 | } | ||
45 | else { // check /tmp/firejail directory belongs to root end exit if doesn't! | ||
46 | if (s.st_uid != 0 || s.st_gid != 0) { | ||
47 | fprintf(stderr, "Error: non-root %s directory, exiting...\n", FIREJAIL_DIR); | ||
48 | exit(1); | ||
49 | } | ||
50 | } | ||
51 | } | ||
52 | |||
53 | |||
54 | // build /tmp/firejail/mnt directory | ||
55 | static int tmpfs_mounted = 0; | ||
56 | void fs_build_mnt_dir(void) { | ||
57 | struct stat s; | ||
58 | fs_build_firejail_dir(); | ||
59 | |||
60 | // create /tmp/firejail directory | ||
61 | if (stat(MNT_DIR, &s)) { | ||
62 | if (arg_debug) | ||
63 | printf("Creating %s directory\n", MNT_DIR); | ||
64 | /* coverity[toctou] */ | ||
65 | int rv = mkdir(MNT_DIR, S_IRWXU | S_IRWXG | S_IRWXO); | ||
66 | if (rv == -1) | ||
67 | errExit("mkdir"); | ||
68 | if (chown(MNT_DIR, 0, 0) < 0) | ||
69 | errExit("chown"); | ||
70 | if (chmod(MNT_DIR, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) | ||
71 | errExit("chmod"); | ||
72 | } | ||
73 | |||
74 | // ... and mount tmpfs on top of it | ||
75 | if (!tmpfs_mounted) { | ||
76 | // mount tmpfs on top of /tmp/firejail/mnt | ||
77 | if (arg_debug) | ||
78 | printf("Mounting tmpfs on %s directory\n", MNT_DIR); | ||
79 | if (mount("tmpfs", MNT_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | ||
80 | errExit("mounting /tmp/firejail/mnt"); | ||
81 | tmpfs_mounted = 1; | ||
82 | } | ||
83 | } | ||
84 | |||
85 | // build /tmp/firejail/overlay directory | ||
86 | void fs_build_overlay_dir(void) { | ||
87 | struct stat s; | ||
88 | fs_build_firejail_dir(); | ||
89 | |||
90 | // create /tmp/firejail directory | ||
91 | if (stat(OVERLAY_DIR, &s)) { | ||
92 | if (arg_debug) | ||
93 | printf("Creating %s directory\n", MNT_DIR); | ||
94 | /* coverity[toctou] */ | ||
95 | int rv = mkdir(OVERLAY_DIR, S_IRWXU | S_IRWXG | S_IRWXO); | ||
96 | if (rv == -1) | ||
97 | errExit("mkdir"); | ||
98 | if (chown(OVERLAY_DIR, 0, 0) < 0) | ||
99 | errExit("chown"); | ||
100 | if (chmod(OVERLAY_DIR, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) | ||
101 | errExit("chmod"); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | |||
106 | |||
107 | |||
108 | |||
109 | //*********************************************** | ||
110 | // process profile file | ||
111 | //*********************************************** | ||
112 | typedef enum { | ||
113 | BLACKLIST_FILE, | ||
114 | MOUNT_READONLY, | ||
115 | MOUNT_TMPFS, | ||
116 | OPERATION_MAX | ||
117 | } OPERATION; | ||
118 | |||
119 | |||
120 | static char *create_empty_dir(void) { | ||
121 | struct stat s; | ||
122 | fs_build_firejail_dir(); | ||
123 | |||
124 | if (stat(RO_DIR, &s)) { | ||
125 | /* coverity[toctou] */ | ||
126 | int rv = mkdir(RO_DIR, S_IRUSR | S_IXUSR); | ||
127 | if (rv == -1) | ||
128 | errExit("mkdir"); | ||
129 | if (chown(RO_DIR, 0, 0) < 0) | ||
130 | errExit("chown"); | ||
131 | } | ||
132 | |||
133 | return RO_DIR; | ||
134 | } | ||
135 | |||
136 | static char *create_empty_file(void) { | ||
137 | struct stat s; | ||
138 | fs_build_firejail_dir(); | ||
139 | |||
140 | if (stat(RO_FILE, &s)) { | ||
141 | /* coverity[toctou] */ | ||
142 | FILE *fp = fopen(RO_FILE, "w"); | ||
143 | if (!fp) | ||
144 | errExit("fopen"); | ||
145 | fclose(fp); | ||
146 | if (chown(RO_FILE, 0, 0) < 0) | ||
147 | errExit("chown"); | ||
148 | if (chmod(RO_FILE, S_IRUSR) < 0) | ||
149 | errExit("chown"); | ||
150 | } | ||
151 | |||
152 | return RO_FILE; | ||
153 | } | ||
154 | |||
155 | static void disable_file(OPERATION op, const char *fname, const char *emptydir, const char *emptyfile) { | ||
156 | assert(fname); | ||
157 | assert(emptydir); | ||
158 | assert(emptyfile); | ||
159 | assert(op <OPERATION_MAX); | ||
160 | |||
161 | // if the file is a link, follow the link | ||
162 | char *lnk = NULL; | ||
163 | if (is_link(fname)) { | ||
164 | lnk = get_link(fname); | ||
165 | if (lnk) | ||
166 | fname = lnk; | ||
167 | else | ||
168 | fprintf(stderr, "Warning: cannot follow link %s, skipping...\n", fname); | ||
169 | } | ||
170 | |||
171 | // if the file is not present, do nothing | ||
172 | struct stat s; | ||
173 | if (stat(fname, &s) == -1) { | ||
174 | if (lnk) | ||
175 | free(lnk); | ||
176 | return; | ||
177 | } | ||
178 | |||
179 | // modify the file | ||
180 | if (op == BLACKLIST_FILE) { | ||
181 | if (arg_debug) | ||
182 | printf("Disable %s\n", fname); | ||
183 | if (S_ISDIR(s.st_mode)) { | ||
184 | if (mount(emptydir, fname, "none", MS_BIND, "mode=400,gid=0") < 0) | ||
185 | errExit("disable file"); | ||
186 | } | ||
187 | else { | ||
188 | if (mount(emptyfile, fname, "none", MS_BIND, "mode=400,gid=0") < 0) | ||
189 | errExit("disable file"); | ||
190 | } | ||
191 | } | ||
192 | else if (op == MOUNT_READONLY) { | ||
193 | if (arg_debug) | ||
194 | printf("Mounting read-only %s\n", fname); | ||
195 | fs_rdonly(fname); | ||
196 | } | ||
197 | else if (op == MOUNT_TMPFS) { | ||
198 | if (S_ISDIR(s.st_mode)) { | ||
199 | if (arg_debug) | ||
200 | printf("Mounting tmpfs on %s\n", fname); | ||
201 | // preserve owner and mode for the directory | ||
202 | if (mount("tmpfs", fname, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, 0) < 0) | ||
203 | errExit("mounting tmpfs"); | ||
204 | /* coverity[toctou] */ | ||
205 | if (chown(fname, s.st_uid, s.st_gid) == -1) | ||
206 | errExit("mounting tmpfs chmod"); | ||
207 | } | ||
208 | else | ||
209 | printf("Warning: %s is not a directory; cannot mount a tmpfs on top of it.\n", fname); | ||
210 | } | ||
211 | else | ||
212 | assert(0); | ||
213 | |||
214 | if (lnk) | ||
215 | free(lnk); | ||
216 | } | ||
217 | |||
218 | static void globbing(OPERATION op, const char *fname, const char *emptydir, const char *emptyfile) { | ||
219 | assert(fname); | ||
220 | assert(emptydir); | ||
221 | assert(emptyfile); | ||
222 | |||
223 | // filename globbing: expand * macro and continue processing for every single file | ||
224 | if (strchr(fname, '*')) { | ||
225 | glob_t globbuf; | ||
226 | globbuf.gl_offs = 0; | ||
227 | glob(fname, GLOB_DOOFFS, NULL, &globbuf); | ||
228 | int i; | ||
229 | for (i = 0; i < globbuf.gl_pathc; i++) { | ||
230 | assert(globbuf.gl_pathv[i]); | ||
231 | disable_file(op, globbuf.gl_pathv[i], emptydir, emptyfile); | ||
232 | } | ||
233 | } | ||
234 | else | ||
235 | disable_file(op, fname, emptydir, emptyfile); | ||
236 | } | ||
237 | |||
238 | static void expand_path(OPERATION op, const char *path, const char *fname, const char *emptydir, const char *emptyfile) { | ||
239 | assert(path); | ||
240 | assert(fname); | ||
241 | assert(emptydir); | ||
242 | assert(emptyfile); | ||
243 | char newname[strlen(path) + strlen(fname) + 1]; | ||
244 | sprintf(newname, "%s%s", path, fname); | ||
245 | |||
246 | globbing(op, newname, emptydir, emptyfile); | ||
247 | } | ||
248 | |||
249 | // blacklist files or directoies by mounting empty files on top of them | ||
250 | void fs_blacklist(const char *homedir) { | ||
251 | ProfileEntry *entry = cfg.profile; | ||
252 | if (!entry) | ||
253 | return; | ||
254 | |||
255 | char *emptydir = create_empty_dir(); | ||
256 | char *emptyfile = create_empty_file(); | ||
257 | |||
258 | while (entry) { | ||
259 | OPERATION op = OPERATION_MAX; | ||
260 | char *ptr; | ||
261 | |||
262 | // process blacklist command | ||
263 | if (strncmp(entry->data, "bind", 4) == 0) { | ||
264 | char *dname1 = entry->data + 5; | ||
265 | char *dname2 = split_comma(dname1); | ||
266 | if (dname2 == NULL) { | ||
267 | fprintf(stderr, "Error: second directory missing in bind command\n"); | ||
268 | entry = entry->next; | ||
269 | continue; | ||
270 | } | ||
271 | struct stat s; | ||
272 | if (stat(dname1, &s) == -1) { | ||
273 | fprintf(stderr, "Error: cannot find directories for bind command\n"); | ||
274 | entry = entry->next; | ||
275 | continue; | ||
276 | } | ||
277 | if (stat(dname2, &s) == -1) { | ||
278 | fprintf(stderr, "Error: cannot find directories for bind command\n"); | ||
279 | entry = entry->next; | ||
280 | continue; | ||
281 | } | ||
282 | |||
283 | // mount --bind olddir newdir | ||
284 | if (arg_debug) | ||
285 | printf("Mount-bind %s on top of %s\n", dname1, dname2); | ||
286 | // preserve dname2 mode and ownership | ||
287 | if (mount(dname1, dname2, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
288 | errExit("mount bind"); | ||
289 | /* coverity[toctou] */ | ||
290 | if (chown(dname2, s.st_uid, s.st_gid) == -1) | ||
291 | errExit("mount-bind chown"); | ||
292 | /* coverity[toctou] */ | ||
293 | if (chmod(dname2, s.st_mode) == -1) | ||
294 | errExit("mount-bind chmod"); | ||
295 | |||
296 | entry = entry->next; | ||
297 | continue; | ||
298 | } | ||
299 | |||
300 | // process blacklist command | ||
301 | if (strncmp(entry->data, "blacklist", 9) == 0) { | ||
302 | ptr = entry->data + 10; | ||
303 | op = BLACKLIST_FILE; | ||
304 | } | ||
305 | else if (strncmp(entry->data, "read-only", 9) == 0) { | ||
306 | ptr = entry->data + 10; | ||
307 | op = MOUNT_READONLY; | ||
308 | } | ||
309 | else if (strncmp(entry->data, "tmpfs", 5) == 0) { | ||
310 | ptr = entry->data + 6; | ||
311 | op = MOUNT_TMPFS; | ||
312 | } | ||
313 | else { | ||
314 | fprintf(stderr, "Error: invalid profile line %s\n", entry->data); | ||
315 | entry = entry->next; | ||
316 | continue; | ||
317 | } | ||
318 | |||
319 | // replace home macro in blacklist array | ||
320 | char *new_name = NULL; | ||
321 | if (strncmp(ptr, "${HOME}", 7) == 0) { | ||
322 | if (asprintf(&new_name, "%s%s", homedir, ptr + 7) == -1) | ||
323 | errExit("asprintf"); | ||
324 | ptr = new_name; | ||
325 | } | ||
326 | |||
327 | // expand path macro - look for the file in /bin, /usr/bin, /sbin and /usr/sbin directories | ||
328 | if (strncmp(ptr, "${PATH}", 7) == 0) { | ||
329 | expand_path(op, "/bin", ptr + 7, emptydir, emptyfile); | ||
330 | expand_path(op, "/sbin", ptr + 7, emptydir, emptyfile); | ||
331 | expand_path(op, "/usr/bin", ptr + 7, emptydir, emptyfile); | ||
332 | expand_path(op, "/usr/sbin", ptr + 7, emptydir, emptyfile); | ||
333 | } | ||
334 | else | ||
335 | globbing(op, ptr, emptydir, emptyfile); | ||
336 | |||
337 | if (new_name) | ||
338 | free(new_name); | ||
339 | entry = entry->next; | ||
340 | } | ||
341 | } | ||
342 | |||
343 | //*********************************************** | ||
344 | // mount namespace | ||
345 | //*********************************************** | ||
346 | |||
347 | // remount a directory read-only | ||
348 | void fs_rdonly(const char *dir) { | ||
349 | assert(dir); | ||
350 | // check directory exists | ||
351 | struct stat s; | ||
352 | int rv = stat(dir, &s); | ||
353 | if (rv == 0) { | ||
354 | // mount --bind /bin /bin | ||
355 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
356 | errExit("mount read-only"); | ||
357 | // mount --bind -o remount,ro /bin | ||
358 | if (mount(NULL, dir, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_REC, NULL) < 0) | ||
359 | errExit("mount read-only"); | ||
360 | } | ||
361 | } | ||
362 | void fs_rdonly_noexit(const char *dir) { | ||
363 | assert(dir); | ||
364 | // check directory exists | ||
365 | struct stat s; | ||
366 | int rv = stat(dir, &s); | ||
367 | if (rv == 0) { | ||
368 | int merr = 0; | ||
369 | // mount --bind /bin /bin | ||
370 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
371 | merr = 1; | ||
372 | // mount --bind -o remount,ro /bin | ||
373 | if (mount(NULL, dir, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_REC, NULL) < 0) | ||
374 | merr = 1; | ||
375 | if (merr) | ||
376 | fprintf(stderr, "Warning: cannot mount %s read-only\n", dir); | ||
377 | } | ||
378 | } | ||
379 | |||
380 | // mount /proc and /sys directories | ||
381 | void fs_proc_sys_dev_boot(void) { | ||
382 | struct stat s; | ||
383 | |||
384 | if (arg_debug) | ||
385 | printf("Remounting /proc and /proc/sys filesystems\n"); | ||
386 | if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) | ||
387 | errExit("mounting /proc"); | ||
388 | |||
389 | // remount /proc/sys readonly | ||
390 | if (mount("/proc/sys", "/proc/sys", NULL, MS_BIND | MS_REC, NULL) < 0) | ||
391 | errExit("mounting /proc/sys"); | ||
392 | |||
393 | if (mount(NULL, "/proc/sys", NULL, MS_BIND | MS_REMOUNT | MS_RDONLY | MS_REC, NULL) < 0) | ||
394 | errExit("mounting /proc/sys"); | ||
395 | |||
396 | |||
397 | /* Mount a version of /sys that describes the network namespace */ | ||
398 | if (arg_debug) | ||
399 | printf("Remounting /sys directory\n"); | ||
400 | if (umount2("/sys", MNT_DETACH) < 0) | ||
401 | fprintf(stderr, "Warning: failed to unmount /sys\n"); | ||
402 | if (mount("sysfs", "/sys", "sysfs", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REC, NULL) < 0) | ||
403 | fprintf(stderr, "Warning: failed to mount /sys\n"); | ||
404 | |||
405 | // if (mount("sysfs", "/sys", "sysfs", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REC, NULL) < 0) | ||
406 | // errExit("mounting /sys"); | ||
407 | |||
408 | |||
409 | // mounting firejail kernel module files | ||
410 | if (stat("/proc/firejail-uptime", &s) == 0) { | ||
411 | errno = 0; | ||
412 | FILE *fp = fopen("/proc/firejail", "w"); | ||
413 | int cnt = 0; | ||
414 | while (errno == EBUSY && cnt < 10) { | ||
415 | if (!fp) { | ||
416 | int s = random(); | ||
417 | s /= 200000; | ||
418 | usleep(s); | ||
419 | fp = fopen("/proc/firejail", "w"); | ||
420 | } | ||
421 | else | ||
422 | break; | ||
423 | } | ||
424 | if (!fp) { | ||
425 | fprintf(stderr, "Error: cannot register sandbox with firejail-lkm\n"); | ||
426 | exit(1); | ||
427 | } | ||
428 | if (fp) { | ||
429 | // registration | ||
430 | fprintf(fp, "register\n"); | ||
431 | fflush(0); | ||
432 | // filtering x11 connect calls | ||
433 | if (arg_nox11) { | ||
434 | fprintf(fp, "no connect unix /tmp/.X11\n"); | ||
435 | fflush(0); | ||
436 | printf("X11 access disabled\n"); | ||
437 | } | ||
438 | if (arg_nodbus) { | ||
439 | fprintf(fp, "no connect unix /var/run/dbus/system_bus_socket\n"); | ||
440 | fflush(0); | ||
441 | fprintf(fp, "no connect unix /tmp/dbus\n"); | ||
442 | fflush(0); | ||
443 | printf("D-Bus access disabled\n"); | ||
444 | } | ||
445 | fclose(fp); | ||
446 | if (mount("/proc/firejail-uptime", "/proc/uptime", NULL, MS_BIND|MS_REC, NULL) < 0) | ||
447 | fprintf(stderr, "Warning: cannot mount /proc/firejail-uptime\n"); | ||
448 | } | ||
449 | } | ||
450 | |||
451 | // Disable SysRq | ||
452 | // a linux box can be shut down easily using the following commands (as root): | ||
453 | // # echo 1 > /proc/sys/kernel/sysrq | ||
454 | // #echo b > /proc/sysrq-trigger | ||
455 | // for more information see https://www.kernel.org/doc/Documentation/sysrq.txt | ||
456 | if (arg_debug) | ||
457 | printf("Disable /proc/sysrq-trigger\n"); | ||
458 | fs_rdonly_noexit("/proc/sysrq-trigger"); | ||
459 | |||
460 | // disable hotplug and uevent_helper | ||
461 | if (arg_debug) | ||
462 | printf("Disable /proc/sys/kernel/hotplug\n"); | ||
463 | fs_rdonly_noexit("/proc/sys/kernel/hotplug"); | ||
464 | if (arg_debug) | ||
465 | printf("Disable /sys/kernel/uevent_helper\n"); | ||
466 | fs_rdonly_noexit("/sys/kernel/uevent_helper"); | ||
467 | |||
468 | // read-only /proc/irq and /proc/bus | ||
469 | if (arg_debug) | ||
470 | printf("Disable /proc/irq\n"); | ||
471 | fs_rdonly_noexit("/proc/irq"); | ||
472 | if (arg_debug) | ||
473 | printf("Disable /proc/bus\n"); | ||
474 | fs_rdonly_noexit("/proc/bus"); | ||
475 | |||
476 | // disable /proc/kcore | ||
477 | disable_file(BLACKLIST_FILE, "/proc/kcore", "not used", "/dev/null"); | ||
478 | |||
479 | // disable /proc/kallsyms | ||
480 | disable_file(BLACKLIST_FILE, "/proc/kallsyms", "not used", "/dev/null"); | ||
481 | |||
482 | // disable /boot | ||
483 | if (stat("/boot", &s) == 0) { | ||
484 | if (arg_debug) | ||
485 | printf("Mounting a new /boot directory\n"); | ||
486 | if (mount("tmpfs", "/boot", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0) | ||
487 | errExit("mounting /boot directory"); | ||
488 | } | ||
489 | |||
490 | // disable /dev/port | ||
491 | if (stat("/dev/port", &s) == 0) { | ||
492 | disable_file(BLACKLIST_FILE, "/dev/port", "not used", "/dev/null"); | ||
493 | } | ||
494 | } | ||
495 | |||
496 | static void sanitize_home(void) { | ||
497 | // extract current /home directory data | ||
498 | struct dirent *dir; | ||
499 | DIR *d = opendir("/home"); | ||
500 | if (d == NULL) | ||
501 | return; | ||
502 | |||
503 | char *emptydir = create_empty_dir(); | ||
504 | while ((dir = readdir(d))) { | ||
505 | if(strcmp(dir->d_name, "." ) == 0 || strcmp(dir->d_name, ".." ) == 0) | ||
506 | continue; | ||
507 | |||
508 | if (dir->d_type == DT_DIR ) { | ||
509 | // get properties | ||
510 | struct stat s; | ||
511 | char *name; | ||
512 | if (asprintf(&name, "/home/%s", dir->d_name) == -1) | ||
513 | continue; | ||
514 | if (stat(name, &s) == -1) | ||
515 | continue; | ||
516 | if (S_ISLNK(s.st_mode)) { | ||
517 | free(name); | ||
518 | continue; | ||
519 | } | ||
520 | |||
521 | if (strcmp(name, cfg.homedir) == 0) | ||
522 | continue; | ||
523 | |||
524 | // printf("directory %u %u:%u #%s#\n", | ||
525 | // s.st_mode, | ||
526 | // s.st_uid, | ||
527 | // s.st_gid, | ||
528 | // name); | ||
529 | |||
530 | // disable directory | ||
531 | disable_file(BLACKLIST_FILE, name, emptydir, "not used"); | ||
532 | free(name); | ||
533 | } | ||
534 | } | ||
535 | closedir(d); | ||
536 | } | ||
537 | |||
538 | |||
539 | |||
540 | |||
541 | |||
542 | |||
543 | // build a basic read-only filesystem | ||
544 | void fs_basic_fs(void) { | ||
545 | if (arg_debug) | ||
546 | printf("Mounting read-only /bin, /sbin, /lib, /lib64, /usr, /etc, /var\n"); | ||
547 | fs_rdonly("/bin"); | ||
548 | fs_rdonly("/sbin"); | ||
549 | fs_rdonly("/lib"); | ||
550 | fs_rdonly("/lib64"); | ||
551 | fs_rdonly("/usr"); | ||
552 | fs_rdonly("/etc"); | ||
553 | fs_rdonly("/var"); | ||
554 | |||
555 | // update /var directory in order to support multiple sandboxes running on the same root directory | ||
556 | if (!arg_private_dev) | ||
557 | fs_dev_shm(); | ||
558 | fs_var_lock(); | ||
559 | fs_var_tmp(); | ||
560 | fs_var_log(); | ||
561 | fs_var_lib(); | ||
562 | fs_var_cache(); | ||
563 | fs_var_utmp(); | ||
564 | |||
565 | // only in user mode | ||
566 | if (getuid()) | ||
567 | sanitize_home(); | ||
568 | } | ||
569 | |||
570 | |||
571 | // mount overlayfs on top of / directory | ||
572 | // mounting an overlay and chrooting into it: | ||
573 | // | ||
574 | // Old Ubuntu kernel | ||
575 | // # cd ~ | ||
576 | // # mkdir -p overlay/root | ||
577 | // # mkdir -p overlay/diff | ||
578 | // # mount -t overlayfs -o lowerdir=/,upperdir=/root/overlay/diff overlayfs /root/overlay/root | ||
579 | // # chroot /root/overlay/root | ||
580 | // to shutdown, first exit the chroot and then unmount the overlay | ||
581 | // # exit | ||
582 | // # umount /root/overlay/root | ||
583 | // | ||
584 | // Kernels 3.18+ | ||
585 | // # cd ~ | ||
586 | // # mkdir -p overlay/root | ||
587 | // # mkdir -p overlay/diff | ||
588 | // # mkdir -p overlay/work | ||
589 | // # mount -t overlay -o lowerdir=/,upperdir=/root/overlay/diff,workdir=/root/overlay/work overlay /root/overlay/root | ||
590 | // # cat /etc/mtab | grep overlay | ||
591 | // /root/overlay /root/overlay/root overlay rw,relatime,lowerdir=/,upperdir=/root/overlay/diff,workdir=/root/overlay/work 0 0 | ||
592 | // # chroot /root/overlay/root | ||
593 | // to shutdown, first exit the chroot and then unmount the overlay | ||
594 | // # exit | ||
595 | // # umount /root/overlay/root | ||
596 | |||
597 | |||
598 | // to do: fix the code below; also, it might work without /dev; impose seccomp/caps filters when not root | ||
599 | #include <sys/utsname.h> | ||
600 | void fs_overlayfs(void) { | ||
601 | // check kernel version | ||
602 | struct utsname u; | ||
603 | int rv = uname(&u); | ||
604 | if (rv != 0) | ||
605 | errExit("uname"); | ||
606 | int major; | ||
607 | int minor; | ||
608 | if (2 != sscanf(u.release, "%d.%d", &major, &minor)) { | ||
609 | fprintf(stderr, "Error: cannot extract Linux kernel version: %s\n", u.version); | ||
610 | exit(1); | ||
611 | } | ||
612 | |||
613 | if (arg_debug) | ||
614 | printf("Linux kernel version %d.%d\n", major, minor); | ||
615 | int oldkernel = 0; | ||
616 | if (major < 3) { | ||
617 | fprintf(stderr, "Error: minimum kernel version required 3.x\n"); | ||
618 | exit(1); | ||
619 | } | ||
620 | if (major == 3 && minor < 18) | ||
621 | oldkernel = 1; | ||
622 | |||
623 | // build overlay directories | ||
624 | fs_build_mnt_dir(); | ||
625 | |||
626 | char *oroot; | ||
627 | if(asprintf(&oroot, "%s/oroot", MNT_DIR) == -1) | ||
628 | errExit("asprintf"); | ||
629 | if (mkdir(oroot, S_IRWXU | S_IRWXG | S_IRWXO)) | ||
630 | errExit("mkdir"); | ||
631 | if (chown(oroot, 0, 0) < 0) | ||
632 | errExit("chown"); | ||
633 | if (chmod(oroot, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) | ||
634 | errExit("chmod"); | ||
635 | |||
636 | char *odiff; | ||
637 | if(asprintf(&odiff, "%s/odiff", MNT_DIR) == -1) | ||
638 | errExit("asprintf"); | ||
639 | if (mkdir(odiff, S_IRWXU | S_IRWXG | S_IRWXO)) | ||
640 | errExit("mkdir"); | ||
641 | if (chown(odiff, 0, 0) < 0) | ||
642 | errExit("chown"); | ||
643 | if (chmod(odiff, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) | ||
644 | errExit("chmod"); | ||
645 | |||
646 | char *owork; | ||
647 | if(asprintf(&owork, "%s/owork", MNT_DIR) == -1) | ||
648 | errExit("asprintf"); | ||
649 | if (mkdir(owork, S_IRWXU | S_IRWXG | S_IRWXO)) | ||
650 | errExit("mkdir"); | ||
651 | if (chown(owork, 0, 0) < 0) | ||
652 | errExit("chown"); | ||
653 | if (chmod(owork, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) | ||
654 | errExit("chmod"); | ||
655 | |||
656 | // mount overlayfs | ||
657 | if (arg_debug) | ||
658 | printf("Mounting OverlayFS\n"); | ||
659 | char *option; | ||
660 | if (oldkernel) { // old Ubuntu/OpenSUSE kernels | ||
661 | if (asprintf(&option, "lowerdir=/,upperdir=%s", odiff) == -1) | ||
662 | errExit("asprintf"); | ||
663 | if (mount("overlayfs", oroot, "overlayfs", MS_MGC_VAL, option) < 0) | ||
664 | errExit("mounting overlayfs"); | ||
665 | } | ||
666 | else { // kernel 3.18 or newer | ||
667 | if (asprintf(&option, "lowerdir=/,upperdir=%s,workdir=%s", odiff, owork) == -1) | ||
668 | errExit("asprintf"); | ||
669 | if (mount("overlay", oroot, "overlay", MS_MGC_VAL, option) < 0) | ||
670 | errExit("mounting overlayfs"); | ||
671 | } | ||
672 | |||
673 | // mount-bind dev directory | ||
674 | if (arg_debug) | ||
675 | printf("Mounting /dev\n"); | ||
676 | char *dev; | ||
677 | if (asprintf(&dev, "%s/dev", oroot) == -1) | ||
678 | errExit("asprintf"); | ||
679 | if (mount("/dev", dev, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
680 | errExit("mounting /dev"); | ||
681 | |||
682 | // chroot in the new filesystem | ||
683 | if (chroot(oroot) == -1) | ||
684 | errExit("chroot"); | ||
685 | // update /var directory in order to support multiple sandboxes running on the same root directory | ||
686 | if (!arg_private_dev) | ||
687 | fs_dev_shm(); | ||
688 | fs_var_lock(); | ||
689 | fs_var_tmp(); | ||
690 | fs_var_log(); | ||
691 | fs_var_lib(); | ||
692 | fs_var_cache(); | ||
693 | fs_var_utmp(); | ||
694 | |||
695 | // only in user mode | ||
696 | if (getuid()) | ||
697 | sanitize_home(); | ||
698 | |||
699 | // cleanup and exit | ||
700 | free(option); | ||
701 | free(oroot); | ||
702 | free(odiff); | ||
703 | } | ||
704 | |||
705 | |||
706 | |||
707 | #ifdef HAVE_CHROOT | ||
708 | // return 1 if error | ||
709 | int fs_check_chroot_dir(const char *rootdir) { | ||
710 | assert(rootdir); | ||
711 | struct stat s; | ||
712 | char *name; | ||
713 | |||
714 | // check /dev | ||
715 | if (asprintf(&name, "%s/dev", rootdir) == -1) | ||
716 | errExit("asprintf"); | ||
717 | if (stat(name, &s) == -1) { | ||
718 | fprintf(stderr, "Error: cannot find /dev in chroot directory\n"); | ||
719 | return 1; | ||
720 | } | ||
721 | free(name); | ||
722 | |||
723 | // check /var/tmp | ||
724 | if (asprintf(&name, "%s/var/tmp", rootdir) == -1) | ||
725 | errExit("asprintf"); | ||
726 | if (stat(name, &s) == -1) { | ||
727 | fprintf(stderr, "Error: cannot find /var/tmp in chroot directory\n"); | ||
728 | return 1; | ||
729 | } | ||
730 | free(name); | ||
731 | |||
732 | // check /proc | ||
733 | if (asprintf(&name, "%s/proc", rootdir) == -1) | ||
734 | errExit("asprintf"); | ||
735 | if (stat(name, &s) == -1) { | ||
736 | fprintf(stderr, "Error: cannot find /proc in chroot directory\n"); | ||
737 | return 1; | ||
738 | } | ||
739 | free(name); | ||
740 | |||
741 | // check /proc | ||
742 | if (asprintf(&name, "%s/tmp", rootdir) == -1) | ||
743 | errExit("asprintf"); | ||
744 | if (stat(name, &s) == -1) { | ||
745 | fprintf(stderr, "Error: cannot find /tmp in chroot directory\n"); | ||
746 | return 1; | ||
747 | } | ||
748 | free(name); | ||
749 | |||
750 | // check /bin/bash | ||
751 | if (asprintf(&name, "%s/bin/bash", rootdir) == -1) | ||
752 | errExit("asprintf"); | ||
753 | if (stat(name, &s) == -1) { | ||
754 | fprintf(stderr, "Error: cannot find /bin/bash in chroot directory\n"); | ||
755 | return 1; | ||
756 | } | ||
757 | free(name); | ||
758 | |||
759 | return 0; | ||
760 | } | ||
761 | |||
762 | // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf | ||
763 | void fs_chroot(const char *rootdir) { | ||
764 | assert(rootdir); | ||
765 | |||
766 | //*********************************** | ||
767 | // mount-bind a /dev in rootdir | ||
768 | //*********************************** | ||
769 | // mount /dev | ||
770 | char *newdev; | ||
771 | if (asprintf(&newdev, "%s/dev", rootdir) == -1) | ||
772 | errExit("asprintf"); | ||
773 | if (arg_debug) | ||
774 | printf("Mounting /dev on %s\n", newdev); | ||
775 | if (mount("/dev", newdev, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
776 | errExit("mounting /dev"); | ||
777 | |||
778 | // some older distros don't have a /run directory | ||
779 | // create one by default | ||
780 | // no exit on error, let the user deal with any problems | ||
781 | char *rundir; | ||
782 | if (asprintf(&rundir, "%s/run", rootdir) == -1) | ||
783 | errExit("asprintf"); | ||
784 | if (!is_dir(rundir)) { | ||
785 | int rv = mkdir(rundir, S_IRWXU | S_IRWXG | S_IRWXO); | ||
786 | (void) rv; | ||
787 | rv = chown(rundir, 0, 0); | ||
788 | (void) rv; | ||
789 | } | ||
790 | |||
791 | // copy /etc/resolv.conf in chroot directory | ||
792 | // if resolv.conf in chroot is a symbolic link, this will fail | ||
793 | // no exit on error, let the user deal with the problem | ||
794 | char *fname; | ||
795 | if (asprintf(&fname, "%s/etc/resolv.conf", rootdir) == -1) | ||
796 | errExit("asprintf"); | ||
797 | if (arg_debug) | ||
798 | printf("Updating /etc/resolv.conf in %s\n", fname); | ||
799 | if (copy_file("/etc/resolv.conf", fname) == -1) | ||
800 | fprintf(stderr, "Warning: /etc/resolv.conf not initialized\n"); | ||
801 | |||
802 | // chroot into the new directory | ||
803 | if (arg_debug) | ||
804 | printf("Chrooting into %s\n", rootdir); | ||
805 | if (chroot(rootdir) < 0) | ||
806 | errExit("chroot"); | ||
807 | |||
808 | // update /var directory in order to support multiple sandboxes running on the same root directory | ||
809 | if (!arg_private_dev) | ||
810 | fs_dev_shm(); | ||
811 | fs_var_lock(); | ||
812 | fs_var_tmp(); | ||
813 | fs_var_log(); | ||
814 | fs_var_lib(); | ||
815 | fs_var_cache(); | ||
816 | fs_var_utmp(); | ||
817 | |||
818 | // only in user mode | ||
819 | if (getuid()) | ||
820 | sanitize_home(); | ||
821 | |||
822 | } | ||
823 | #endif | ||
824 | |||
825 | |||