In LK03(Dexter) we’ll learn about a vulnerability called Double Fetch.
Download the files for LK03 first.
QEMU boot options
In LK03 SMEP, KASLR and KPTI is enabled whereas SMAP isn’t.
Keep in mind that the bug we’ll be dealing with is related to a race condition and is executed in a multi-core environment.
The program disabled SMAP so that privilege escalation is easily achieved, even with SMAP turned on vulnerability is still effective.
#!/bin/sh
qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr" \
-no-reboot \
-cpu kvm64,+smep \
-smp 2 \
-monitor /dev/null \
-initrd rootfs.cpio \
-net nic,model=virtio \
-net user
Source Code
Let’s take a look at the source code for LK03. The source code is at src/dexter.c.
This program is a kernel module that lets you save up to 0x20 bytes of data.
You can control the program by ioctl, which allows you to read and write stuff.
#define CMD_GET 0xdec50001
#define CMD_SET 0xdec50002
...
switch (cmd) {
case CMD_GET: return copy_data_to_user(filp, (void*)arg);
case CMD_SET: return copy_data_from_user(filp, (void*)arg);
default: return -EINVAL;
}
BTW, the value for EINVAL is 22.
When the device calls open, 0x20 bytes will be allocated from kzalloc to private_data.
Closing the device will free it.
kzalloc is a kernel function that does a malloc + zeroes out the allocated bytes.
static int module_open(struct inode *inode, struct file *filp) {
filp->private_data = kzalloc(BUFFER_SIZE, GFP_KERNEL);
if (!filp->private_data) return -ENOMEM;
return 0;
}
static int module_close(struct inode *inode, struct file *filp) {
kfree(filp->private_data);
return 0;
}
When ioctl is called, verify_request will check the data the user passed.
verify_request checks whether the pointer passed by the user isn’t a NULL and that it doesn’t exceed 0x20.
int verify_request(void *reqp) {
request_t req;
if (copy_from_user(&req, reqp, sizeof(request_t)))
return -1;
if (!req.ptr || req.len > BUFFER_SIZE)
return -1;
return 0;
}
...
if (verify_request((void*)arg))
return -EINVAL;
Next by using CMD_GET or CMD_SET will copy data from private_data to the userspace or, can copy data from the userspace to the kernel.
Since it checks the size before copying data from the userspace with verity_request it looks as if there isn’t a heap buffer overflow.
Double Fetch
Double Fetch is one of the race conditions on the kernel.
It literally means a race condition that occurs when reading(fetching) the same data twice in the kernel.
When the kernel reads the same data twice in the userspace, a different thread can alter the data that at that exact moment.
The consistency breaks because the content of the data is different from the first and second fetch.
These types of inconsistent data races are called Double Fetch.
Unlike LK01, these bugs cannot be resolved even by acquiring a mutex.
The driver uses verify_request and copy_data_to_user/copy_data_from_user to fetch the data from the user.
After verify_request passes the correct size if it passes a wrong value before copy_data_to_user or copy_data_from_user gets executed we can trigger a heap buffer overflow.
When dealing with data from the userspace, you should only use the one that was initially copied to the kernel space.