Double-Fetch
The most simple of vulnerabilities
A double-fetch vulnerability is when data is accessed from userspace multiple times. Because userspace programs will commonly pass parameters in to the kernel as pointers, the data can be modified at any time. If it is modified at the exact right time, an attacker could compromise the execution of the kernel.
A Vulnerable Kernel Module
Let's start with a convoluted example, where all we want to do is change the id
that the module stores. We are not allowed to set it to 0
, as that is the ID of root
, but all other values are allowed.
The code below will be the contents of the read()
function of a kernel. I've removed the boilerplate code mentioned previously, but here are the relevant parts:
The program will:
Check if the ID we are attempting to switch to is
0
If it is, it doesn't allow us, as we attempted to log in as root
Sleep for 1 second (this is just to illustrate the example better, we will remove it later)
Compare the password to
p4ssw0rd
If it is, it will set the
id
variable to theid
in thecreds
structure
Simple Communication
Let's say we want to communicate with the module, and we set up a simple C program to do so:
We compile this statically (as there are no shared libraries on our VM):
As expected, the id
variable gets set to 900
- we can check this in dmesg
:
That all works fine.
Exploiting a Double-Fetch and Switching to ID 0
The flaw here is that creds->id
is dereferenced twice. What does this mean? The kernel module is passed a reference to a Credentials
struct:
This is a pointer, and that is perhaps the most important thing to remember. When we interact with the module, we give it a specific memory address. This memory address holds the Credentials
struct that we define and pass to the module. The kernel does not have a copy - it relies on the user's copy, and goes to userspace memory to use it.
Because this struct is controlled by the user, they have the power to change it whenever they like.
The kernel module uses the id
field of the struct on two separate occasions. Firstly, to check that the ID we wish to swap to is valid (not 0
):
And once more, to set the id
variable:
Again, this might seem fine - but it's not. What is stopping it from changing inbetween these two uses? The answer is simple: nothing. That is what differentiates userspace exploitation from kernel space.
A Proof-of-Concept: Switching to ID 0
Inbetween the two dereferences creds->id
, there is a timeframe. Here, we have artificially extended it (by sleeping for one second). We have a race codition - the aim is to switch id
in that timeframe. If we do this successfully, we will pass the initial check (as the ID will start off as 900
), but by the time it is copied to id
, it will have become 0
and we have bypassed the security check.
Here's the plan, visually, if it helps:
In the waiting period, we swap out the id
.
If you are trying to compile your own kernel, you need CONFIG_SMP
enabled, because we need to modify it in a different thread! Additionally, you need QEMU to have the flag -smp 2
(or more) to enable 2 cores, though it may default to having multiple even without the flag. This example may work without SMP, but that's because of the sleep - when we most onto part 2, with no sleep, we require multiple cores.
The C program will hang on write
until the kernel module returns, so we can't use the main thread.
With that in mind, the "exploit" is fairly self-explanatory - we start another thread, wait 0.3 seconds, and change id
!
We have to compile it statically, as the VM has no shared libraries.
Now we have to somehow get it into the file system. In order to do that, we need to first extract the .cpio
archive (you may want to do this in another folder):
Now copy exploit
there and make sure it's marked executable. You can then compress the filesystem again:
Use the newly-created initramfs.cpio
to lauch the VM with run.sh
. Executing exploit
, it is successful!
Note that the VM loaded you in as root
by default. This is for debugging purposes, as it allows you to use utilities such as dmesg
to read the kernel module output and check for errors, as well as a host of other things we will talk about. When testing exploits, it's always helpful to fix the init
script to load you in as root! Just don't forget to test it as another user in the end.
Last updated