/*
 * DF-1286 harness: SPROM rev4/5/8 TXPID parser reads kernel heap at negative
 * array indices (unported Linux-relative offset constants) in siba_sprom_r458.
 *
 * Object-level proof. The bwn/siba driver attaches to Broadcom BCM43xx WiFi;
 * none is present in the audit QEMU guest, so siba_sprom_r458 is not runtime-
 * reachable here. This harness replays the exact SIBA_OFFSET indexing with the
 * actual constant values from sibareg.h to PROVE the negative-index reads.
 *
 * Cited path: sys/dev/netif/bwn/siba/siba_core.c:1509 (SIBA_OFFSET macro),
 *   :1513 (SIBA_SHIFTOUT macro = in[SIBA_OFFSET(...)]),
 *   :1591-1628 (siba_sprom_r458, 16 TXPID reads).
 * Constants: sibareg.h:305 SIBA_SPROM_BASE=0x1000,
 *   :363-386 SIBA_SPROM4_TXPID* = 0x0062..0x0070 (UNPORTED Linux-relative).
 * All sibling SPROM4 offset constants are absolute (0x10xx, e.g.
 *   SIBA_SPROM4_MAC_80211BG=0x104c, MAXP_BG=0x1080) -- the TXPID block alone
 *   was left as Linux-style relative word offsets.
 *
 * Build: cc -O2 -o harness harness.c
 * Run:   ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define SIBA_SPROM_BASE 0x1000
/* the buggy (as-shipped) constants */
#define BUG_TXPID2G01   0x0062
#define BUG_TXPID2G23   0x0064
#define BUG_TXPID5G01   0x0066
#define BUG_TXPID5G23   0x0068
#define BUG_TXPID5GL01  0x006a
#define BUG_TXPID5GL23  0x006c
#define BUG_TXPID5GH01  0x006e
#define BUG_TXPID5GH23  0x0070
/* the fixed constants (absolute, +0x1000) */
#define FIX_TXPID2G01   0x1062
#define FIX_TXPID5GH23  0x1070

/* exact kernel macro, siba_core.c:1509 */
#define SIBA_OFFSET(offset) (((offset) - SIBA_SPROM_BASE) / sizeof(uint16_t))
/* exact kernel macro, siba_core.c:1513 -- reads in[SIBA_OFFSET(offset)] */
#define SIBA_SHIFTOUT(in, offset) ((in)[SIBA_OFFSET(offset)])

#define SPROM_WORDS 220            /* typical SPROM image size in uint16 words */
#define PREFIX_WORDS 2100          /* >= 1999 to cover the worst negative idx  */

int main(void)
{
	/* Simulate the kernel heap: a large "prefix" (residue) region BEFORE the
	 * SPROM buffer, then the SPROM buffer itself. In the kernel the SPROM
	 * buffer is a kmalloc'd region; in[-1999] reads 3998 bytes earlier into
	 * unrelated slab/kernel heap. */
	uint16_t *prefix = calloc(PREFIX_WORDS, sizeof(uint16_t));
	uint16_t *sprom  = calloc(SPROM_WORDS, sizeof(uint16_t));
	/* paint the prefix with a recognizable "leaked heap" pattern */
	for (int i = 0; i < PREFIX_WORDS; i++) prefix[i] = 0xBE00 | (i & 0xff);
	/* the real SPROM words at the TXPID locations (fixed-offset view): */
	sprom[SIBA_OFFSET(FIX_TXPID2G01)]  = 0x1111;
	sprom[SIBA_OFFSET(FIX_TXPID5GH23)] = 0x8888;

	printf("[DF-1286] SIBA_SPROM_BASE=0x%x, sizeof(word)=%zu\n",
	       SIBA_SPROM_BASE, sizeof(uint16_t));
	printf("[DF-1286] SPROM buffer = %d words; TXPID reads use SIBA_SHIFTOUT "
	       "(in[SIBA_OFFSET(offset)])\n\n", SPROM_WORDS);

	puts("=== AS-SHIPPED (buggy) TXPID constants 0x0062..0x0070 ===");
	int buggy_offsets[] = { BUG_TXPID2G01, BUG_TXPID2G23, BUG_TXPID5G01,
	    BUG_TXPID5G23, BUG_TXPID5GL01, BUG_TXPID5GL23, BUG_TXPID5GH01,
	    BUG_TXPID5GH23 };
	int any_neg = 0;
	for (int k = 0; k < 8; k++) {
		int idx = SIBA_OFFSET(buggy_offsets[k]);
		printf("  constant 0x%04x -> SIBA_OFFSET = %d  ", buggy_offsets[k], idx);
		if (idx < 0) {
			printf("=> NEGATIVE: in[%d] reads %d bytes BEFORE the SPROM "
			       "buffer (heap leak)\n", idx, idx * (int)sizeof(uint16_t));
			any_neg = 1;
		} else {
			printf("(valid)\n");
		}
	}

	/* Demonstrate the leak concretely: the buggy code reads prefix bytes. */
	int worst = SIBA_OFFSET(BUG_TXPID2G01);   /* -1999 */
	printf("\n[DF-1286] Worst case: in[%d] reads bytes [%d..%d] relative to "
	       "SPROM base (i.e. %d bytes before the allocation).\n",
	       worst, worst*2, worst*2+1, worst*2);

	/* In the kernel, 'in' points at the SPROM kmalloc. To show what gets
	 * leaked, treat prefix as the slab memory preceding the allocation: */
	uint16_t leaked = prefix[PREFIX_WORDS + worst]; /* prefix[-1999]-equiv */
	printf("[DF-1286] Simulated leak: buggy txpid2g[0] = 0x%04x (heap residue "
	       "before SPROM buf), NOT the real SPROM TXPID value.\n", leaked);

	puts("\n=== FIXED constants 0x1062..0x1070 (absolute, like all siblings) ===");
	printf("  SIBA_OFFSET(0x1062) = %d  -> in[%d] reads the REAL SPROM word\n",
	       SIBA_OFFSET(FIX_TXPID2G01), SIBA_OFFSET(FIX_TXPID2G01));
	printf("  fixed txpid2g[0]  = 0x%04x (matches the SPROM image)\n",
	       SIBA_SHIFTOUT(sprom, FIX_TXPID2G01));
	printf("  fixed txpid5gh[3] = 0x%04x\n", SIBA_SHIFTOUT(sprom, FIX_TXPID5GH23));

	puts("");
	printf("[DF-1286] RESULT: %s -- %d bytes of kernel heap read before the "
	       "SPROM buffer per SPROM rev4/5/8 parse (16 reads total = 32 bytes),\n"
	       "          surfacing via SIBA_SPROMVAR_TXPID_* sysctl spromvars "
	       "(KASLR/heap-info leak).\n",
	       any_neg ? "BUG CONFIRMED" : "no bug", -worst*2);

	free(prefix); free(sprom);
	return any_neg ? 0 : 2;
}
