FUSE for Linux Exploitation 101
During the past few weeks, my friend @kiks and I started to develop an exploit for CVE-2022-2602: it’s an io_uring
UAF.
We already completed the exploit using the userfaultfd
technique to pause a kernel thread.
Unfortunately, this technique is dead (thank you, vm.unprivileged_userfaultfd
:/ ), so we started searching a good replacement for userfaultfd
.
While reading @chompie’s blogpost about io_uring
, I stumbled upon FUSE.
In this blogpost I’ll try to present to you my take on FUSE for Linux Exploitation :)
TLDR: FUSE filesystem allows users to pause a kernel thread, implementing a blocking read/write FUSE operation.
What is FUSE?
FUSE (Filesystem in USErspace) is a feature that allows users (also unprivileged users) to create fileystems in userspace.
FUSE architecture is composed of 3 main parts:
- Fuse kernel module, which forwards requests on FUSE files to the user-space callbacks.
libfuse
, which is the userspace component.fusermount
, an utility used to unmount fuse filesystems.
A simple FUSE filesystem
In order to write a simple filesystem in userspace, we need to use the fuse.h
header in C programs.
Note:
make sure you have libfuse-dev package installed on your system :)
Inside fuse.h
, the struct fuse_operations
is defined.
It contains all the function pointers to callbacks that the kernel module will use to handle different operations on FUSE files.
Another important element inside fuse.h
is the fuse_main
function.
Its signature is: fuse_main(int argc, char **argv, const struct fuse_operations *op, void *private_data)
.
This is the entry point of the filesystem: it takes the arguments from the main()
function and also a pointer to the user-defined struct fuse_operations
.
It will take care of initializing the communication with the kernel module fuse.ko
and it will wait for callbacks.
When an user issues a write to a FUSE file, the generic kernel write()
interface will determine the filesystem of the file.
If it’s a FUSE file, it will forward the write
request to the kernel module fuse.ko
, which will use the callback we defined to complete the write
request.
So, in order to create a minimal FUSE filesystem is required to:
- Define all callbacks for operations supported by the filesystem.
- Declare the
struct fuse_operations
with all function pointers - Call
fuse_main()
And that’s it!
Goodbye userfaultfd, welcome FUSE!
With userfaultfd
it’s possible to pause a kernel thread during the execution of a copy_from_user
, using an mmap()
ed chunk of memory with demand-zero paging¹.
With FUSE, an unprivileged user can create its own filesystem…how could it be exploited to pause a kernel thread?
The idea is that when a file is mmap()
ed in memory, it’s mmap()
ed using demand-zero paging as well.
This means that the first read or write to that memory will cause a read()
of the file from disk, in order to be copied into memory.
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <assert.h>
#include <string.h>
int main(){
int fd = open("fuse_dir/lol", O_RDWR);
void *addr = mmap(0x1000, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
// mmap()ed the file in demand-zero paging
printf("No read done from FUSE\n");
assert(addr != -1);
printf("Triggering read from FUSE\n");
//THIS will trigger the call to FUSE read
printf("%s\n", (char *)addr);
}
Well, since we have to handle all file operations to files in the FUSE filesystem from userspace,
this will trigger the read()
we defined in the FUSE filesystem.
We control what read()
does, so we could, for example, simply call in a for
loop sleep()
.
If a kernel thread executes a copy_from_user()
on the FUSE mmap()
ed file, this will pause the kernel thread.
#define FILE_TARGET "/lol"
static int FUSE_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi){
if(strcmp(path, FILE_TARGET) == 0){
for(;;){
printf("[+] Pausing kernel thread...\n");
sleep(100);
}
}
return 0;
}
Conclusion
Building a FUSE filesystem it’s a good alternative to userfaultfd
technique when developing kernel exploits, since userfaultfd
is not enabled by default anymore for unprivileged users on almost all major linux distributions.
Full source code of the FUSE filesystem is here
CVE-2022-2602 writeup coming soon, stay tuned! :)