DF-4232 / trigger.c
/* * DF-4232 trigger -- annotated hardware-precondition probe. * * BUG (source-certain): every ring cursor the txp(4) driver walks is a * 32-bit word in the DMA-shared hostvar block (struct txp_hostvar, * if_txpreg.h:542-554) -- i.e. writable by the NIC firmware -- and the * driver consumes them with no range/alignment validation: * * 1) txp_rx_reclaim() (if_txp.c:649-744) * roff = *r->r_roff; woff = *r->r_woff; // :658-659 device-fed * rxd = r->r_desc + (roff / sizeof(struct txp_rx_desc)); // :660 unbounded * while (roff != woff) { // :662 may never exit * sd = rxd->rx_sd; // :671 kernel pointer * m = sd->sd_mbuf; // :673 wild deref * ... * roff += sizeof(struct txp_rx_desc); // :732 +16 forever * if (roff == RX_ENTRIES*16) roff = 0; // :733 exact equality * * - roff outside [0, 2048) or not a multiple of 16 indexes the rx * ring out of bounds and keeps walking: rxd runs off the end of * the contigmalloc'd sc_ldata block into adjacent physical memory. * - rx_sd is a *kernel pointer stored in DMA memory* (union in * struct txp_rx_desc, if_txpreg.h:268-271); an OOB (or device- * forged) rx_sd is dereferenced at :673 and the resulting wild * mbuf pointer is freed/copyed to (m_freem / m_copydata / if_input). * - if woff is not congruent to roff modulo 16 (or lies outside the * ring), the loop never terminates: unbounded kernel walk under * the ifnet serializer -> hard livelock. * * 2) txp_response() (if_txp.c:1411-1456) * ridx = hv->hv_resp_read_idx; // :1393 device-writable * while (ridx != hv->hv_resp_write_idx) { // :1418 device-fed widx * rsp = base + ridx; // :1419 unbounded * rsp_numdesc = rsp->rsp_numdesc; // u_int8 from OOB read * kmalloc(sizeof(rsp_desc) * (rsp_numdesc+1));// :1422 sized by garbage * txp_rsp_fixup(...); // bcopy walk * * - hv_resp_write_idx is firmware-fed; if it is not one of * {0,16,...,496} the walk cycles the 32-entry ring forever * (non-terminating loop + console spam under serializer). * - rsp = base + ridx with a firmware-corrupted hv_resp_read_idx * reads the response descriptor OOB; rsp_numdesc then sizes a * kmalloc and a bcopy walk from OOB memory (rsp_numdesc is a * u_int8_t so <= 255 descs; the walk itself wraps at * sc_rspring.size, but the SOURCE is attacker-chosen memory). * * All cursors should be masked/validated against the ring byte size * before first use, as every other DragonFly NIC driver does. * * PRECONDITION: 3c990 firmware (or a device attacking via DMA) writing * the hostvar block / rx descriptors. No syscall path can forge these. */ #include <stdio.h> int main(void) { printf("DF-4232: txp walks device-fed ring cursors unvalidated\n"); printf(" 1) txp_rx_reclaim if_txp.c:658-737 OOB rxd walk / wild rx_sd\n"); printf(" deref / non-terminating while(roff!=woff)\n"); printf(" 2) txp_response if_txp.c:1418-1453 non-terminating walk on\n"); printf(" hv_resp_write_idx; rsp = base + device-fed ridx (OOB read)\n"); printf(" needs: 3Com 3c990 writing hostvar/rx rings (hardware)\n"); return (0); } |