DF-5302 / df5302.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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | /* * DF-5302 / DF-5303 verification harness. * * DF-5302 — NULL-pointer WRITE (kernel panic) in fwohci_db_init() * sys/bus/firewire/fwohci.c:1253 * reached from fwohci_irx_enable() fwohci.c:1606-1608 (RX) and * fwohci_itxbuf_enable() fwohci.c:1485-1487 (TX) with * dbch->ndb = xferq->bnpacket * xferq->bnchunk == 0. * * Production trigger chain (statically verified, path:line): * open("/dev/fw0.0") fwdev.c:157 * ioctl(FW_SSTBUF, {rx.nchunk=8, rx.npacket=0, rx.psize=512}) * fwdev.c:504-506 bcopy(ibufreq, &d->bufreq) -- NO validation * ioctl(FW_SRSTREAM) fwdev.c:475 * fwdev_allocbuf fwdev.c:95 * fwdma_malloc_multiseg(n = 8*0 = 0) fwdev.c:108-109 * returns a VALID am with nseg == 0 fwdma.c:160 (howmany(0,k)==0) * flag |= FWXFERQ_EXTBUF fwdev.c:134 * fc->irx_enable == fwohci_irx_enable fwohci.c:728 * dbch->ndb = 0 * 8 == 0 fwohci.c:1606 * dbch->ndesc = 2 fwohci.c:1607 * fwohci_db_init(sc, dbch) fwohci.c:1608 * for (idb = 0; idb < 0; ...) fwohci.c:1229 (skipped) * STAILQ_LAST(&dbch->db_trq, ...)->link.stqe_next = ... * fwohci.c:1253 * db_trq is EMPTY -> STAILQ_LAST == NULL (sys/sys/queue.h) * -> write at NULL + offsetof(fwohcidb_tr, link) == 0x0 * * TX twin: FW_STSTREAM (fwdev.c:447) + first fw_write() -> * fwohci_itxbuf_enable (fwohci.c:1471) -> same db_init at :1487. * * DF-5303 — DUMPDMA ioctl channel-bound off-by-one: * fwohci.c:372 `if (*dmach <= OHCI_MAX_DMA_CH)` allows ch == 0x44 * (OHCI_MAX_DMA_CH == 0x4+0x20+0x20 == 0x44, fwohcireg.h:88), but the * valid IR channel indices are 0..0x43: * dump_db(sc, 0x44): dbch = &sc->ir[0x44-0x24] = &sc->ir[0x20] * ONE PAST ir[OHCI_DMA_IRCH] (fwohcivar.h:64) -- * past the END of fwohci_softc -> adjacent heap * read (ndb/top), then a wild STAILQ walk * (fwohci.c:2271-2303 derefs unvalidated * garbage pointers). * dump_dma(sc, 0x44): OHCI_IRCTL(0x20) = 0x800 -- MMIO read past * the 2 KB OHCI register file. * * The harness compiles the PRODUCTION sys/bus/firewire/fwohci.c into the * module (via #include) so the static production functions run unchanged. * The only synthetic state is the bus_space (bst = MEM tag, bsh = a plain * kernel-memory block: on x86_64 bus_space_read_4/write_4 with the MEM * tag are direct memory accesses, sys/cpu/x86_64/include/bus_dma.h:139-162) * and the FireWire bus state itself -- no FireWire controller exists on * the QEMU guest (hardware-gated finding). * * fwohci.c's global symbols are renamed via -D in the Makefile so they do * not collide with the copy already compiled into the guest kernel * (X86_64_GENERIC has `device firewire`). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/malloc.h> #include <sys/bus.h> /* PRODUCTION source: statics (fwohci_db_init, fwohci_irx_enable, dump_db) * become callable in this translation unit. */ #include <bus/firewire/fwohci.c> static int df5302_mode = 1; /* 1 = DF-5302, 2 = DF-5303 */ static int df5302_trigger = 0; SYSCTL_INT(_kern, OID_AUTO, df5302_mode, CTLFLAG_RW, &df5302_mode, 0, "harness case: 1=DF-5302 db_init ndb=0, 2=DF-5303 dump_db ch 0x44"); SYSCTL_INT(_kern, OID_AUTO, df5302_trigger, CTLFLAG_RW, &df5302_trigger, 0, "write 1 to fire the harness case (expect a kernel panic)"); /* * DF-5302: drive the PRODUCTION fwohci_irx_enable() with exactly the * xferq state fwdev_allocbuf() leaves after FW_SSTBUF{npacket=0}. */ static void df5302_run_irx_enable(void) { struct fwohci_softc *sc; struct fw_xferq *ir; char *regs; int error; sc = kmalloc(sizeof(*sc), M_FW, M_WAITOK | M_ZERO); regs = kmalloc(0x1000, M_FW, M_WAITOK | M_ZERO); /* fake MMIO window (plain kernel memory on x86_64 non-IO tags) */ sc->bst = X86_64_BUS_SPACE_MEM; sc->bsh = (bus_space_handle_t)regs; /* state exactly as fwdev_allocbuf() + FW_SSTBUF produced it */ sc->fc.nisodma = 8; /* probed channels */ ir = &sc->ir[0].xferq; ir->flag = FWXFERQ_STREAM | FWXFERQ_EXTBUF; /* fwdev.c:133-134 */ ir->bnchunk = 8; /* FW_SSTBUF rx.nchunk */ ir->bnpacket = 0; /* FW_SSTBUF rx.npacket */ ir->psize = 512; /* FW_SSTBUF rx.psize */ ir->dmach = 0; kprintf("DF5302: sizeof(fwohci_softc)=%zu sizeof(fwohcidb_tr)=%zu " "offsetof(fwohcidb_tr,link)=%zu\n", sizeof(*sc), sizeof(struct fwohcidb_tr), offsetof(struct fwohcidb_tr, link)); kprintf("DF5302: calling production fwohci_irx_enable() with " "bnpacket*bnchunk = %u*%u = 0 -- expect NULL write panic at " "fwohci.c:1253\n", ir->bnpacket, ir->bnchunk); error = fwohci_irx_enable(&sc->fc, 0); /* production fwohci.c:1586 */ kprintf("DF5302: SURVIVED fwohci_irx_enable, returned %d " "(baseline expects panic; fixed kernel expects EINVAL)\n", error); } /* * DF-5303: drive the PRODUCTION dump_db() with ch == OHCI_MAX_DMA_CH, * the off-by-one value the DUMPDMA ioctl accepts (fwohci.c:372). */ static void df5303_run_dump_db(void) { struct fwohci_softc *sc; struct fwohci_dbch *oob; char *regs; /* * ir[OHCI_DMA_IRCH] is one past the ir[] array. ir[] is NOT the * last member of fwohci_softc (fwohcivar.h:64-70): the phantom * fwohci_dbch at &sc->ir[32] TYPE-CONFUSES the softc tail: * phantom->ndb == sc->maxrec (nonzero after reset: * speed+8, fwohci.c:521) * phantom->db_trq.stqh_first == sc->sid_buf (and stqh_last == * sc->sid_dma.dma_tag) * phantom->top == sc->sid_dma.dma_map (fwdma.h:40 -- a REAL * kernel heap pointer once fwohci_init() * loaded the SID buffer map, so on hardware * the walk always follows bus_dmamap_impl * internals as a descriptor ring and * print_db() leaks them to the console) * so on real hardware dump_db() ALWAYS walks (ndb != 0) and follows * sid_dma/sid_buf fields as an fwohcidb_tr ring. We emulate the * post-reset state and make the first hop (STAILQ_NEXT of the * "top") fault at 0x10. */ sc = kmalloc(sizeof(*sc), M_FW, M_WAITOK | M_ZERO); regs = kmalloc(0x1000, M_FW, M_WAITOK | M_ZERO); sc->bst = X86_64_BUS_SPACE_MEM; sc->bsh = (bus_space_handle_t)regs; sc->maxrec = 10; /* sc->fc.speed+8, fwohci.c:521 */ sc->sid_dma.dma_map = (bus_dmamap_t)0x10; /* phantom dbch->top */ oob = (struct fwohci_dbch *)&sc->ir[OHCI_DMA_IRCH]; kprintf("DF5303: sc=%p sizeof=%zu &sc->ir[0x20]=%p is %zd bytes " "past ir[0] (%zu into the %zu-byte softc tail alias)\n", sc, sizeof(*sc), &sc->ir[OHCI_DMA_IRCH], (char *)&sc->ir[OHCI_DMA_IRCH] - (char *)&sc->ir[0], (char *)&sc->ir[OHCI_DMA_IRCH] - (char *)&sc->maxrec + 1, sizeof(*sc)); kprintf("DF5303: phantom dbch->ndb(=maxrec)=%u phantom->top" "(=sid_dma.dma_map)=%p\n", oob->ndb, oob->top); kprintf("DF5303: calling production dump_db(sc, OHCI_MAX_DMA_CH=0x44)" " -- expect fault at 0x10 inside the STAILQ walk\n"); dump_db(sc, OHCI_MAX_DMA_CH); /* production fwohci.c:2236 */ kprintf("DF5303: SURVIVED dump_db (unexpected)\n"); } static int df5302_sysctl_trigger(SYSCTL_HANDLER_ARGS) { int error, t = df5302_trigger; error = sysctl_handle_int(oidp, &t, 0, req); if (error != 0 || req->newptr == NULL) return (error); df5302_trigger = t; if (t) { if (df5302_mode == 2) df5303_run_dump_db(); else df5302_run_irx_enable(); } return (0); } SYSCTL_PROC(_kern, OID_AUTO, df5302_fire, CTLTYPE_INT | CTLFLAG_RW, 0, 0, df5302_sysctl_trigger, "I", "fire the DF-5302/5303 case"); static int df5302_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("DF5302: harness loaded (production fwohci.c " "compiled in); kern.df5302_mode=%d " "kern.df5302_fire=1 to trigger\n", df5302_mode); return (0); case MOD_UNLOAD: return (0); default: return (ENOTSUP); } } static moduledata_t df5302_mod = { "df5302", df5302_modevent, NULL }; DECLARE_MODULE(df5302, df5302_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |