Multiple Vulnerabilities in ntfs-3g NTFS Mount Tool =================================================== Advisory: UNPAR-2022-0 Component: ntfs-3g Vendor: https://github.com/tuxera/ntfs-3g Version(s): Up to release 2021.8.22 Weakness(es): Write-what-where Condition (CWE-123) Unexpected Status Code or Return Value (CWE-394) Numeric Range Comparison Without Minimum Check (CWE-839) CVE: CVE-2022-30783 CVE-2022-30785 CVE-2022-30787 CVSS: Base score 7.8: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/ S:U/C:H/I:H/A:H/E:H/RL:O/RC:C/CR:L/IR:L/AR:L/ MAV:L/MAC:L/MPR:L/MUI:N/MS:U/MC:H/MI:H/MA:H Author: Roman Fiedler Summary: ======== NTFS-3G is an open-source cross-platform implementation of the Microsoft Windows NTFS file system with read/write support. NTFS-3G often uses the FUSE file system interface, so it can run unmodified on many different operating systems. [1] The "ntfs-3g" SUID binary allows unprivileged users to mount a NTFS file system via FUSE to a directory the user owns. A logic flaw in the "help" option parsing grants an attacker access to the FUSE file descriptor intended to communicate with the kernel via "/dev/fuse". Combining the file descriptor access with an integer overflow and other memory access flaws provides full read-write access to memory, circumventing ASLR and thus arbitrary code execution. Timeline: * 20220503: Contact with Debian Package Maintainer, Debian Security and upstream maintainer; vulnerability analysis, exploit and patch suggestion handed over * 20220504: Maintainer requested CRD to be handled by Tuxera Inc. * 20220509: PoC verified by Tuxera Inc., more memory issues found, CVE(s) were requested * 20220526: CRD of patches, advisory [2] * 20220607: Advisory with PoC published Details: ======== * Logic flaw with "--help": The function "fuse_kern_mount" from "libfuse-lite/mount.c" is expected to return a FUSE file descriptor on success or -1 to indicate an error. With the "--help" command line option, "mo.ishelp" is true, thus res=0 is returned instead of acquiring a FUSE file descriptor by calling "fusermount" instead. Thus "ntfs-3g" assumes to communicate with the kernel using the unrelated file descriptor 0 (stdin). int fuse_kern_mount(const char *mountpoint, struct fuse_args *args) { ... int res = -1; ... res = 0; if (mo.ishelp) goto out; ... res = fusermount(0, 0, 0, mnt_opts ? mnt_opts : "", mountpoint); ... out: ... return res; * Integer underflow in "readdir": The function "fuse_lib_readdir" in "libfuse-lite/fuse.c" is used to report directory entries to the kernel, which will then be shown as content of the mounted fuse file system. It was not checked if this code can also be reached without the previous "--help" flaw, e.g. having NTFS images with large number of entries, malformed NTFS images or concurrent "readdir" and directory or NTFS image modification. Combined with the previous vulnerability, the functionality to resume directory reads at a given offset can be abused to read memory at negative offsets. static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *llfi) ... if (dh->filled) { if (off < dh->len) { if (off + size > dh->len) size = dh->len - off; } else size = 0; } else { size = dh->len; off = 0; } ... As "ntfs-3g" memory returned by "fuse_lib_readdir" is not copied but sent using "writev", no SEGV is triggered for unmapped addresses. "ntfs-3g" will see an EFAULT error, which is even reported but otherwise ignored: fuse: writing device: Bad address * Using the memory address of directory structures as directory handle: "ntfs-3g" uses the internal memory address of "struct fuse_dh" as a directory handle to allow the kernel to identify directories on the FUSE protocol. This is quite definitely no vulnerability by itself, as there seems really no way to control this value from userspace. Other bugs or severe logic flaws seem mandatory to access the relevant code. The function "fuse_lib_opendir" from "libfuse-lite/fuse.c" will return the heap address of "struct fuse_dh" as directory handle: static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *llfi) { ... dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh)); if (dh == NULL) { reply_err(req, -ENOMEM); return; } memset(dh, 0, sizeof(struct fuse_dh)); dh->fuse = f; dh->contents = NULL; dh->len = 0; dh->filled = 0; dh->nodeid = ino; fuse_mutex_init(&dh->lock); llfi->fh = (uintptr_t) dh; ... In directory related operations the kernel will then use the file/directory handle to identify the directory, e.g. in the function "fuse_lib_readdir" from "libfuse-lite/fuse.c": static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *llfi) ... struct fuse_dh *dh = get_dirhandle(llfi, &fi); Applying standard exploitation techniques therefore allow memory read and subsequently also write at arbitrary user specified addresses. Impact: ======= Exploitation of the vulnerabilities allows privileged code execution with UID 0. For Debian Bullseye a reliable ASLR-aware PoC exists, for PoC code and details on exploitation see [3]. Vulnerable Systems: =================== Vulnerable systems contain the "/bin/ntfs-3g" with SUID bit set. On Debian Bullseye the SUID bit is set by "postinstall" during Debian package installation. To test for the flaw, use following commands, and check for the existance of the "short read on fuse device" in the output: $ dd if=/dev/zero bs=2M count=1 of=image ... $ /sbin/mkfs.ntfs --force image ... $ /bin/ntfs-3g -o --help,no_detach image dir-dont-care < /dev/null ... short read on fuse device ... Mitigation: =========== On unpatched systems the SUID bit should be removed from "ntfs-3g". There might be a way recommended by Tuxera to use it without the additional SUID privileges as there is a message embedded in the binary, but the link does not seem functional any more. write(2, "User doesn't have privilege to mount. For more information\nplease see: http://tuxera.com/community/ntfs-3g-faq/#unprivileged\n", 125) = 125 Fix: ==== See published upstream security advisory [2] and patches (version 2022.5.17) [4] [5] for full program code changes. * Logic flaw with "--help": Return an error code (number below zero) when handling the "--help" option as the caller of the vulnerable function expects a FUSE file descriptor on success as return value. * Integer underflow in "readdir": Do not allow reading the buffer at negative offsets by refusing to accept negative offset values. * Using the memory address of directory structures as directory handle: As a hardening measure the direct and unchecked use of user space addresses as file handles could be avoided. Instead file handles could be indexes into a table of open files, checking that the index is sane. Flaws would then only cause unintended operations on other open resources and not arbitrary memory access. Proof of Concept: ================= The "help-to-heap" program demonstrates ASLR-aware exploitation of the combined vulnerabilities on Debian Bullseye. For other binary versions, the the delta from the "mknod" fuse function pointer to a "dlopen" call has to be calculated. For PoC code and details on exploitation see [3]. $ ./help-to-heap ... fuse: writing device: Bad address * returning 0xf0a0 bytes Maybe struct match dir 0x55a0bef3f580 with content 0x55a0bef41000 test 0x55a0bef3f580 Assuming heap start at 0x55a0bef32000 with 0xf0a0 bytes data extracted Got fuse_fs address 0x55a0bef3f030. Got mknod op: 0x55a0bd3adcb0 New address 0x55a0bd3ba6a0 Type shell commands: id uid=0(root) gid=100(users) groups=100(users) ... Discussion: =========== There might be some take aways from these vulnerabilities: * In SUID (privileged) context everything is security critical: Even such basic function as "--help" just writing to stdout/stderr has to implemented carefully to ensure that there are no unexpected side effects on the whole program. * Apply highest coding standards to all code pathes: Functions that were never seen as part of the attack surface as they should only be invoked with sane parameters, privileged user or kernel, ... might get accessible due to bugs and then become the new last line of defence against compromised. * Minimize privileged code by program logic changes: Key to exploitation of "ntfs-3g" was the fact, that the program never fully dropped all privileges but only temporary changed EUID to the unprivileged user UID. The reason for that seems to be, that the same process was used to mount the NTFS image, serve the image data via FUSE and umount in the end. Therefore nearly all program functions, from NTFS image parsing to FUSE protocol handling are somehow part of the attack surface. By changing the program logic, that could be avoided, e.g. by forking and only the privileged parent process performing mount/umount operations while the child irreversibly drops all privileges before starting NTFS image parsing and FUSE protocol. * Minimize privileged code by code reuse: A better solution could be to drop all SUID functionality completely as there exists another SUID tool exactly specialised in secure mounting and umounting for userspace file systems: "fusermount". It is usually already part of the attack surface on machines with FUSE support installed, hence using the very same tool avoids a similar attack surface in "ntfs-3g". Thus UNIX-style code deduplication by use of small (command line) tools, that also reduces the amount of security critical code to be maintained and therefore waste of human and IT resources, which may also improve the environmental impact of open source software. Credits: ======== * Tuxera Inc. (code review, patching, testing, CRD handling) Revision History: ================= * 20220607: UNPAR-2022-0 advisory released References: =========== [1] https://en.wikipedia.org/wiki/NTFS-3G [2] https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-6mv4-4v73-xw58 [3] https://unparalleled.eu/blog/2022/20220607-help-to-heap-suid-privilege-escalation/ [4] Help option patch https://github.com/tuxera/ntfs-3g/commit/7f81935f32e58e8fec22bc46683b1b067469405f [5] Readdir offset check https://github.com/tuxera/ntfs-3g/commit/fb28eef6f1c26170566187c1ab7dc913a13ea43c