⬢ DragonFlyBSD Kernel Audit
DF-4232 / trigger.c
← back to finding ↓ download raw
/*
 * 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);
}