/*
 * DF-1090 — userspace harness for pnp_parse_desc OOB pattern.
 *
 * The kernel path (sys/bus/isa/pnpparse.c:pnp_parse_desc) is reachable
 * only via PnP hardware returning crafted TLV resources. The audit guest
 * has no PnP cards, so this harness mirrors the vulnerable algorithm
 * byte-for-byte (offset accesses only — no isa_config writes needed to
 * show the OOB) to demonstrate the primitive.
 *
 * It allocates a tiny "resource" buffer of the declared TLV length, then
 * exercises each affected descriptor type. With the unpatched algorithm,
 * the accesses blow straight past the buffer end. With the per-descriptor
 * length guards (the fix), each branch bails out cleanly.
 *
 * Build:  cc -O0 -o df1090_harness df1090_harness.c
 *   or:   cc -O0 -DFIX -o df1090_harness_fix df1090_harness.c
 * Run:    ./df1090_harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define I16(p) ((p)[0] + ((p)[1] << 8))
#define I32(p) (I16(p) + (I16((p)+2) << 16))

/* "resource buffer" the kernel kmalloc'd; we allocate exactly len bytes so
 * any access at offset >= len is a real OOB. Malloc gives us a guard page
 * nearby so runaway reads SIGSEGV loudly. */
static unsigned char *
make_res(int len)
{
	unsigned char *p = calloc(1, len);
	if (!p) { perror("calloc"); exit(2); }
	return p;
}

static int oob_detected = 0;

/* wrap each access so we can detect OOB without crashing */
#define READ_BYTE(buf, len, off) \
	(((off) < (len)) ? (buf)[off] : (oob_detected = 1, 0xFF))
#define READ_I16(buf, len, off) \
	(((off)+1 < (len)) ? I16((buf)+(off)) : (oob_detected = 1, 0xFFFF))

/* Mirror pnp_parse_desc for the 8 affected descriptor types.
 * 'FIX' = apply the per-descriptor len guard (the proposed fix). */
static void
parse_desc(unsigned char tag, unsigned char *res, int len)
{
	if (tag == 0) {
		/* PNP_TAG_COMPAT_DEVICE  (small tag 3) needs len >= 4 */
#ifdef FIX
		if (len < 4) { printf("  [fix] skip COMPAT_DEVICE len=%d\n", len); return; }
#endif
		(void)res[0]; (void)res[1]; (void)res[2]; (void)res[3]; /* bcopy 4 */
		READ_BYTE(res, len, 0); READ_BYTE(res, len, 1);
		READ_BYTE(res, len, 2); READ_BYTE(res, len, 3);
		printf("  COMPAT_DEVICE len=%d: read [0..3]\n", len);
		return;
	}
	if (tag == 1) {
		/* PNP_TAG_IRQ_FORMAT needs len >= 2 */
#ifdef FIX
		if (len < 2) { printf("  [fix] skip IRQ_FORMAT len=%d\n", len); return; }
#endif
		READ_I16(res, len, 0);
		printf("  IRQ_FORMAT len=%d: read I16(0)\n", len);
		return;
	}
	if (tag == 2) {
		/* PNP_TAG_DMA_FORMAT needs len >= 1 */
#ifdef FIX
		if (len < 1) { printf("  [fix] skip DMA_FORMAT len=%d\n", len); return; }
#endif
		READ_BYTE(res, len, 0);
		printf("  DMA_FORMAT len=%d: read [0]\n", len);
		return;
	}
	if (tag == 3) {
		/* PNP_TAG_IO_RANGE needs len >= 7; also WRITES res[5]=1 if 0 */
#ifdef FIX
		if (len < 7) { printf("  [fix] skip IO_RANGE len=%d\n", len); return; }
#endif
		READ_BYTE(res, len, 6);
		READ_I16(res, len, 1);
		READ_I16(res, len, 3);
		READ_BYTE(res, len, 5);  /* the WRITE site */
		printf("  IO_RANGE len=%d: read [6],I16(1),I16(3),[5] (5 is WRITE)\n", len);
		return;
	}
	if (tag == 4) {
		/* PNP_TAG_IO_FIXED needs len >= 3 */
#ifdef FIX
		if (len < 3) { printf("  [fix] skip IO_FIXED len=%d\n", len); return; }
#endif
		READ_BYTE(res, len, 2);
		READ_I16(res, len, 0);
		printf("  IO_FIXED len=%d: read [2],I16(0)\n", len);
		return;
	}
	if (tag == 5) {
		/* PNP_TAG_MEMORY_RANGE needs len >= 9 */
#ifdef FIX
		if (len < 9) { printf("  [fix] skip MEMORY_RANGE len=%d\n", len); return; }
#endif
		READ_I16(res, len, 7);
		READ_I16(res, len, 5);
		printf("  MEMORY_RANGE len=%d: read I16(7),I16(5)\n", len);
		return;
	}
	if (tag == 6) {
		/* PNP_TAG_MEMORY32_RANGE needs len >= 17 */
#ifdef FIX
		if (len < 17) { printf("  [fix] skip MEMORY32_RANGE len=%d\n", len); return; }
#endif
		/* I32 reads 4 bytes each */
		for (int off = 5; off <= 13; off += 4)
			for (int i = 0; i < 4; i++) READ_BYTE(res, len, off+i);
		printf("  MEMORY32_RANGE len=%d: read I32(5),I32(9),I32(13)\n", len);
		return;
	}
	if (tag == 7) {
		/* PNP_TAG_MEMORY32_FIXED needs len >= 9 */
#ifdef FIX
		if (len < 9) { printf("  [fix] skip MEMORY32_FIXED len=%d\n", len); return; }
#endif
		for (int off = 1; off <= 5; off += 4)
			for (int i = 0; i < 4; i++) READ_BYTE(res, len, off+i);
		printf("  MEMORY32_FIXED len=%d: read I32(1),I32(5)\n", len);
		return;
	}
}

int
main(void)
{
	/* Each descriptor type fed a SHORT declared len vs the size the
	 * algorithm actually accesses. The unpatched algorithm reads OOB. */
	struct { const char *name; int tag; int decl_len; } cases[] = {
		{ "COMPAT_DEVICE",   0, 1 },   /* needs 4 */
		{ "IRQ_FORMAT",      1, 1 },   /* needs 2 */
		{ "DMA_FORMAT",      2, 0 },   /* needs 1 */
		{ "IO_RANGE",        3, 3 },   /* needs 7 */
		{ "IO_FIXED",        4, 1 },   /* needs 3 */
		{ "MEMORY_RANGE",    5, 4 },   /* needs 9 */
		{ "MEMORY32_RANGE",  6, 5 },   /* needs 17 */
		{ "MEMORY32_FIXED",  7, 3 },   /* needs 9 */
	};

	printf("=== DF-1090 pnp_parse_desc OOB harness (%s) ===\n",
#ifdef FIX
	    "PATCHED"
#else
	    "UNPATCHED"
#endif
	    );
	for (size_t i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) {
		unsigned char *res = make_res(cases[i].decl_len);
		printf("[%s] tag=%d declared_len=%d:\n",
		    cases[i].name, cases[i].tag, cases[i].decl_len);
		oob_detected = 0;
		parse_desc(cases[i].tag, res, cases[i].decl_len);
		printf("    -> %s (OOB %s)\n",
		    oob_detected ? "OOB DETECTED" : "no OOB",
		    oob_detected ? "WOULD HAPPEN on kernel kmalloc'd buffer"
		                 : "");
		free(res);
	}
	return (0);
}
