From a7164cb38171c0291fac7f3a0767fe8f1c69b02d Mon Sep 17 00:00:00 2001 From: smitsohu Date: Thu, 25 Oct 2018 22:07:19 +0200 Subject: experimental: remounts child mount points as well (read-only, read-write, noexec) --- src/firejail/firejail.h | 8 +- src/firejail/fs.c | 156 ++++++++++++++++++++++++----------- src/firejail/mountinfo.c | 205 ++++++++++++++++++++++++++++++++++++++-------- src/firejail/pulseaudio.c | 14 ++-- src/firejail/x11.c | 10 +++ 5 files changed, 302 insertions(+), 91 deletions(-) (limited to 'src') 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); void fs_blacklist(void); // remount a directory read-only void fs_rdonly(const char *dir); +void fs_rdonly_rec(const char *dir); // remount a directory noexec, nodev and nosuid void fs_noexec(const char *dir); +void fs_noexec_rec(const char *dir); // mount /proc and /sys directories void fs_proc_sys_dev_boot(void); // build a basic read-only filesystem @@ -550,12 +552,16 @@ int invalid_sandbox(const pid_t pid); // The return value points to a static area, and will be overwritten by subsequent calls. // The function does an exit(1) if anything goes wrong. typedef struct { + int mountid; // id of the mount char *fsname; // the pathname of the directory in the filesystem which forms the root of this mount char *dir; // mount destination char *fstype; // filesystem type } MountData; -MountData *get_last_mount(void); +// mountinfo.c +MountData *get_last_mount(void); +int get_mount_id(const char *path); +char **get_all_mounts(const int mountid, const char *path); // fs_var.c void fs_var_log(void); // mounting /var/log diff --git a/src/firejail/fs.c b/src/firejail/fs.c index 3ce2c7571..46124c482 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c @@ -29,12 +29,14 @@ #include #include +#define MAX_BUF 4096 // check noblacklist statements not matched by a proper blacklist in disable-*.inc files //#define TEST_NO_BLACKLIST_MATCHING static void fs_rdwr(const char *dir); +static void fs_rdwr_rec(const char *dir); @@ -147,21 +149,15 @@ static void disable_file(OPERATION op, const char *filename) { } } else if (op == MOUNT_READONLY) { - if (arg_debug) - printf("Mounting read-only %s\n", fname); - fs_rdonly(fname); + fs_rdonly_rec(fname); // todo: last_disable = SUCCESSFUL; } else if (op == MOUNT_RDWR) { - if (arg_debug) - printf("Mounting read-only %s\n", fname); - fs_rdwr(fname); + fs_rdwr_rec(fname); // todo: last_disable = SUCCESSFUL; } else if (op == MOUNT_NOEXEC) { - if (arg_debug) - printf("Mounting noexec %s\n", fname); - fs_noexec(fname); + fs_noexec_rec(fname); // todo: last_disable = SUCCESSFUL; } else if (op == MOUNT_TMPFS) { @@ -457,9 +453,10 @@ static int get_mount_flags(const char *path, unsigned long *flags) { //*********************************************** // mount namespace +// - functions need fully resolved paths //*********************************************** -// remount a directory read-only +// remount directory read-only void fs_rdonly(const char *dir) { assert(dir); // check directory exists @@ -471,77 +468,138 @@ void fs_rdonly(const char *dir) { if ((flags & MS_RDONLY) == MS_RDONLY) return; flags |= MS_RDONLY; + if (arg_debug) + printf("Mounting read-only %s\n", dir); // mount --bind /bin /bin // mount --bind -o remount,ro /bin if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || - mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT|MS_REC, NULL) < 0) + mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0) errExit("mount read-only"); fs_logger2("read-only", dir); } } -static void fs_rdwr(const char *dir) { +// remount directory read-only recursively +void fs_rdonly_rec(const char *dir) { assert(dir); - // check directory exists and ensure we have a resolved path - // the resolved path allows to run a sanity check after the mount - char *path = realpath(dir, NULL); - if (path == NULL) + EUID_USER(); + // get mount point of the directory + int mountid = get_mount_id(dir); + if (mountid == 0) return; - // allow only user owned directories, except the user is root - uid_t u = getuid(); + // build array with all mount points that need to get remounted + char **arr = get_all_mounts(mountid, dir); + assert(arr); + // remount + EUID_ROOT(); + char **tmp = arr; + while (*tmp) { + fs_rdonly(*tmp); + free(*tmp++); + } + free(arr); +} + +// remount directory read-write +static void fs_rdwr(const char *dir) { + assert(dir); + // check directory exists struct stat s; - int rv = stat(path, &s); - if (rv) { - free(path); - return; - } - if (u != 0 && s.st_uid != u) { - fwarning("you are not allowed to change %s to read-write\n", path); - free(path); - return; - } - // mount --bind /bin /bin - // mount --bind -o remount,rw /bin - unsigned long flags = 0; - get_mount_flags(path, &flags); - if ((flags & MS_RDONLY) == 0) { - free(path); - return; + int rv = stat(dir, &s); + if (rv == 0) { + // allow only user owned directories, except the user is root + uid_t u = getuid(); + if (u != 0 && s.st_uid != u) { + fwarning("you are not allowed to change %s to read-write\n", dir); + return; + } + unsigned long flags = 0; + get_mount_flags(dir, &flags); + if ((flags & MS_RDONLY) == 0) + return; + flags &= ~MS_RDONLY; + if (arg_debug) + printf("Mounting read-write %s\n", dir); + // mount --bind /bin /bin + // mount --bind -o remount,rw /bin + if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || + mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0) + errExit("mount read-write"); + fs_logger2("read-write", dir); + // run a sanity check on /proc/self/mountinfo + MountData *mptr = get_last_mount(); + size_t len = strlen(dir); + if (strncmp(mptr->dir, dir, len) != 0 || + (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/')) + errLogExit("invalid read-write mount"); } - flags &= ~MS_RDONLY; - if (mount(path, path, NULL, MS_BIND|MS_REC, NULL) < 0 || - mount(NULL, path, NULL, flags|MS_BIND|MS_REMOUNT|MS_REC, NULL) < 0) - errExit("mount read-write"); - fs_logger2("read-write", path); - - // run a check on /proc/self/mountinfo to validate the mount - MountData *mptr = get_last_mount(); - if (strncmp(mptr->dir, path, strlen(path)) != 0) - errLogExit("invalid read-write mount"); +} - free(path); +// remount directory read-write recursively +static void fs_rdwr_rec(const char *dir) { + assert(dir); + EUID_USER(); + // get mount point of the directory + int mountid = get_mount_id(dir); + if (mountid == 0) + return; + // build array with all mount points that need to get remounted + char **arr = get_all_mounts(mountid, dir); + assert(arr); + // remount + EUID_ROOT(); + char **tmp = arr; + while (*tmp) { + fs_rdwr(*tmp); + free(*tmp++); + } + free(arr); } +// remount directory noexec, nodev, nosuid void fs_noexec(const char *dir) { assert(dir); // check directory exists struct stat s; int rv = stat(dir, &s); if (rv == 0) { - // mount --bind /bin /bin - // mount --bind -o remount,ro /bin unsigned long flags = 0; get_mount_flags(dir, &flags); if ((flags & (MS_NOEXEC|MS_NODEV|MS_NOSUID)) == (MS_NOEXEC|MS_NODEV|MS_NOSUID)) return; flags |= MS_NOEXEC|MS_NODEV|MS_NOSUID; + if (arg_debug) + printf("Mounting noexec %s\n", dir); + // mount --bind /bin /bin + // mount --bind -o remount,noexec /bin if (mount(dir, dir, NULL, MS_BIND|MS_REC, NULL) < 0 || - mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT|MS_REC, NULL) < 0) + mount(NULL, dir, NULL, flags|MS_BIND|MS_REMOUNT, NULL) < 0) errExit("mount noexec"); fs_logger2("noexec", dir); } } +// remount directory noexec, nodev, nosuid recursively +void fs_noexec_rec(const char *dir) { + assert(dir); + EUID_USER(); + // get mount point of the directory + int mountid = get_mount_id(dir); + if (mountid == 0) + return; + // build array with all mount points that need to get remounted + char **arr = get_all_mounts(mountid, dir); + assert(arr); + // remount + EUID_ROOT(); + char **tmp = arr; + while (*tmp) { + fs_noexec(*tmp); + free(*tmp++); + } + free(arr); +} + // Disable /mnt, /media, /run/mount and /run/media access void fs_mnt(const int enforce) { 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 @@ */ #include "firejail.h" +#include #define MAX_BUF 4096 + static char mbuf[MAX_BUF]; static MountData mdata; + // Convert octal escape sequence to decimal value static int read_oct(const char *path) { int decimal = 0; int digit, i; - // there are always three octal digits + // there are always exactly three octal digits for (i = 1; i < 4; i++) { digit = *(path + i); if (digit < '0' || digit > '7') { @@ -61,43 +64,38 @@ static void unmangle_path(char *path) { } } -// Get info regarding the last kernel mount operation. -// The return value points to a static area, and will be overwritten by subsequent calls. -// The function does an exit(1) if anything goes wrong. -MountData *get_last_mount(void) { - // open /proc/self/mountinfo - FILE *fp = fopen("/proc/self/mountinfo", "r"); - if (!fp) - goto errexit; - - mbuf[0] = '\0'; - while (fgets(mbuf, MAX_BUF, fp)); - fclose(fp); - if (arg_debug) - printf("%s", mbuf); - - // extract filesystem name, directory and filesystem type +// Parse a line from /proc/self/mountinfo, +// the function does an exit(1) if anything goes wrong. +static void parse_line(char *line, MountData *output) { + assert(line && *line); + memset(output, 0, sizeof(*output)); + // extract filesystem name, directory and filesystem types // examples: // 587 543 8:1 /tmp /etc rw,relatime master:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered - // mdata.fsname: /tmp - // mdata.dir: /etc - // mdata.fstype: ext4 + // output.mountid: 587 + // output.fsname: /tmp + // output.dir: /etc + // output.fstype: ext4 // 585 564 0:76 / /home/netblue/.cache rw,nosuid,nodev - tmpfs tmpfs rw - // mdata.fsname: / - // mdata.dir: /home/netblue/.cache - // mdata.fstype: tmpfs - memset(&mdata, 0, sizeof(mdata)); - char *ptr = strtok(mbuf, " "); + // output.mountid: 585 + // output.fsname: / + // output.dir: /home/netblue/.cache + // output.fstype: tmpfs + + char *ptr = strtok(line, " "); if (!ptr) goto errexit; - + if (ptr != line) + goto errexit; + output->mountid = atoi(ptr); int cnt = 1; + while ((ptr = strtok(NULL, " ")) != NULL) { cnt++; if (cnt == 4) - mdata.fsname = ptr; + output->fsname = ptr; else if (cnt == 5) { - mdata.dir = ptr; + output->dir = ptr; break; } } @@ -109,21 +107,156 @@ MountData *get_last_mount(void) { ptr = strtok(NULL, " "); if (!ptr) goto errexit; - mdata.fstype = ptr++; + output->fstype = ptr++; - if (mdata.fsname == NULL || - mdata.dir == NULL || - mdata.fstype == NULL) + + if (output->mountid == 0 || + output->fsname == NULL || + output->dir == NULL || + output->fstype == NULL) goto errexit; - unmangle_path(mdata.fsname); - unmangle_path(mdata.dir); + // restore empty spaces + unmangle_path(output->fsname); + unmangle_path(output->dir); + + return; + +errexit: + fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); + exit(1); +} + +// The return value points to a static area, and will be overwritten by subsequent calls. +MountData *get_last_mount(void) { + // open /proc/self/mountinfo + FILE *fp = fopen("/proc/self/mountinfo", "re"); + if (!fp) { + perror("fopen"); + fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); + exit(1); + } + + mbuf[0] = '\0'; + // go to the last line + while (fgets(mbuf, MAX_BUF, fp)); + fclose(fp); + if (arg_debug) + printf("%s", mbuf); + + parse_line(mbuf, &mdata); if (arg_debug) - printf("fsname=%s dir=%s fstype=%s\n", mdata.fsname, mdata.dir, mdata.fstype); + printf("mountid=%d fsname=%s dir=%s fstype=%s\n", mdata.mountid, mdata.fsname, mdata.dir, mdata.fstype); return &mdata; +} + +// Extract the mount id from /proc/self/fdinfo and return it. +int get_mount_id(const char *path) { + EUID_ASSERT(); + int fd = open(path, O_PATH|O_CLOEXEC); + if (fd == -1) + return 0; + + char *fdinfo; + if (asprintf(&fdinfo, "/proc/self/fdinfo/%d", fd) == -1) + errExit("asprintf"); + EUID_ROOT(); + FILE *fp = fopen(fdinfo, "re"); + EUID_USER(); + if (!fp) + goto errexit; + // go to the last line + char buf[MAX_BUF]; + while (fgets(buf, MAX_BUF, fp)); + fclose(fp); + close(fd); + // go to the mount id + if (strncmp(buf, "mnt_id:", 7) != 0) + goto errexit; + char *ptr = buf + 7; + while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) { + ptr++; + } + if (*ptr == '\0') + goto errexit; + free(fdinfo); + + return atoi(ptr); errexit: - fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); + fprintf(stderr, "Error: cannot read file in /proc/self/fdinfo\n"); exit(1); } + +// Return array with all paths that might need a remount. +char **get_all_mounts(const int mountid, const char *path) { + // open /proc/self/mountinfo + FILE *fp = fopen("/proc/self/mountinfo", "re"); + if (!fp) { + perror("fopen"); + fprintf(stderr, "Error: cannot read /proc/self/mountinfo\n"); + exit(1); + } + + size_t size = 32; + size_t cnt = 0; + char **rv = malloc(size * sizeof(*rv)); + if (!rv) + errExit("malloc"); + + // read /proc/self/mountinfo + size_t pathlen = strlen(path); + int found = 0; + while (fgets(mbuf, MAX_BUF, fp)) { + // find mount point with mount id + if (!found) { + parse_line(mbuf, &mdata); + if (mdata.mountid == mountid) { + // don't remount blacklisted paths, + // give up if mount id has been reassigned + if (strstr(mdata.fsname, "firejail.ro.dir") || + strstr(mdata.fsname, "firejail.ro.file") || + strncmp(mdata.dir, path, strlen(mdata.dir))) + break; + + *rv = strdup(path); + if (*rv == NULL) + errExit("strdup"); + cnt++; + found = 1; + continue; + } + else + continue; + } + // from here on add all mount points below path + parse_line(mbuf, &mdata); + if (strncmp(mdata.dir, path, pathlen) == 0 && + mdata.dir[pathlen] == '/' && + strstr(mdata.fsname, "firejail.ro.dir") == NULL && + strstr(mdata.fsname, "firejail.ro.file") == NULL) { + + if (cnt >= size) { + size *= 2; + rv = realloc(rv, size * sizeof(*rv)); + if (!rv) + errExit("realloc"); + } + rv[cnt] = strdup(mdata.dir); + if (!rv[cnt]) + errExit("strdup"); + cnt++; + } + } + if (cnt == size) { + size++; + rv = realloc(rv, size * sizeof(*rv)); + if (!rv) + errExit("realloc"); + } + rv[cnt] = NULL; // end of the array + + fclose(fp); + return rv; +} 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 @@ #include "firejail.h" #include #include +#include #include #include #include @@ -82,10 +83,8 @@ void pulseaudio_init(void) { // create the new user pulseaudio directory if (mkdir(RUN_PULSE_DIR, 0700) == -1) errExit("mkdir"); - // make it a mount point and add mount flags - if (mount(RUN_PULSE_DIR, RUN_PULSE_DIR, NULL, MS_BIND, NULL) < 0 || - mount(NULL, RUN_PULSE_DIR, NULL, MS_NOEXEC|MS_NODEV|MS_NOSUID|MS_BIND|MS_REMOUNT, NULL) < 0) - errExit("mount RUN_PULSE_DIR"); + // mount it nosuid, noexec, nodev + fs_noexec(RUN_PULSE_DIR); // create the new client.conf file char *pulsecfg = NULL; @@ -189,7 +188,12 @@ void pulseaudio_init(void) { // confirm the actual mount destination is owned by the user if (fstat(fd, &s) == -1 || s.st_uid != getuid()) errExit("fstat"); - + // preserve a read-only mount + struct statvfs vfs; + if (fstatvfs(fd, &vfs) == -1) + errExit("fstatvfs"); + if ((vfs.f_flag & MS_RDONLY) == MS_RDONLY) + fs_rdonly(RUN_PULSE_DIR); // mount via the link in /proc/self/fd char *proc; 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 @@ #include "firejail.h" #include #include +#include #include #include #include @@ -1163,6 +1164,9 @@ void x11_xorg(void) { unlink(tmpfname); umount("/tmp"); + // remount RUN_XAUTHORITY_SEC_FILE noexec, nodev, nosuid + fs_noexec(RUN_XAUTHORITY_SEC_FILE); + // Ensure there is already a file in the usual location, so that bind-mount below will work. char *dest; if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1) @@ -1184,6 +1188,12 @@ void x11_xorg(void) { fprintf(stderr, "Error: .Xauthority is not a user owned regular file\n"); exit(1); } + // preserve a read-only mount + struct statvfs vfs; + if (fstatvfs(fd, &vfs) == -1) + errExit("fstatvfs"); + if ((vfs.f_flag & MS_RDONLY) == MS_RDONLY) + fs_rdonly(RUN_XAUTHORITY_SEC_FILE); // mount via the link in /proc/self/fd char *proc; -- cgit v1.2.3-54-g00ecf From ccb5f36da044a9034c38c4da4215222ad52f4648 Mon Sep 17 00:00:00 2001 From: smitsohu Date: Thu, 25 Oct 2018 23:09:54 +0200 Subject: fix: return with euid 0 --- src/firejail/fs.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/firejail/fs.c b/src/firejail/fs.c index 46124c482..6fe9d56aa 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c @@ -485,8 +485,10 @@ void fs_rdonly_rec(const char *dir) { EUID_USER(); // get mount point of the directory int mountid = get_mount_id(dir); - if (mountid == 0) + if (mountid == 0) { + EUID_ROOT(); return; + } // build array with all mount points that need to get remounted char **arr = get_all_mounts(mountid, dir); assert(arr); @@ -541,8 +543,10 @@ static void fs_rdwr_rec(const char *dir) { EUID_USER(); // get mount point of the directory int mountid = get_mount_id(dir); - if (mountid == 0) + if (mountid == 0) { + EUID_ROOT(); return; + } // build array with all mount points that need to get remounted char **arr = get_all_mounts(mountid, dir); assert(arr); @@ -585,8 +589,10 @@ void fs_noexec_rec(const char *dir) { EUID_USER(); // get mount point of the directory int mountid = get_mount_id(dir); - if (mountid == 0) + if (mountid == 0) { + EUID_ROOT(); return; + } // build array with all mount points that need to get remounted char **arr = get_all_mounts(mountid, dir); assert(arr); -- cgit v1.2.3-54-g00ecf