ntfs-3g: modprobe is executed with unsanitized environment
Project Member Reported by firstname.lastname@example.org, Jan 5 2017
ntfs-3g is installed by default e.g. on Ubuntu and comes with a setuid root program /bin/ntfs-3g. When this program is invoked on a system whose kernel does not support FUSE filesystems (detected by get_fuse_fstype()), ntfs-3g attempts to load the "fuse" module using /sbin/modprobe via load_fuse_module(). The issue is that /sbin/modprobe is not designed to run in a setuid context. As the manpage of modprobe explicitly points out: The MODPROBE_OPTIONS environment variable can also be used to pass arguments to modprobe. Therefore, on a system that does not seem to support FUSE filesystems, an attacker can set the environment variable MODPROBE_OPTIONS to something like "-C /tmp/evil_config -d /tmp/evil_root" to force modprobe to load its configuration and the module from attacker-controlled directories. This allows a local attacker to load arbitrary code into the kernel. In practice, the FUSE module is usually already loaded. However, the issue can still be attacked because a failure to open /proc/filesystems (meaning that get_fuse_fstype() returns FSTYPE_UNKNOWN) always causes modprobe to be executed, even if the FUSE module is already loaded. An attacker can cause an attempt to open /proc/filesystems to fail by exhausting the global limit on the number of open file descriptions (/proc/sys/fs/file-max). I have attached an exploit for the issue. I have tested it in a VM with Ubuntu Server 16.10. To reproduce, unpack the attached file, compile the exploit and run it: user@ubuntu:~$ tar xf ntfs-3g-modprobe-unsafe.tar user@ubuntu:~$ cd ntfs-3g-modprobe-unsafe/ user@ubuntu:~/ntfs-3g-modprobe-unsafe$ ./compile.sh make: Entering directory '/usr/src/linux-headers-4.8.0-32-generic' CC [M] /home/user/ntfs-3g-modprobe-unsafe/rootmod.o Building modules, stage 2. MODPOST 1 modules CC /home/user/ntfs-3g-modprobe-unsafe/rootmod.mod.o LD [M] /home/user/ntfs-3g-modprobe-unsafe/rootmod.ko make: Leaving directory '/usr/src/linux-headers-4.8.0-32-generic' depmod: WARNING: could not open /home/user/ntfs-3g-modprobe-unsafe/depmod_tmp//lib/modules/4.8.0-32-generic/modules.order: No such file or directory depmod: WARNING: could not open /home/user/ntfs-3g-modprobe-unsafe/depmod_tmp//lib/modules/4.8.0-32-generic/modules.builtin: No such file or directory user@ubuntu:~/ntfs-3g-modprobe-unsafe$ ./sploit looks like we won the race got ENFILE at 198088 total Failed to open /proc/filesystems: Too many open files in system yay, modprobe ran! modprobe: ERROR: ../libkmod/libkmod.c:514 lookup_builtin_file() could not open builtin file '/tmp/ntfs_sploit.u48sGO/lib/modules/4.8.0-32-generic/modules.builtin.bin' modprobe: ERROR: could not insert 'rootmod': Too many levels of symbolic links Error opening '/tmp/ntfs_sploit.u48sGO/volume': Is a directory Failed to mount '/tmp/ntfs_sploit.u48sGO/volume': Is a directory we have root privs now... root@ubuntu:~/ntfs-3g-modprobe-unsafe# id uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lxd),123(libvirt),127(sambashare),128(lpadmin),1000(user) Note: The exploit seems to work relatively reliably in VMs with multiple CPU cores, but not in VMs with a single CPU core. If you test this exploit in a VM, please ensure that the VM has at least two CPU cores. This bug is subject to a 90 day disclosure deadline. If 90 days elapse without a broadly available patch, then the bug report will automatically become visible to the public.
Jan 5 2017,
The vendor has created a patch that provides modprobe with an empty environment. The patch looks good.
Feb 11 2017,
Derestricting: While I don't see a new release on the vendor's website (http://www.tuxera.com/community/open-source-ntfs-3g/), this bug was posted on oss-security by Laszlo Boszormenyi (http://seclists.org/oss-sec/2017/q1/259). (Someone also posted an exploit for the bug; however, unlike my exploit, that exploit probably won't work if the FUSE kernel module is already loaded.)
Feb 23 2017,
@jannh -- what makes you think my exploit doesn't work when fuse is already loaded? :) Tested on standard debian 9 and works out of the box for me. Did you see the trick I used to bypass what you think wouldn't be possible? I believe you should be able to reproduce as well.
Feb 23 2017,
@kristian...: I just installed Debian 9 in a VM, and FUSE is not already loaded: "grep fuse /proc/filesystems" shows nothing. With e.g. Ubuntu Server 16.10, that command does print output. The slightly tricky part of my exploit is to bypass the get_fuse_fstype() check in ntfs-3g if the FUSE driver is already loaded. As far as I can tell, you don't need this bypass on a freshly booted Debian 9 machine because the FUSE driver is only loaded on demand in Debian 9. Note that the presence of /dev/fuse does not imply that the fuse module is loaded; however, attempting to open that device should trigger module autoloading if necessary. Does your exploit still work if the "fuse" module has been autoloaded (e.g. via "cat /dev/fuse")? Or does it work on any Linux system where FUSE support is compiled into the kernel (e.g. Ubuntu Server 16.10)?
Feb 24 2017,
Jann -- so you have also confirmed my exploit works on Debian 9 stretch, thanks! You are correct that my exploit does NOT work on other versions of debian or Ubuntu due to fuse already being loaded. I had not tested on other distros and so as you mentioned, your exploit is able to bypass that check on ubuntu, which is neat. The "trick" I was referring to was the symlink traversal and to the aliased module name, which as you correctly identified, does not actually bypass the ntfs-3g code, so I was mistaken. Your symlink and file exhaustion attack does work on Ubuntu to bypass the fuse loaded check; very nice. Thanks again for confirming everything. Looks like my exploit only works on Debian 9 stretch due to the fact that fuse is not loaded by default. If you can confirm all this that would be great. Cheers.
Jun 2 2017,
Nov 5 2017,
Hi, Is problem associated with NTFS-3G or modprobe? I think the modprobe shouldn't be accepting arguments from environment variables. What is the motivation to do so? Can we say that modprobe should not be executed in setuid environment? It anyway needs root permission to load or unload modules.
Nov 6 2017,
> I think the modprobe shouldn't be accepting arguments from environment variables. > What is the motivation to do so? It is normal for programs and even libraries to accept configuration directives from environment variables. https://codesearch.debian.net/search?q=MODPROBE_OPTIONS shows that there are Debian packages that rely on this behavior; e.g. initramfs-tools uses it to pass the "-q" flag to modprobe. The convention for executing binaries in a setuid context is that unless a binary is specifically designed for that usecase, the caller must clean up the runtime environment (in particular environment variables) before calling execve() on that binary. (The same applies to using libraries.) > Can we say that modprobe should not be executed in setuid environment? > It anyway needs root permission to load or unload modules. The whole point of setuid binaries is that they can do things that the user calling them wouldn't be able to do.
Sign in to add a comment