/*
 * DF-4066 trigger/geometry model (runs on the audit guest; userland).
 *
 * Bug modeled (source-certain, hardware-gated):
 *   ti_rxeof() reads the return-ring descriptor written by the Tigon
 *   firmware over PCI DMA and uses its uint16_t ti_idx field
 *   (sys/dev/netif/ti/if_tireg.h:711) directly as an array index:
 *
 *     rxidx = cur_rx->ti_idx;                          if_ti.c:1638
 *     m = sc->ti_cdata.ti_rx_jumbo_chain[rxidx];       if_ti.c:1648
 *     sc->ti_cdata.ti_rx_jumbo_chain[rxidx] = NULL;    if_ti.c:1649
 *     m = sc->ti_cdata.ti_rx_mini_chain[rxidx];        if_ti.c:1662
 *     sc->ti_cdata.ti_rx_mini_chain[rxidx] = NULL;     if_ti.c:1663
 *     m = sc->ti_cdata.ti_rx_std_chain[rxidx];         if_ti.c:1676
 *     sc->ti_cdata.ti_rx_std_chain[rxidx] = NULL;      if_ti.c:1677
 *
 *   ... with NO bounds check against the array sizes
 *   (if_tireg.h:1097-1102):
 *     ti_rx_std_chain   [TI_STD_RX_RING_CNT   = 512]
 *     ti_rx_jumbo_chain [TI_JUMBO_RX_RING_CNT = 256]
 *     ti_rx_mini_chain  [TI_MINI_RX_RING_CNT  = 1024]
 *
 *   Consequences per out-of-range index:
 *     (1) OOB READ of a kernel pointer that is then used as `struct mbuf *m`:
 *         if_ti.c:1690 does  m->m_pkthdr.len = m->m_len = cur_rx->ti_len
 *         (two stores through the wild pointer) and if_ti.c:1706
 *         ifp->if_input(ifp, m, ...) feeds the wild mbuf to the network
 *         stack (type confusion: whatever the OOB read picked up -- e.g.
 *         ti_jslots[].ti_buf / .ti_sc kernel pointers -- is treated as an
 *         mbuf).
 *     (2) OOB WRITE: chain[rxidx] = NULL stores 8 zero bytes at
 *         base + 8*rxidx, up to +512KB past the array (rxidx <= 65535),
 *         inside/past struct ti_softc (device softc, kernel heap).
 *
 *   This program models the softc chain-data layout (if_tireg.h:1096-1105)
 *   and prints, for each receive path and a sample of DMA'd indices, where
 *   the OOB read / OOB NULL write land.  It does NOT touch kernel memory:
 *   exercising the real path needs a Tigon/Tigon2 NIC whose firmware emits
 *   an out-of-range descriptor index (faulty or malicious peripheral /
 *   DMA tampering -- same attacker model as DF-3604/DF-3989/DF-4024/
 *   DF-4045/DF-4055).
 */

#include <stdio.h>
#include <stdint.h>

/* if_tireg.h:395-411 */
#define TI_STD_RX_RING_CNT    512
#define TI_JUMBO_RX_RING_CNT  256
#define TI_MINI_RX_RX_RING_CNT 1024
#define TI_MINI_RX_RING_CNT   1024
#define TI_TX_RING_CNT        512
#define TI_JSLOTS             384

/* if_tireg.h:1097-1105: struct ti_chain_data, 64-bit layout, all pointers 8B */
struct region {
	const char *name;
	size_t      bytes;
};

static struct region cdata_layout[] = {
	{ "ti_tx_chain[512]      ", TI_TX_RING_CNT    * 8 },
	{ "ti_rx_std_chain[512]  ", TI_STD_RX_RING_CNT * 8 },
	{ "ti_rx_jumbo_chain[256]", TI_JUMBO_RX_RING_CNT * 8 },
	{ "ti_rx_mini_chain[1024]", TI_MINI_RX_RING_CNT * 8 },
	{ "ti_jslots[384] (32B ea; ti_sc/ti_buf/ti_inuse/ti_slot)", TI_JSLOTS * 32 },
	{ "ti_jumbo_buf ptr      ", 8 },
	{ "-- end of struct ti_chain_data (softc continues: ti_rdata ptr, ifmedia, arpcom fields...) --", 0 },
};

