On Tue, Jul 24, 2018 at 01:12:50PM +0200, Salvatore Bonaccorso wrote: > I have prepared an update for stretch (not yet released), although as > said, its a problem only with active SELinux, which is not by default > in Debian.
Attaching debdiff. Regards, Salvatore
diff -Nru fuse-2.9.7/debian/changelog fuse-2.9.7/debian/changelog --- fuse-2.9.7/debian/changelog 2016-06-23 20:54:56.000000000 +0200 +++ fuse-2.9.7/debian/changelog 2018-07-23 20:50:41.000000000 +0200 @@ -1,3 +1,11 @@ +fuse (2.9.7-1+deb9u1) stretch-security; urgency=high + + * Non-maintainer upload by the Security Team. + * Restriction bypass of the "allow_other" option when SELinux is active + (CVE-2018-10906) + + -- Salvatore Bonaccorso <[email protected]> Mon, 23 Jul 2018 20:50:41 +0200 + fuse (2.9.7-1) unstable; urgency=low * New upstream release. diff -Nru fuse-2.9.7/debian/patches/CVE-2018-10906/0001-fusermount-prevent-silent-truncation-of-mount-option.patch fuse-2.9.7/debian/patches/CVE-2018-10906/0001-fusermount-prevent-silent-truncation-of-mount-option.patch --- fuse-2.9.7/debian/patches/CVE-2018-10906/0001-fusermount-prevent-silent-truncation-of-mount-option.patch 1970-01-01 01:00:00.000000000 +0100 +++ fuse-2.9.7/debian/patches/CVE-2018-10906/0001-fusermount-prevent-silent-truncation-of-mount-option.patch 2018-07-23 20:50:41.000000000 +0200 @@ -0,0 +1,98 @@ +From: Jann Horn <[email protected]> +Date: Fri, 13 Jul 2018 14:51:17 -0700 +Subject: [1/5] fusermount: prevent silent truncation of mount options +Origin: https://github.com/libfuse/commit/34c62ee90c69b07998629f6b5a06ab0120be681c +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2018-10906 +Bug: https://github.com/libfuse/libfuse/pull/268 + +Currently, in the kernel, copy_mount_options() copies in one page of +userspace memory (or less if some of that memory area is not mapped). +do_mount() then writes a null byte to the last byte of the copied page. +This means that mount option strings longer than PAGE_SIZE-1 bytes get +truncated silently. + +Therefore, this can happen: + +user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4000')" mount +sending file descriptor: Bad file descriptor +user@d9-ut:~$ grep /mount /proc/mounts +/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0 +user@d9-ut:~$ fusermount -u mount +user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4050')" mount +sending file descriptor: Bad file descriptor +user@d9-ut:~$ grep /mount /proc/mounts +/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=100 0 0 +user@d9-ut:~$ fusermount -u mount +user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4051')" mount +sending file descriptor: Bad file descriptor +user@d9-ut:~$ grep /mount /proc/mounts +/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=10 0 0 +user@d9-ut:~$ fusermount -u mount +user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4052')" mount +sending file descriptor: Bad file descriptor +user@d9-ut:~$ grep /mount /proc/mounts +/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1 0 0 +user@d9-ut:~$ fusermount -u mount + +I'm not aware of any context in which this is actually exploitable - you'd +still need the UIDs to fit, and you can't do it if the three GIDs of the +process don't match (in the case of a typical setgid binary), but it does +look like something that should be fixed. + +I also plan to try to get this fixed on the kernel side. +[carnil: Refresh patch to apply cleanly for context in 2.9.7] +--- + util/fusermount.c | 23 ++++++++++++++++++++--- + 1 file changed, 20 insertions(+), 3 deletions(-) + +--- a/util/fusermount.c ++++ b/util/fusermount.c +@@ -712,6 +712,23 @@ static int get_string_opt(const char *s, + return 1; + } + ++/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters. ++ * This can be dangerous if it e.g. truncates the option "group_id=1000" to ++ * "group_id=1". ++ * This wrapper detects this case and bails out with an error. ++ */ ++static int mount_notrunc(const char *source, const char *target, ++ const char *filesystemtype, unsigned long mountflags, ++ const char *data) { ++ if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) { ++ fprintf(stderr, "%s: mount options too long\n", progname); ++ errno = EINVAL; ++ return -1; ++ } ++ return mount(source, target, filesystemtype, mountflags, data); ++} ++ ++ + static int do_mount(const char *mnt, char **typep, mode_t rootmode, + int fd, const char *opts, const char *dev, char **sourcep, + char **mnt_optsp, off_t rootsize) +@@ -836,7 +853,7 @@ static int do_mount(const char *mnt, cha + else + strcpy(source, subtype ? subtype : dev); + +- res = mount(source, mnt, type, flags, optbuf); ++ res = mount_notrunc(source, mnt, type, flags, optbuf); + if (res == -1 && errno == ENODEV && subtype) { + /* Probably missing subtype support */ + strcpy(type, blkdev ? "fuseblk" : "fuse"); +@@ -847,13 +864,13 @@ static int do_mount(const char *mnt, cha + strcpy(source, type); + } + +- res = mount(source, mnt, type, flags, optbuf); ++ res = mount_notrunc(source, mnt, type, flags, optbuf); + } + if (res == -1 && errno == EINVAL) { + /* It could be an old version not supporting group_id */ + sprintf(d, "fd=%i,rootmode=%o,user_id=%u", + fd, rootmode, getuid()); +- res = mount(source, mnt, type, flags, optbuf); ++ res = mount_notrunc(source, mnt, type, flags, optbuf); + } + if (res == -1) { + int errno_save = errno; diff -Nru fuse-2.9.7/debian/patches/CVE-2018-10906/0002-fusermount-don-t-feed-escaped-commas-into-mount-opti.patch fuse-2.9.7/debian/patches/CVE-2018-10906/0002-fusermount-don-t-feed-escaped-commas-into-mount-opti.patch --- fuse-2.9.7/debian/patches/CVE-2018-10906/0002-fusermount-don-t-feed-escaped-commas-into-mount-opti.patch 1970-01-01 01:00:00.000000000 +0100 +++ fuse-2.9.7/debian/patches/CVE-2018-10906/0002-fusermount-don-t-feed-escaped-commas-into-mount-opti.patch 2018-07-23 20:50:41.000000000 +0200 @@ -0,0 +1,49 @@ +From: Jann Horn <[email protected]> +Date: Fri, 13 Jul 2018 15:15:36 -0700 +Subject: [2/5] fusermount: don't feed "escaped commas" into mount options +Origin: https://github.com/libfuse/commit/28bdae3d113ef479c1660a581ef720cdc33bf466 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2018-10906 +Bug: https://github.com/libfuse/libfuse/pull/268 + +The old code permits the following behavior: + +$ _FUSE_COMMFD=10000 priv_strace -etrace=mount -s200 fusermount -o 'foobar=\,allow_other' mount +mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "foobar=\\,allow_other,fd=3,rootmode=40000,user_id=1000,group_id=1000") = -1 EINVAL (Invalid argument) + +However, backslashes do not have any special meaning for the kernel here. + +As it happens, you can't abuse this because there is no FUSE mount option +that takes a string value that can contain backslashes; but this is very +brittle. Don't interpret "escape characters" in places where they don't +work. +--- + util/fusermount.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/util/fusermount.c b/util/fusermount.c +index 0e1d34d..143bd4a 100644 +--- a/util/fusermount.c ++++ b/util/fusermount.c +@@ -29,6 +29,7 @@ + #include <sys/socket.h> + #include <sys/utsname.h> + #include <sched.h> ++#include <stdbool.h> + + #define FUSE_COMMFD_ENV "_FUSE_COMMFD" + +@@ -754,8 +755,10 @@ static int do_mount(const char *mnt, char **typep, mode_t rootmode, + unsigned len; + const char *fsname_str = "fsname="; + const char *subtype_str = "subtype="; ++ bool escape_ok = begins_with(s, fsname_str) || ++ begins_with(s, subtype_str); + for (len = 0; s[len]; len++) { +- if (s[len] == '\\' && s[len + 1]) ++ if (escape_ok && s[len] == '\\' && s[len + 1]) + len++; + else if (s[len] == ',') + break; +-- +2.18.0 + diff -Nru fuse-2.9.7/debian/patches/CVE-2018-10906/0003-fusermount-bail-out-on-transient-config-read-failure.patch fuse-2.9.7/debian/patches/CVE-2018-10906/0003-fusermount-bail-out-on-transient-config-read-failure.patch --- fuse-2.9.7/debian/patches/CVE-2018-10906/0003-fusermount-bail-out-on-transient-config-read-failure.patch 1970-01-01 01:00:00.000000000 +0100 +++ fuse-2.9.7/debian/patches/CVE-2018-10906/0003-fusermount-bail-out-on-transient-config-read-failure.patch 2018-07-23 20:50:41.000000000 +0200 @@ -0,0 +1,50 @@ +From: Jann Horn <[email protected]> +Date: Fri, 13 Jul 2018 15:50:50 -0700 +Subject: [3/5] fusermount: bail out on transient config read failure +Origin: https://github.com/libfuse/commit/cc315f5aa7fae04e16dda419859b2995992977cd +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2018-10906 +Bug: https://github.com/libfuse/libfuse/pull/268 + +If an attacker wishes to use the default configuration instead of the +system's actual configuration, they can attempt to trigger a failure in +read_conf(). This only permits increasing mount_max if it is lower than the +default, so it's not particularly interesting. Still, this should probably +be prevented robustly; bail out if funny stuff happens when we're trying to +read the config. + +Note that the classic attack trick of opening so many files that the +system-wide limit is reached won't work here - because fusermount only +drops the fsuid, not the euid, the process is running with euid=0 and +CAP_SYS_ADMIN, so it bypasses the number-of-globally-open-files check in +get_empty_filp() (unless you're inside a user namespace). +--- + util/fusermount.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/util/fusermount.c b/util/fusermount.c +index 143bd4a..4e0f51a 100644 +--- a/util/fusermount.c ++++ b/util/fusermount.c +@@ -565,10 +565,19 @@ static void read_conf(void) + fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF); + + } ++ if (ferror(fp)) { ++ fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF); ++ exit(1); ++ } + fclose(fp); + } else if (errno != ENOENT) { ++ bool fatal = (errno != EACCES && errno != ELOOP && ++ errno != ENAMETOOLONG && errno != ENOTDIR && ++ errno != EOVERFLOW); + fprintf(stderr, "%s: failed to open %s: %s\n", + progname, FUSE_CONF, strerror(errno)); ++ if (fatal) ++ exit(1); + } + } + +-- +2.18.0 + diff -Nru fuse-2.9.7/debian/patches/CVE-2018-10906/0004-fusermount-refuse-unknown-options.patch fuse-2.9.7/debian/patches/CVE-2018-10906/0004-fusermount-refuse-unknown-options.patch --- fuse-2.9.7/debian/patches/CVE-2018-10906/0004-fusermount-refuse-unknown-options.patch 1970-01-01 01:00:00.000000000 +0100 +++ fuse-2.9.7/debian/patches/CVE-2018-10906/0004-fusermount-refuse-unknown-options.patch 2018-07-23 20:50:41.000000000 +0200 @@ -0,0 +1,45 @@ +From: Jann Horn <[email protected]> +Date: Sat, 14 Jul 2018 03:47:50 -0700 +Subject: [4/5] fusermount: refuse unknown options +Origin: https://github.com/libfuse/commit/5018a0c016495155ee598b7e0167b43d5d902414 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2018-10906 +Bug: https://github.com/libfuse/libfuse/pull/268 + +Blacklists are notoriously fragile; especially if the kernel wishes to add +some security-critical mount option at a later date, all existing systems +with older versions of fusermount installed will suddenly have a security +problem. +Additionally, if the kernel's option parsing became a tiny bit laxer, the +blacklist could probably be bypassed. + +Whitelist known-harmless flags instead, even if it's slightly more +inconvenient. +--- + util/fusermount.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/util/fusermount.c b/util/fusermount.c +index 4e0f51a..2792407 100644 +--- a/util/fusermount.c ++++ b/util/fusermount.c +@@ -819,10 +819,16 @@ static int do_mount(const char *mnt, char **typep, mode_t rootmode, + flags |= flag; + else + flags &= ~flag; +- } else { ++ } else if (opt_eq(s, len, "default_permissions") || ++ opt_eq(s, len, "allow_other") || ++ begins_with(s, "max_read=") || ++ begins_with(s, "blksize=")) { + memcpy(d, s, len); + d += len; + *d++ = ','; ++ } else { ++ fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s); ++ exit(1); + } + } + } +-- +2.18.0 + diff -Nru fuse-2.9.7/debian/patches/CVE-2018-10906/0005-fusermount-whitelist-known-good-filesystems-for-moun.patch fuse-2.9.7/debian/patches/CVE-2018-10906/0005-fusermount-whitelist-known-good-filesystems-for-moun.patch --- fuse-2.9.7/debian/patches/CVE-2018-10906/0005-fusermount-whitelist-known-good-filesystems-for-moun.patch 1970-01-01 01:00:00.000000000 +0100 +++ fuse-2.9.7/debian/patches/CVE-2018-10906/0005-fusermount-whitelist-known-good-filesystems-for-moun.patch 2018-07-23 20:50:41.000000000 +0200 @@ -0,0 +1,124 @@ +From: Jann Horn <[email protected]> +Date: Sat, 14 Jul 2018 13:37:41 +0200 +Subject: [5/5] fusermount: whitelist known-good filesystems for mountpoints +Origin: https://github.com/libfuse/commit/795ad5d77434f3502e63a70c8a3fda94fa347e3d +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2018-10906 +Bug: https://github.com/libfuse/libfuse/pull/268 + +Before: + + $ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd + mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0 + sending file descriptor: Socket operation on non-socket + +++ exited with 1 +++ + +After: + + $ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd + util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden + +++ exited with 1 +++ + +This patch could potentially have security +impact on some systems that are configured with allow_other; +see https://launchpad.net/bugs/1530566 for an example of how a similar +issue in the ecryptfs mount helper was exploitable. However, the FUSE +mount helper performs slightly different security checks, so that exact +attack doesn't work with fusermount; I don't know of any specific attack +you could perform using this, apart from faking the SELinux context of your +process when someone's looking at a process listing. Potential targets for +overwrite are (looking on a system with a 4.9 kernel): + +writable only for the current process: +/proc/self/{fd,map_files} +(Yes, "ls -l" claims that you don't have write access, but that's not true; +"find -writable" will show you what access you really have.) + +writable also for other owned processes: +/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj, +oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map, +setgroups,timerslack_ns} +[carnil: Adapt commit message to indent +++ lines to avoid missapplying/errors +when applying with quilt] +--- + util/fusermount.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 49 insertions(+), 1 deletion(-) + +diff --git a/util/fusermount.c b/util/fusermount.c +index 2792407..c63c50e 100644 +--- a/util/fusermount.c ++++ b/util/fusermount.c +@@ -30,6 +30,7 @@ + #include <sys/utsname.h> + #include <sched.h> + #include <stdbool.h> ++#include <sys/vfs.h> + + #define FUSE_COMMFD_ENV "_FUSE_COMMFD" + +@@ -915,6 +916,8 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd) + int res; + const char *mnt = *mntp; + const char *origmnt = mnt; ++ struct statfs fs_buf; ++ size_t i; + + res = lstat(mnt, stbuf); + if (res == -1) { +@@ -987,8 +990,53 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd) + return -1; + } + ++ /* Do not permit mounting over anything in procfs - it has a couple ++ * places to which we have "write access" without being supposed to be ++ * able to just put anything we want there. ++ * Luckily, without allow_other, we can't get other users to actually ++ * use any fake information we try to put there anyway. ++ * Use a whitelist to be safe. */ ++ if (statfs(*mntp, &fs_buf)) { ++ fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", ++ progname, mnt, strerror(errno)); ++ return -1; ++ } + +- return 0; ++ /* Use the same list of permitted filesystems for the mount target as ++ * the ecryptfs mount helper ++ * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225). */ ++ typeof(fs_buf.f_type) f_type_whitelist[] = { ++ 0x61756673 /* AUFS_SUPER_MAGIC */, ++ 0x9123683E /* BTRFS_SUPER_MAGIC */, ++ 0x00C36400 /* CEPH_SUPER_MAGIC */, ++ 0xFF534D42 /* CIFS_MAGIC_NUMBER */, ++ 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */, ++ 0x0000EF53 /* EXT[234]_SUPER_MAGIC */, ++ 0xF2F52010 /* F2FS_SUPER_MAGIC */, ++ 0x65735546 /* FUSE_SUPER_MAGIC */, ++ 0x01161970 /* GFS2_MAGIC */, ++ 0x3153464A /* JFS_SUPER_MAGIC */, ++ 0x000072B6 /* JFFS2_SUPER_MAGIC */, ++ 0x0000564C /* NCP_SUPER_MAGIC */, ++ 0x00006969 /* NFS_SUPER_MAGIC */, ++ 0x00003434 /* NILFS_SUPER_MAGIC */, ++ 0x5346544E /* NTFS_SB_MAGIC */, ++ 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */, ++ 0x52654973 /* REISERFS_SUPER_MAGIC */, ++ 0x73717368 /* SQUASHFS_MAGIC */, ++ 0x01021994 /* TMPFS_MAGIC */, ++ 0x24051905 /* UBIFS_SUPER_MAGIC */, ++ 0x58465342 /* XFS_SB_MAGIC */, ++ 0x2FC12FC1 /* ZFS_SUPER_MAGIC */, ++ }; ++ for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) { ++ if (f_type_whitelist[i] == fs_buf.f_type) ++ return 0; ++ } ++ ++ fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n", ++ progname, (unsigned long)fs_buf.f_type); ++ return -1; + } + + static int try_open(const char *dev, char **devp, int silent) +-- +2.18.0 + diff -Nru fuse-2.9.7/debian/patches/series fuse-2.9.7/debian/patches/series --- fuse-2.9.7/debian/patches/series 2015-06-09 21:41:34.000000000 +0200 +++ fuse-2.9.7/debian/patches/series 2018-07-23 20:50:41.000000000 +0200 @@ -4,3 +4,8 @@ 0004-fusermount-manpage.patch 0005-dlsym.patch 0006-arm64.patch +CVE-2018-10906/0001-fusermount-prevent-silent-truncation-of-mount-option.patch +CVE-2018-10906/0002-fusermount-don-t-feed-escaped-commas-into-mount-opti.patch +CVE-2018-10906/0003-fusermount-bail-out-on-transient-config-read-failure.patch +CVE-2018-10906/0004-fusermount-refuse-unknown-options.patch +CVE-2018-10906/0005-fusermount-whitelist-known-good-filesystems-for-moun.patch

