diff options
-rw-r--r-- | etc/baloo_file.profile | 1 | ||||
-rw-r--r-- | etc/disable-common.inc | 4 | ||||
-rw-r--r-- | src/firejail/firejail.h | 8 | ||||
-rw-r--r-- | src/firejail/fs.c | 160 | ||||
-rw-r--r-- | src/firejail/mountinfo.c | 205 | ||||
-rw-r--r-- | src/firejail/pulseaudio.c | 14 | ||||
-rw-r--r-- | src/firejail/x11.c | 10 |
7 files changed, 307 insertions, 95 deletions
diff --git a/etc/baloo_file.profile b/etc/baloo_file.profile index 5e749cac1..307a16f9c 100644 --- a/etc/baloo_file.profile +++ b/etc/baloo_file.profile | |||
@@ -47,4 +47,3 @@ noexec /tmp | |||
47 | # Note: Baloo will not be able to update the "first run" key in its configuration files. | 47 | # Note: Baloo will not be able to update the "first run" key in its configuration files. |
48 | # read-only ${HOME} | 48 | # read-only ${HOME} |
49 | # read-write ${HOME}/.local/share | 49 | # read-write ${HOME}/.local/share |
50 | # noexec ${HOME}/.local/share | ||
diff --git a/etc/disable-common.inc b/etc/disable-common.inc index e6ba99874..b78af7917 100644 --- a/etc/disable-common.inc +++ b/etc/disable-common.inc | |||
@@ -266,7 +266,6 @@ read-only ${HOME}/bin | |||
266 | # The following block breaks trash functionality in file managers | 266 | # The following block breaks trash functionality in file managers |
267 | #read-only ${HOME}/.local | 267 | #read-only ${HOME}/.local |
268 | #read-write ${HOME}/.local/share | 268 | #read-write ${HOME}/.local/share |
269 | #noexec ${HOME}/.local/share | ||
270 | blacklist ${HOME}/.local/share/Trash | 269 | blacklist ${HOME}/.local/share/Trash |
271 | 270 | ||
272 | # Write-protection for desktop entries | 271 | # Write-protection for desktop entries |
@@ -401,6 +400,3 @@ blacklist /usr/share/flatpak | |||
401 | blacklist /var/lib/flatpak | 400 | blacklist /var/lib/flatpak |
402 | # most of the time bwrap is SUID binary | 401 | # most of the time bwrap is SUID binary |
403 | blacklist ${PATH}/bwrap | 402 | blacklist ${PATH}/bwrap |
404 | |||
405 | # complement noexec ${HOME} and noexec /tmp | ||
406 | noexec /tmp/.X11-unix | ||
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 19b8480f8..d5733e678 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h | |||
@@ -440,8 +440,10 @@ void preproc_clean_run(void); | |||
440 | void fs_blacklist(void); | 440 | void fs_blacklist(void); |
441 | // remount a directory read-only | 441 | // remount a directory read-only |
442 | void fs_rdonly(const char *dir); | 442 | void fs_rdonly(const char *dir); |
443 | void fs_rdonly_rec(const char *dir); | ||
443 | // remount a directory noexec, nodev and nosuid | 444 | // remount a directory noexec, nodev and nosuid |
444 | void fs_noexec(const char *dir); | 445 | void fs_noexec(const char *dir); |
446 | void fs_noexec_rec(const char *dir); | ||
445 | // mount /proc and /sys directories | 447 | // mount /proc and /sys directories |
446 | void fs_proc_sys_dev_boot(void); | 448 | void fs_proc_sys_dev_boot(void); |
447 | // build a basic read-only filesystem | 449 | // build a basic read-only filesystem |
@@ -550,12 +552,16 @@ int invalid_sandbox(const pid_t pid); | |||
550 | // The return value points to a static area, and will be overwritten by subsequent calls. | 552 | // The return value points to a static area, and will be overwritten by subsequent calls. |
551 | // The function does an exit(1) if anything goes wrong. | 553 | // The function does an exit(1) if anything goes wrong. |
552 | typedef struct { | 554 | typedef struct { |
555 | int mountid; // id of the mount | ||
553 | char *fsname; // the pathname of the directory in the filesystem which forms the root of this mount | 556 | char *fsname; // the pathname of the directory in the filesystem which forms the root of this mount |
554 | char *dir; // mount destination | 557 | char *dir; // mount destination |
555 | char *fstype; // filesystem type | 558 | char *fstype; // filesystem type |
556 | } MountData; | 559 | } MountData; |
557 | MountData *get_last_mount(void); | ||
558 | 560 | ||
561 | // mountinfo.c | ||
562 | MountData *get_last_mount(void); | ||
563 | int get_mount_id(const char *path); | ||
564 | char **get_all_mounts(const int mountid, const char *path); | ||
559 | 565 | ||
560 | // fs_var.c | 566 | // fs_var.c |
561 | void fs_var_log(void); // mounting /var/log | 567 | void fs_var_log(void); // mounting /var/log |
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index 3ce2c7571..6fe9d56aa 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -29,12 +29,14 @@ | |||
29 | #include <errno.h> | 29 | #include <errno.h> |
30 | #include <fcntl.h> | 30 | #include <fcntl.h> |
31 | 31 | ||
32 | #define MAX_BUF 4096 | ||
32 | // check noblacklist statements not matched by a proper blacklist in disable-*.inc files | 33 | // check noblacklist statements not matched by a proper blacklist in disable-*.inc files |
33 | //#define TEST_NO_BLACKLIST_MATCHING | 34 | //#define TEST_NO_BLACKLIST_MATCHING |
34 | 35 | ||
35 | 36 | ||
36 | 37 | ||
37 | static void fs_rdwr(const char *dir); | 38 | static void fs_rdwr(const char *dir); |
39 | static void fs_rdwr_rec(const char *dir); | ||
38 | 40 | ||
39 | 41 | ||
40 | 42 | ||
@@ -147,21 +149,15 @@ static void disable_file(OPERATION op, const char *filename) { | |||
147 | } | 149 | } |
148 | } | 150 | } |
149 | else if (op == MOUNT_READONLY) { | 151 | else if (op == MOUNT_READONLY) { |
150 | if (arg_debug) | 152 | fs_rdonly_rec(fname); |
151 | printf("Mounting read-only %s\n", fname); | ||
152 | fs_rdonly(fname); | ||
153 | // todo: last_disable = SUCCESSFUL; | 153 | // todo: last_disable = SUCCESSFUL; |
154 | } | 154 | } |
155 | else if (op == MOUNT_RDWR) { | 155 | else if (op == MOUNT_RDWR) { |
156 | if (arg_debug) | 156 | fs_rdwr_rec(fname); |
157 | printf("Mounting read-only %s\n", fname); | ||
158 | fs_rdwr(fname); | ||
159 | // todo: last_disable = SUCCESSFUL; | 157 | // todo: last_disable = SUCCESSFUL; |
160 | } | 158 | } |
161 | else if (op == MOUNT_NOEXEC) { | 159 | else if (op == MOUNT_NOEXEC) { |
162 | if (arg_debug) | 160 | fs_noexec_rec(fname); |
163 | printf("Mounting noexec %s\n", fname); | ||
164 | fs_noexec(fname); | ||
165 | // todo: last_disable = SUCCESSFUL; | 161 | // todo: last_disable = SUCCESSFUL; |
166 | } | 162 | } |
167 | else if (op == MOUNT_TMPFS) { | 163 | else if (op == MOUNT_TMPFS) { |
@@ -457,9 +453,10 @@ static int get_mount_flags(const char *path, unsigned long *flags) { | |||
457 | 453 | ||
458 | //*********************************************** | 454 | //*********************************************** |
459 | // mount namespace | 455 | // mount namespace |
456 | // - functions need fully resolved paths | ||
460 | //*********************************************** | 457 | //*********************************************** |
461 | 458 | ||
462 | // remount a directory read-only | 459 | // remount directory read-only |
463 | void fs_rdonly(const char *dir) { | 460 | void fs_rdonly(const char *dir) { |
464 | assert(dir); | 461 | assert(dir); |
465 | // check directory exists | 462 | // check directory exists |
@@ -471,77 +468,144 @@ void fs_rdonly(const char *dir) { | |||
471 | if ((flags & MS_RDONLY) == MS_RDONLY) | 468 | if ((flags & MS_RDONLY) == MS_RDONLY) |
472 | return; | 469 | return; |
473 | flags |= MS_RDONLY; | 470 | flags |= MS_RDONLY; |
471 | if (arg_debug) | ||
472 | printf("Mounting read-only %s\n", dir); | ||
474 | // mount --bind /bin /bin | 473 | // mount --bind /bin /bin |
475 | // mount --bind -o remount,ro /bin | 474 | // mount --bind -o remount,ro /bin |
476 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || | 475 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || |
477 | mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT|MS_REC, NULL) < 0) | 476 | mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0) |
478 | errExit("mount read-only"); | 477 | errExit("mount read-only"); |
479 | fs_logger2("read-only", dir); | 478 | fs_logger2("read-only", dir); |
480 | } | 479 | } |
481 | } | 480 | } |
482 | 481 | ||
483 | static void fs_rdwr(const char *dir) { | 482 | // remount directory read-only recursively |
483 | void fs_rdonly_rec(const char *dir) { | ||
484 | assert(dir); | 484 | assert(dir); |
485 | // check directory exists and ensure we have a resolved path | 485 | EUID_USER(); |
486 | // the resolved path allows to run a sanity check after the mount | 486 | // get mount point of the directory |
487 | char *path = realpath(dir, NULL); | 487 | int mountid = get_mount_id(dir); |
488 | if (path == NULL) | 488 | if (mountid == 0) { |
489 | return; | 489 | EUID_ROOT(); |
490 | // allow only user owned directories, except the user is root | ||
491 | uid_t u = getuid(); | ||
492 | struct stat s; | ||
493 | int rv = stat(path, &s); | ||
494 | if (rv) { | ||
495 | free(path); | ||
496 | return; | 490 | return; |
497 | } | 491 | } |
498 | if (u != 0 && s.st_uid != u) { | 492 | // build array with all mount points that need to get remounted |
499 | fwarning("you are not allowed to change %s to read-write\n", path); | 493 | char **arr = get_all_mounts(mountid, dir); |
500 | free(path); | 494 | assert(arr); |
501 | return; | 495 | // remount |
496 | EUID_ROOT(); | ||
497 | char **tmp = arr; | ||
498 | while (*tmp) { | ||
499 | fs_rdonly(*tmp); | ||
500 | free(*tmp++); | ||
501 | } | ||
502 | free(arr); | ||
503 | } | ||
504 | |||
505 | // remount directory read-write | ||
506 | static void fs_rdwr(const char *dir) { | ||
507 | assert(dir); | ||
508 | // check directory exists | ||
509 | struct stat s; | ||
510 | int rv = stat(dir, &s); | ||
511 | if (rv == 0) { | ||
512 | // allow only user owned directories, except the user is root | ||
513 | uid_t u = getuid(); | ||
514 | if (u != 0 && s.st_uid != u) { | ||
515 | fwarning("you are not allowed to change %s to read-write\n", dir); | ||
516 | return; | ||
517 | } | ||
518 | unsigned long flags = 0; | ||
519 | get_mount_flags(dir, &flags); | ||
520 | if ((flags & MS_RDONLY) == 0) | ||
521 | return; | ||
522 | flags &= ~MS_RDONLY; | ||
523 | if (arg_debug) | ||
524 | printf("Mounting read-write %s\n", dir); | ||
525 | // mount --bind /bin /bin | ||
526 | // mount --bind -o remount,rw /bin | ||
527 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || | ||
528 | mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0) | ||
529 | errExit("mount read-write"); | ||
530 | fs_logger2("read-write", dir); | ||
531 | // run a sanity check on /proc/self/mountinfo | ||
532 | MountData *mptr = get_last_mount(); | ||
533 | size_t len = strlen(dir); | ||
534 | if (strncmp(mptr->dir, dir, len) != 0 || | ||
535 | (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/')) | ||
536 | errLogExit("invalid read-write mount"); | ||
502 | } | 537 | } |
503 | // mount --bind /bin /bin | 538 | } |
504 | // mount --bind -o remount,rw /bin | 539 | |
505 | unsigned long flags = 0; | 540 | // remount directory read-write recursively |
506 | get_mount_flags(path, &flags); | 541 | static void fs_rdwr_rec(const char *dir) { |
507 | if ((flags & MS_RDONLY) == 0) { | 542 | assert(dir); |
508 | free(path); | 543 | EUID_USER(); |
544 | // get mount point of the directory | ||
545 | int mountid = get_mount_id(dir); | ||
546 | if (mountid == 0) { | ||
547 | EUID_ROOT(); | ||
509 | return; | 548 | return; |
510 | } | 549 | } |
511 | flags &= ~MS_RDONLY; | 550 | // build array with all mount points that need to get remounted |
512 | if (mount(path, path, NULL, MS_BIND|MS_REC, NULL) < 0 || | 551 | char **arr = get_all_mounts(mountid, dir); |
513 | mount(NULL, path, NULL, flags|MS_BIND|MS_REMOUNT|MS_REC, NULL) < 0) | 552 | assert(arr); |
514 | errExit("mount read-write"); | 553 | // remount |
515 | fs_logger2("read-write", path); | 554 | EUID_ROOT(); |
516 | 555 | char **tmp = arr; | |
517 | // run a check on /proc/self/mountinfo to validate the mount | 556 | while (*tmp) { |
518 | MountData *mptr = get_last_mount(); | 557 | fs_rdwr(*tmp); |
519 | if (strncmp(mptr->dir, path, strlen(path)) != 0) | 558 | free(*tmp++); |
520 | errLogExit("invalid read-write mount"); | 559 | } |
521 | 560 | free(arr); | |
522 | free(path); | ||
523 | } | 561 | } |
524 | 562 | ||
563 | // remount directory noexec, nodev, nosuid | ||
525 | void fs_noexec(const char *dir) { | 564 | void fs_noexec(const char *dir) { |
526 | assert(dir); | 565 | assert(dir); |
527 | // check directory exists | 566 | // check directory exists |
528 | struct stat s; | 567 | struct stat s; |
529 | int rv = stat(dir, &s); | 568 | int rv = stat(dir, &s); |
530 | if (rv == 0) { | 569 | if (rv == 0) { |
531 | // mount --bind /bin /bin | ||
532 | // mount --bind -o remount,ro /bin | ||
533 | unsigned long flags = 0; | 570 | unsigned long flags = 0; |
534 | get_mount_flags(dir, &flags); | 571 | get_mount_flags(dir, &flags); |
535 | if ((flags & (MS_NOEXEC|MS_NODEV|MS_NOSUID)) == (MS_NOEXEC|MS_NODEV|MS_NOSUID)) | 572 | if ((flags & (MS_NOEXEC|MS_NODEV|MS_NOSUID)) == (MS_NOEXEC|MS_NODEV|MS_NOSUID)) |
536 | return; | 573 | return; |
537 | flags |= MS_NOEXEC|MS_NODEV|MS_NOSUID; | 574 | flags |= MS_NOEXEC|MS_NODEV|MS_NOSUID; |
575 | if (arg_debug) | ||
576 | printf("Mounting noexec %s\n", dir); | ||
577 | // mount --bind /bin /bin | ||
578 | // mount --bind -o remount,noexec /bin | ||
538 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || | 579 | if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || |
539 | mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT|MS_REC, NULL) < 0) | 580 | mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0) |
540 | errExit("mount noexec"); | 581 | errExit("mount noexec"); |
541 | fs_logger2("noexec", dir); | 582 | fs_logger2("noexec", dir); |
542 | } | 583 | } |
543 | } | 584 | } |
544 | 585 | ||
586 | // remount directory noexec, nodev, nosuid recursively | ||
587 | void fs_noexec_rec(const char *dir) { | ||
588 | assert(dir); | ||
589 | EUID_USER(); | ||
590 | // get mount point of the directory | ||
591 | int mountid = get_mount_id(dir); | ||
592 | if (mountid == 0) { | ||
593 | EUID_ROOT(); | ||
594 | return; | ||
595 | } | ||
596 | // build array with all mount points that need to get remounted | ||
597 | char **arr = get_all_mounts(mountid, dir); | ||
598 | assert(arr); | ||
599 | // remount | ||
600 | EUID_ROOT(); | ||
601 | char **tmp = arr; | ||
602 | while (*tmp) { | ||
603 | fs_noexec(*tmp); | ||
604 | free(*tmp++); | ||
605 | } | ||
606 | free(arr); | ||
607 | } | ||
608 | |||
545 | // Disable /mnt, /media, /run/mount and /run/media access | 609 | // Disable /mnt, /media, /run/mount and /run/media access |
546 | void fs_mnt(const int enforce) { | 610 | void fs_mnt(const int enforce) { |
547 | if (enforce) { | 611 | if (enforce) { |
diff --git a/src/firejail/mountinfo.c b/src/firejail/mountinfo.c index 4a7816901..b7760ba67 100644 --- a/src/firejail/mountinfo.c +++ b/src/firejail/mountinfo.c | |||
@@ -19,16 +19,19 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | #include "firejail.h" | 21 | #include "firejail.h" |
22 | #include <fcntl.h> | ||
22 | 23 | ||
23 | #define MAX_BUF 4096 | 24 | #define MAX_BUF 4096 |
25 | |||
24 | static char mbuf[MAX_BUF]; | 26 | static char mbuf[MAX_BUF]; |
25 | static MountData mdata; | 27 | static MountData mdata; |
26 | 28 | ||
29 | |||
27 | // Convert octal escape sequence to decimal value | 30 | // Convert octal escape sequence to decimal value |
28 | static int read_oct(const char *path) { | 31 | static int read_oct(const char *path) { |
29 | int decimal = 0; | 32 | int decimal = 0; |
30 | int digit, i; | 33 | int digit, i; |
31 | // there are always three octal digits | 34 | // there are always exactly three octal digits |
32 | for (i = 1; i < 4; i++) { | 35 | for (i = 1; i < 4; i++) { |
33 | digit = *(path + i); | 36 | digit = *(path + i); |
34 | if (digit < '0' || digit > '7') { | 37 | if (digit < '0' || digit > '7') { |
@@ -61,43 +64,38 @@ static void unmangle_path(char *path) { | |||
61 | } | 64 | } |
62 | } | 65 | } |
63 | 66 | ||
64 | // Get info regarding the last kernel mount operation. | 67 | // Parse a line from /proc/self/mountinfo, |
65 | // The return value points to a static area, and will be overwritten by subsequent calls. | 68 | // the function does an exit(1) if anything goes wrong. |
66 | // The function does an exit(1) if anything goes wrong. | 69 | static void parse_line(char *line, MountData *output) { |
67 | MountData *get_last_mount(void) { | 70 | assert(line && *line); |
68 | // open /proc/self/mountinfo | 71 | memset(output, 0, sizeof(*output)); |
69 | FILE *fp = fopen("/proc/self/mountinfo", "r"); | 72 | // extract filesystem name, directory and filesystem types |
70 | if (!fp) | ||
71 | goto errexit; | ||
72 | |||
73 | mbuf[0] = '\0'; | ||
74 | while (fgets(mbuf, MAX_BUF, fp)); | ||
75 | fclose(fp); | ||
76 | if (arg_debug) | ||
77 | printf("%s", mbuf); | ||
78 | |||
79 | // extract filesystem name, directory and filesystem type | ||
80 | // examples: | 73 | // examples: |
81 | // 587 543 8:1 /tmp /etc rw,relatime master:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered | 74 | // 587 543 8:1 /tmp /etc rw,relatime master:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered |
82 | // mdata.fsname: /tmp | 75 | // output.mountid: 587 |
83 | // mdata.dir: /etc | 76 | // output.fsname: /tmp |
84 | // mdata.fstype: ext4 | 77 | // output.dir: /etc |
78 | // output.fstype: ext4 | ||
85 | // 585 564 0:76 / /home/netblue/.cache rw,nosuid,nodev - tmpfs tmpfs rw | 79 | // 585 564 0:76 / /home/netblue/.cache rw,nosuid,nodev - tmpfs tmpfs rw |
86 | // mdata.fsname: / | 80 | // output.mountid: 585 |
87 | // mdata.dir: /home/netblue/.cache | 81 | // output.fsname: / |
88 | // mdata.fstype: tmpfs | 82 | // output.dir: /home/netblue/.cache |
89 | memset(&mdata, 0, sizeof(mdata)); | 83 | // output.fstype: tmpfs |
90 | char *ptr = strtok(mbuf, " "); | 84 | |
85 | char *ptr = strtok(line, " "); | ||
91 | if (!ptr) | 86 | if (!ptr) |
92 | goto errexit; | 87 | goto errexit; |
93 | 88 | if (ptr != line) | |
89 | goto errexit; | ||
90 | output->mountid = atoi(ptr); | ||
94 | int cnt = 1; | 91 | int cnt = 1; |
92 | |||
95 | while ((ptr = strtok(NULL, " ")) != NULL) { | 93 | while ((ptr = strtok(NULL, " ")) != NULL) { |
96 | cnt++; | 94 | cnt++; |
97 | if (cnt == 4) | 95 | if (cnt == 4) |
98 | mdata.fsname = ptr; | 96 | output->fsname = ptr; |
99 | else if (cnt == 5) { | 97 | else if (cnt == 5) { |
100 | mdata.dir = ptr; | 98 | output->dir = ptr; |
101 | break; | 99 | break; |
102 | } | 100 | } |
103 | } | 101 | } |
@@ -109,21 +107,156 @@ MountData *get_last_mount(void) { | |||
109 | ptr = strtok(NULL, " "); | 107 | ptr = strtok(NULL, " "); |
110 | if (!ptr) | 108 | if (!ptr) |
111 | goto errexit; | 109 | goto errexit; |
112 | mdata.fstype = ptr++; | 110 | output->fstype = ptr++; |
113 | 111 | ||
114 | if (mdata.fsname == NULL || | 112 | |
115 | mdata.dir == NULL || | 113 | if (output->mountid == 0 || |
116 | mdata.fstype == NULL) | 114 | output->fsname == NULL || |
115 | output->dir == NULL || | ||
116 | output->fstype == NULL) | ||
117 | goto errexit; | 117 | goto errexit; |
118 | 118 | ||
119 | unmangle_path(mdata.fsname); | 119 | // restore empty spaces |
120 | unmangle_path(mdata.dir); | 120 | unmangle_path(output->fsname); |
121 | unmangle_path(output->dir); | ||
122 | |||
123 | return; | ||
124 | |||
125 | errexit: | ||
126 | fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); | ||
127 | exit(1); | ||
128 | } | ||
129 | |||
130 | // The return value points to a static area, and will be overwritten by subsequent calls. | ||
131 | MountData *get_last_mount(void) { | ||
132 | // open /proc/self/mountinfo | ||
133 | FILE *fp = fopen("/proc/self/mountinfo", "re"); | ||
134 | if (!fp) { | ||
135 | perror("fopen"); | ||
136 | fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); | ||
137 | exit(1); | ||
138 | } | ||
139 | |||
140 | mbuf[0] = '\0'; | ||
141 | // go to the last line | ||
142 | while (fgets(mbuf, MAX_BUF, fp)); | ||
143 | fclose(fp); | ||
144 | if (arg_debug) | ||
145 | printf("%s", mbuf); | ||
146 | |||
147 | parse_line(mbuf, &mdata); | ||
121 | 148 | ||
122 | if (arg_debug) | 149 | if (arg_debug) |
123 | printf("fsname=%s dir=%s fstype=%s\n", mdata.fsname, mdata.dir, mdata.fstype); | 150 | printf("mountid=%d fsname=%s dir=%s fstype=%s\n", mdata.mountid, mdata.fsname, mdata.dir, mdata.fstype); |
124 | return &mdata; | 151 | return &mdata; |
152 | } | ||
153 | |||
154 | // Extract the mount id from /proc/self/fdinfo and return it. | ||
155 | int get_mount_id(const char *path) { | ||
156 | EUID_ASSERT(); | ||
157 | int fd = open(path, O_PATH|O_CLOEXEC); | ||
158 | if (fd == -1) | ||
159 | return 0; | ||
160 | |||
161 | char *fdinfo; | ||
162 | if (asprintf(&fdinfo, "/proc/self/fdinfo/%d", fd) == -1) | ||
163 | errExit("asprintf"); | ||
164 | EUID_ROOT(); | ||
165 | FILE *fp = fopen(fdinfo, "re"); | ||
166 | EUID_USER(); | ||
167 | if (!fp) | ||
168 | goto errexit; | ||
169 | // go to the last line | ||
170 | char buf[MAX_BUF]; | ||
171 | while (fgets(buf, MAX_BUF, fp)); | ||
172 | fclose(fp); | ||
173 | close(fd); | ||
174 | // go to the mount id | ||
175 | if (strncmp(buf, "mnt_id:", 7) != 0) | ||
176 | goto errexit; | ||
177 | char *ptr = buf + 7; | ||
178 | while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) { | ||
179 | ptr++; | ||
180 | } | ||
181 | if (*ptr == '\0') | ||
182 | goto errexit; | ||
183 | free(fdinfo); | ||
184 | |||
185 | return atoi(ptr); | ||
125 | 186 | ||
126 | errexit: | 187 | errexit: |
127 | fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); | 188 | fprintf(stderr, "Error: cannot read file in /proc/self/fdinfo\n"); |
128 | exit(1); | 189 | exit(1); |
129 | } | 190 | } |
191 | |||
192 | // Return array with all paths that might need a remount. | ||
193 | char **get_all_mounts(const int mountid, const char *path) { | ||
194 | // open /proc/self/mountinfo | ||
195 | FILE *fp = fopen("/proc/self/mountinfo", "re"); | ||
196 | if (!fp) { | ||
197 | perror("fopen"); | ||
198 | fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); | ||
199 | exit(1); | ||
200 | } | ||
201 | |||
202 | size_t size = 32; | ||
203 | size_t cnt = 0; | ||
204 | char **rv = malloc(size * sizeof(*rv)); | ||
205 | if (!rv) | ||
206 | errExit("malloc"); | ||
207 | |||
208 | // read /proc/self/mountinfo | ||
209 | size_t pathlen = strlen(path); | ||
210 | int found = 0; | ||
211 | while (fgets(mbuf, MAX_BUF, fp)) { | ||
212 | // find mount point with mount id | ||
213 | if (!found) { | ||
214 | parse_line(mbuf, &mdata); | ||
215 | if (mdata.mountid == mountid) { | ||
216 | // don't remount blacklisted paths, | ||
217 | // give up if mount id has been reassigned | ||
218 | if (strstr(mdata.fsname, "firejail.ro.dir") || | ||
219 | strstr(mdata.fsname, "firejail.ro.file") || | ||
220 | strncmp(mdata.dir, path, strlen(mdata.dir))) | ||
221 | break; | ||
222 | |||
223 | *rv = strdup(path); | ||
224 | if (*rv == NULL) | ||
225 | errExit("strdup"); | ||
226 | cnt++; | ||
227 | found = 1; | ||
228 | continue; | ||
229 | } | ||
230 | else | ||
231 | continue; | ||
232 | } | ||
233 | // from here on add all mount points below path | ||
234 | parse_line(mbuf, &mdata); | ||
235 | if (strncmp(mdata.dir, path, pathlen) == 0 && | ||
236 | mdata.dir[pathlen] == '/' && | ||
237 | strstr(mdata.fsname, "firejail.ro.dir") == NULL && | ||
238 | strstr(mdata.fsname, "firejail.ro.file") == NULL) { | ||
239 | |||
240 | if (cnt >= size) { | ||
241 | size *= 2; | ||
242 | rv = realloc(rv, size * sizeof(*rv)); | ||
243 | if (!rv) | ||
244 | errExit("realloc"); | ||
245 | } | ||
246 | rv[cnt] = strdup(mdata.dir); | ||
247 | if (!rv[cnt]) | ||
248 | errExit("strdup"); | ||
249 | cnt++; | ||
250 | } | ||
251 | } | ||
252 | if (cnt == size) { | ||
253 | size++; | ||
254 | rv = realloc(rv, size * sizeof(*rv)); | ||
255 | if (!rv) | ||
256 | errExit("realloc"); | ||
257 | } | ||
258 | rv[cnt] = NULL; // end of the array | ||
259 | |||
260 | fclose(fp); | ||
261 | return rv; | ||
262 | } | ||
diff --git a/src/firejail/pulseaudio.c b/src/firejail/pulseaudio.c index e6696ecb4..4ddaba7ed 100644 --- a/src/firejail/pulseaudio.c +++ b/src/firejail/pulseaudio.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 <sys/statvfs.h> | ||
23 | #include <sys/mount.h> | 24 | #include <sys/mount.h> |
24 | #include <dirent.h> | 25 | #include <dirent.h> |
25 | #include <sys/wait.h> | 26 | #include <sys/wait.h> |
@@ -82,10 +83,8 @@ void pulseaudio_init(void) { | |||
82 | // create the new user pulseaudio directory | 83 | // create the new user pulseaudio directory |
83 | if (mkdir(RUN_PULSE_DIR, 0700) == -1) | 84 | if (mkdir(RUN_PULSE_DIR, 0700) == -1) |
84 | errExit("mkdir"); | 85 | errExit("mkdir"); |
85 | // make it a mount point and add mount flags | 86 | // mount it nosuid, noexec, nodev |
86 | if (mount(RUN_PULSE_DIR, RUN_PULSE_DIR, NULL, MS_BIND, NULL) < 0 || | 87 | fs_noexec(RUN_PULSE_DIR); |
87 | mount(NULL, RUN_PULSE_DIR, NULL, MS_NOEXEC|MS_NODEV|MS_NOSUID|MS_BIND|MS_REMOUNT, NULL) < 0) | ||
88 | errExit("mount RUN_PULSE_DIR"); | ||
89 | 88 | ||
90 | // create the new client.conf file | 89 | // create the new client.conf file |
91 | char *pulsecfg = NULL; | 90 | char *pulsecfg = NULL; |
@@ -189,7 +188,12 @@ void pulseaudio_init(void) { | |||
189 | // confirm the actual mount destination is owned by the user | 188 | // confirm the actual mount destination is owned by the user |
190 | if (fstat(fd, &s) == -1 || s.st_uid != getuid()) | 189 | if (fstat(fd, &s) == -1 || s.st_uid != getuid()) |
191 | errExit("fstat"); | 190 | errExit("fstat"); |
192 | 191 | // preserve a read-only mount | |
192 | struct statvfs vfs; | ||
193 | if (fstatvfs(fd, &vfs) == -1) | ||
194 | errExit("fstatvfs"); | ||
195 | if ((vfs.f_flag & MS_RDONLY) == MS_RDONLY) | ||
196 | fs_rdonly(RUN_PULSE_DIR); | ||
193 | // mount via the link in /proc/self/fd | 197 | // mount via the link in /proc/self/fd |
194 | char *proc; | 198 | char *proc; |
195 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | 199 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) |
diff --git a/src/firejail/x11.c b/src/firejail/x11.c index 7d02701c9..9a15a06c8 100644 --- a/src/firejail/x11.c +++ b/src/firejail/x11.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 <sys/statvfs.h> | ||
23 | #include <sys/socket.h> | 24 | #include <sys/socket.h> |
24 | #include <sys/un.h> | 25 | #include <sys/un.h> |
25 | #include <unistd.h> | 26 | #include <unistd.h> |
@@ -1163,6 +1164,9 @@ void x11_xorg(void) { | |||
1163 | unlink(tmpfname); | 1164 | unlink(tmpfname); |
1164 | umount("/tmp"); | 1165 | umount("/tmp"); |
1165 | 1166 | ||
1167 | // remount RUN_XAUTHORITY_SEC_FILE noexec, nodev, nosuid | ||
1168 | fs_noexec(RUN_XAUTHORITY_SEC_FILE); | ||
1169 | |||
1166 | // Ensure there is already a file in the usual location, so that bind-mount below will work. | 1170 | // Ensure there is already a file in the usual location, so that bind-mount below will work. |
1167 | char *dest; | 1171 | char *dest; |
1168 | if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1) | 1172 | if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1) |
@@ -1184,6 +1188,12 @@ void x11_xorg(void) { | |||
1184 | fprintf(stderr, "Error: .Xauthority is not a user owned regular file\n"); | 1188 | fprintf(stderr, "Error: .Xauthority is not a user owned regular file\n"); |
1185 | exit(1); | 1189 | exit(1); |
1186 | } | 1190 | } |
1191 | // preserve a read-only mount | ||
1192 | struct statvfs vfs; | ||
1193 | if (fstatvfs(fd, &vfs) == -1) | ||
1194 | errExit("fstatvfs"); | ||
1195 | if ((vfs.f_flag & MS_RDONLY) == MS_RDONLY) | ||
1196 | fs_rdonly(RUN_XAUTHORITY_SEC_FILE); | ||
1187 | 1197 | ||
1188 | // mount via the link in /proc/self/fd | 1198 | // mount via the link in /proc/self/fd |
1189 | char *proc; | 1199 | char *proc; |