static void locate(const char *path, uint32_t count, size_t arr_base, uint32_t rxidx)
{
	size_t off = (size_t)rxidx * 8;   /* element offset from indexed array */
	size_t abs_off = arr_base + off;  /* offset within struct ti_chain_data */
	size_t base = 0, i;

	printf("  %s rxidx=%-6u -> element offset +%7zu (max in-bounds +%zu)",
	       path, rxidx, off, ((size_t)count - 1) * 8);
	if (rxidx < count) {
		printf("   in-bounds\n");
		return;
	}
	printf("   OOB by %u elements\n", rxidx + 1 - count);
	for (i = 0; cdata_layout[i].bytes; i++) {
		if (abs_off >= base && abs_off < base + cdata_layout[i].bytes) {
			printf("      8-byte NULL write lands inside %s (+%zu within that region)\n",
			       cdata_layout[i].name, abs_off - base);
			return;
		}
		base += cdata_layout[i].bytes;
	}
	printf("      8-byte NULL write lands %zu bytes past struct ti_chain_data "
	       "(further softc fields / adjacent kernel heap)\n", abs_off - base);
}

/* array bases within struct ti_chain_data (if_tireg.h:1097-1105, 64-bit) */
#define BASE_TX    ((size_t)0)
#define BASE_STD   (BASE_TX    + TI_TX_RING_CNT * 8)
#define BASE_JUMBO (BASE_STD   + TI_STD_RX_RING_CNT * 8)
#define BASE_MINI  (BASE_JUMBO + TI_JUMBO_RX_RING_CNT * 8)

int main(void)
{
	uint32_t sidx[] = { 511, 512, 513, 640, 1023, 2047, 4095, 65535 };
	uint32_t jidx[] = { 255, 256, 257, 511, 1024, 65535 };
	uint32_t midx[] = { 1023, 1024, 2048, 65535 };
	unsigned i;

	printf("DF-4066 model: ti_rxeof OOB via NIC-DMA'd uint16_t ti_idx "
	       "(if_ti.c:1638,1648-77)\n");
	printf("chain arrays (if_tireg.h:1099-1102): std=512 jumbo=256 mini=1024; "
	       "idx field u16 (if_tireg.h:707/711)\n\n");

	printf("STD path (array ti_rx_std_chain[512], if_ti.c:1676-1677):\n");
	for (i = 0; i < sizeof(sidx)/sizeof(sidx[0]); i++)
		locate("std  ", TI_STD_RX_RING_CNT, BASE_STD, sidx[i]);
	printf("\nJUMBO path (array ti_rx_jumbo_chain[256], if_ti.c:1648-1649):\n");
	for (i = 0; i < sizeof(jidx)/sizeof(jidx[0]); i++)
		locate("jumbo", TI_JUMBO_RX_RING_CNT, BASE_JUMBO, jidx[i]);
	printf("\nMINI path (array ti_rx_mini_chain[1024], if_ti.c:1662-1663):\n");
	for (i = 0; i < sizeof(midx)/sizeof(midx[0]); i++)
		locate("mini ", TI_MINI_RX_RING_CNT, BASE_MINI, midx[i]);

	printf("\nPrimitives per out-of-range rxidx:\n");
	printf("  READ : m = chain[rxidx]           (if_ti.c:1648/1662/1676)\n");
	printf("  WRITE: chain[rxidx] = NULL        (if_ti.c:1649/1663/1677) "
	       "-> 8 zero bytes at base+8*rxidx, rxidx<=%u -> up to +%u KB\n",
	       65535u, (unsigned)(65535 * 8 / 1024));
	printf("  WRITE: m->m_pkthdr.len = m->m_len = cur_rx->ti_len "
	       "(if_ti.c:1690) -> two stores through the OOB-read pointer,\n"
	       "         m_len at fixed offset +8 of the wild 'mbuf', value = "
	       "NIC-chosen 16-bit ti_len\n");
	printf("  USE  : ifp->if_input(ifp, m, NULL, -1) (if_ti.c:1706) -> "
	       "wild-mbuf type confusion into ether_input\n");
	printf("\nRC_DF4066_MODEL=0\n");
	return 0;
}
