/*
 * poc_quirkfastpath.c -- DF-5585 rig for sys/bus/pccard/pccard_cis_quirks.c
 * (pass 2, GLM 5.3).  Userland model, no PCMCIA hardware required.
 *
 * VERBATIM-BACKED: struct pccard_config_entry / pccard_function /
 * pccard_card / pccard_cis_quirk are byte-for-byte
 * sys/bus/pccard/pccardvar.h:87-160,165-184,205-211 (bus types typedef'd),
 * and pccard_cis_quirk_match() + pccard_check_cis_quirks() are byte-for-byte
 * sys/bus/pccard/pccard_cis_quirks.c:201-216 and :218-292 with ONLY these
 * mechanical substitutions (each marked  * RIG: *  inline):
 *   - device_t dev / PCCARD_SOFTC(dev)  -> struct pccard_softc *sc parameter
 *   - device_printf(dev,...)/kprintf    -> qlog(...)   (macro)
 *   - kmalloc(sz,M_DEVBUF,M_NOWAIT)     -> qk_alloc(sz) (macro, failpoint)
 *   - kfree(p,M_DEVBUF)                 -> qk_free(p)    (macro, shadow-copy)
 *   - the quirk table + n_pccard_cis_quirks become runtime-set globals
 * (see fidelity.diff for the normalized diff against sys/).
 *
 * The failpoint allocator snapshots every block at kfree time (kernel kfree
 * does not scrub; contents survive).  After the run, freed blocks are
 * compared against their snapshots: any difference is a write into freed
 * memory -- the stale-pf write of the quirks.c:263-270 fast path.
 *
 * Scenarios (argv[1]):
 *   negative      shipped-table shape, no allocation failure  -> clean
 *   shippedfail   shipped-table shape (func0,func1), cfe alloc of the 2nd
 *                 quirk fails (fail_nth=4)                    -> clean (today)
 *   repeat        3-entry table re-using the func0 template (the natural
 *                 future extension of quirks.c:178-197), same fail_nth=4
 *                 -> STAILQ_INSERT_TAIL writes into the pf kfree'd at
 *                    pccard_cis_quirks.c:282
 *   nulltemplate  entry with pf==NULL after a non-empty wipe -> fast path
 *                 (pf_last==NULL==q->pf) writes into the pf freed by the
 *                 wipe loop (pccard_cis_quirks.c:248-257); empty-list
 *                 variant would write to ~address 0 (NULL page) instead
 *   matchmatrix   enumerates every card identity the SHIPPED table can
 *                 match; proves no identity ever takes the fast path
 *                 (no consecutive same-template matches, sets <= 2)
 *   fixed_*       same as repeat/nulltemplate but through
 *                 pccard_check_cis_quirks_fixed() = verbatim + the
 *                 fix.diff guard lines -> no freed-memory writes
 *   all           everything in order
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/queue.h>

/* macro shims so the verbatim function bodies below compile unchanged;
 * kmalloc/kfree swap in the failpoint allocator, printf stands in for
 * device_printf/kprintf (first arg dev discarded, never evaluated) */
#define qlog printf
#define kmalloc(sz, type, flags)	qk_alloc(sz)
#define kfree(p, type)			qk_free(p)
#define device_printf(dev, ...)		printf(__VA_ARGS__)
#define kprintf(...)			printf(__VA_ARGS__)
static int bootverbose = 0;

/* ---------------- failpoint allocator ----------------                  */

#define QK_NBLOCKS 64
#define QK_BLOCK   512

struct qk_blk {
	void   *p;
	int     freed;
	size_t  serial;			/* kmalloc call number that made it */
	uint8_t shadow[QK_BLOCK];	/* contents at kfree time */
};

static struct qk_blk qk_pool[QK_NBLOCKS];
static int qk_used;
static size_t qk_alloc_calls;
static long qk_fail_nth = -1;		/* fail the Nth kmalloc (1-based) */

static void *qk_alloc(size_t sz)
{
	void *p;

	qk_alloc_calls++;
	if (qk_fail_nth > 0 && (size_t)qk_fail_nth == qk_alloc_calls) {
		qlog("RIG: kmalloc call #%zu (%zu bytes) -> NULL (failpoint)\n",
		    qk_alloc_calls, sz);
		return NULL;
	}
	if (sz > QK_BLOCK || qk_used == QK_NBLOCKS) {
		qlog("RIG: pool exhausted\n");
		return NULL;
	}
	p = calloc(1, QK_BLOCK);	/* kernel kmalloc: not zeroed, but a
					 * fresh cfe/pf copies over every
					 * field it reads; calloc keeps the
					 * model deterministic */
	qk_pool[qk_used].p = p;
	qk_pool[qk_used].freed = 0;
	qk_pool[qk_used].serial = qk_alloc_calls;
	qk_used++;
	return p;
}

static void qk_free(void *p)
{
	int i;

	if (p == NULL)
		return;
	for (i = 0; i < qk_used; i++) {
		if (qk_pool[i].p == p && !qk_pool[i].freed) {
			qk_pool[i].freed = 1;
			memcpy(qk_pool[i].shadow, p, QK_BLOCK);
			return;
		}
	}
	qlog("RIG: qk_free(%p): not a live pool block!\n", p);
}

/* writes-into-freed-memory audit */
static int qk_audit(const char *tag)
{
	int i, dirty = 0, off, noff;
	uint8_t *b;

	for (i = 0; i < qk_used; i++) {
		if (!qk_pool[i].freed)
			continue;
		b = qk_pool[i].p;
		noff = 0;
		for (off = 0; off < QK_BLOCK; off++) {
			if (b[off] != qk_pool[i].shadow[off])
				noff++;
		}
		if (noff) {
			dirty++;
			for (off = 0; off < QK_BLOCK; off++) {
				if (b[off] != qk_pool[i].shadow[off])
					break;
			}
			qlog("  FREED-WRITE[%s]: block %p (kmalloc #%zu) "
			    "modified: first diff at +%d, %d byte(s) changed "
			    "(snapshot 0x%02x -> 0x%02x)\n",
			    tag, qk_pool[i].p, qk_pool[i].serial, off, noff,
			    qk_pool[i].shadow[off], b[off]);
		}
	}
	if (!dirty)
		qlog("  freed-memory audit[%s]: CLEAN (0 writes into "
		    "kfree'd blocks)\n", tag);
	return dirty;
}

/* device_printf/kprintf stand-in */
#define qlog printf

/* ---------------- verbatim struct definitions -------------------------- */
/* pccardvar.h:87-112 */
struct pccard_config_entry {
	int		number;
	uint32_t	flags;
	int		iftype;
	int		num_iospace;
	u_long		iomask;
	struct {
		u_long	length;
		u_long	start;
	} iospace[4];
	uint16_t	irqmask;
	int		num_memspace;
	struct {
		u_long	length;
		u_long	cardaddr;
		u_long	hostaddr;
	} memspace[2];
	int		maxtwins;
	STAILQ_ENTRY(pccard_config_entry) cfe_list;
};

/* pccardvar.h:114-126 */
struct pccard_funce_disk {
	int pfd_interface;
};

struct pccard_funce_lan {
	int pfl_nidlen;
	uint8_t pfl_nid[8];
};

union pccard_funce {
	struct pccard_funce_disk pfv_disk;
	struct pccard_funce_lan pfv_lan;
};

struct pccard_softc;			/* RIG: fwd, was via bus.h */

/* pccardvar.h:128-160 (bus types typedef'd, field order verbatim) */
struct pccard_function {
	int		number;
	int		function;
	int		last_config_index;
	uint32_t	ccr_base;
	uint32_t	ccr_mask;
	void	       *ccr_res;		/* RIG: struct resource * */
	int		ccr_rid;
	STAILQ_HEAD(, pccard_config_entry) cfe_head;
	STAILQ_ENTRY(pccard_function) pf_list;
	struct pccard_softc *sc;
	struct pccard_config_entry *cfe;
	struct {				/* RIG: pccard_mem_handle */
		uint64_t addr, size, realsize, cardaddr;
		int kind;
	} pf_pcmh;
	void	       *dev;			/* RIG: device_t */
	uint32_t	pf_ccr_offset;
	int		pf_ccr_window;
	uint64_t	pf_mfc_iobase;		/* RIG: bus_addr_t */
	uint64_t	pf_mfc_iomax;		/* RIG: bus_addr_t */
	int		pf_flags;
	void	       *intr_handler;		/* RIG: driver_intr_t * */
	void	       *intr_handler_arg;
	void	       *intr_handler_cookie;
	union pccard_funce pf_funce;
};

/* pccardvar.h:165-184 */
struct pccard_card {
	int		cis1_major;
	int		cis1_minor;
	char		cis1_info_buf[256];
	char		*cis1_info[4];
	int32_t		manufacturer;
#define	PCMCIA_VENDOR_INVALID	-1
	int32_t		product;
#define	PCMCIA_PRODUCT_INVALID		-1
	int16_t		prodext;
	uint16_t	error;
#define	PCMCIA_CIS_INVALID		{ NULL, NULL, NULL, NULL }
	STAILQ_HEAD(, pccard_function) pf_head;
};

struct pccard_softc {			/* RIG: pccardvar.h:196-203, card only */
	struct pccard_card card;
};

/* pccardvar.h:205-211 */
struct pccard_cis_quirk {
	int32_t manufacturer;
	int32_t product;
	char *cis1_info[4];
	struct pccard_function *pf;
	struct pccard_config_entry *cfe;
};

/* RIG: the kmalloc/kfree/device_printf/kprintf/bootverbose shims live at
 * the top of the file; the quirk table + count are runtime-set globals
 * (scenario scaffolding) so the function bodies below stay verbatim. */
static struct pccard_cis_quirk *pccard_cis_quirks;
static int n_pccard_cis_quirks;

/* ---------------- VERBATIM pccard_cis_quirks.c:201-216 ----------------- */

static int
pccard_cis_quirk_match(struct pccard_softc *sc, struct pccard_cis_quirk *q)
{
	if ((sc->card.manufacturer == q->manufacturer) &&
		(sc->card.product == q->product) &&
		(((sc->card.manufacturer != PCMCIA_VENDOR_INVALID) &&
		  (sc->card.product != PCMCIA_PRODUCT_INVALID)) ||
		 ((sc->card.manufacturer == PCMCIA_VENDOR_INVALID) &&
		  (sc->card.product == PCMCIA_PRODUCT_INVALID) &&
		  sc->card.cis1_info[0] &&
		  (strcmp(sc->card.cis1_info[0], q->cis1_info[0]) == 0) &&
		  sc->card.cis1_info[1] &&
		  (strcmp(sc->card.cis1_info[1], q->cis1_info[1]) == 0))))
		return (1);
	return (0);
}

/* ------------- VERBATIM pccard_cis_quirks.c:218-292 -------------------- */

void
pccard_check_cis_quirks(struct pccard_softc *sc)	/* RIG: was (device_t dev) */
{
	/* RIG: struct pccard_softc *sc = PCCARD_SOFTC(dev); -- now a param */
	int wiped = 0;
	int i, j;
	struct pccard_function *pf, *pf_next, *pf_last;
	struct pccard_config_entry *cfe, *cfe_next;
	struct pccard_cis_quirk *q;

	pf = NULL;
	pf_last = NULL;

	for (i=0; i<n_pccard_cis_quirks; i++) {
		q = &pccard_cis_quirks[i];
		if (!pccard_cis_quirk_match(sc, q))
			continue;
		if (!wiped) {
			if (bootverbose) {
				/* RIG: device_printf(dev,...) -> printf; body
				 * verbatim, not reached (bootverbose==0) */
				qlog("using CIS quirks for ");
				for (j = 0; j < 4; j++) {
					if (sc->card.cis1_info[j] == NULL)
						break;
					if (j)
						kprintf(", ");
					kprintf("%s", sc->card.cis1_info[j]);
				}
				kprintf("\n");
			}

			for (pf = STAILQ_FIRST(&sc->card.pf_head); pf != NULL;
			     pf = pf_next) {
				for (cfe = STAILQ_FIRST(&pf->cfe_head); cfe != NULL;
				     cfe = cfe_next) {
					cfe_next = STAILQ_NEXT(cfe, cfe_list);
					kfree(cfe, M_DEVBUF);
				}
				pf_next = STAILQ_NEXT(pf, pf_list);
				kfree(pf, M_DEVBUF);
			}

			STAILQ_INIT(&sc->card.pf_head);
			wiped = 1;
		}

		if (pf_last == q->pf) {
			cfe = kmalloc(sizeof(*cfe), M_DEVBUF, M_NOWAIT);
			if (cfe == NULL) {
				device_printf(dev, "no memory for quirk (1)\n");
				continue;
			}
			*cfe = *q->cfe;
			STAILQ_INSERT_TAIL(&pf->cfe_head, cfe, cfe_list);
		} else {
			pf = kmalloc(sizeof(*pf), M_DEVBUF, M_NOWAIT);
			if (pf == NULL) {
				device_printf(dev,
					"no memory for pccard function\n");
				continue;
			}
			*pf = *q->pf;
			STAILQ_INIT(&pf->cfe_head);
			cfe = kmalloc(sizeof(*cfe), M_DEVBUF, M_NOWAIT);
			if (cfe == NULL) {
				kfree(pf, M_DEVBUF);
				device_printf(dev, "no memory for quirk (2)\n");
				continue;
			}
			*cfe = *q->cfe;
			STAILQ_INSERT_TAIL(&pf->cfe_head, cfe, cfe_list);
			STAILQ_INSERT_TAIL(&sc->card.pf_head, pf, pf_list);
			pf_last = q->pf;
		}
	}
}

/* ---- fix.diff variant: verbatim + the two guard lines (marked FIX) ---- */

void
pccard_check_cis_quirks_fixed(struct pccard_softc *sc)
{
	int wiped = 0;
	int i, j;
	struct pccard_function *pf, *pf_next, *pf_last;
	struct pccard_config_entry *cfe, *cfe_next;
	struct pccard_cis_quirk *q;

	pf = NULL;
	pf_last = NULL;

	for (i=0; i<n_pccard_cis_quirks; i++) {
		q = &pccard_cis_quirks[i];
		if (!pccard_cis_quirk_match(sc, q))
			continue;
		if (q->pf == NULL || q->cfe == NULL)	/* FIX: malformed */
			continue;				/* FIX: entry */
		if (!wiped) {
			for (pf = STAILQ_FIRST(&sc->card.pf_head); pf != NULL;
			     pf = pf_next) {
				for (cfe = STAILQ_FIRST(&pf->cfe_head); cfe != NULL;
				     cfe = cfe_next) {
					cfe_next = STAILQ_NEXT(cfe, cfe_list);
					kfree(cfe, M_DEVBUF);
				}
				pf_next = STAILQ_NEXT(pf, pf_list);
				kfree(pf, M_DEVBUF);
			}

			STAILQ_INIT(&sc->card.pf_head);
			wiped = 1;
		}

		if (pf_last == q->pf) {
			cfe = kmalloc(sizeof(*cfe), M_DEVBUF, M_NOWAIT);
			if (cfe == NULL) {
				device_printf(dev, "no memory for quirk (1)\n");
				continue;
			}
			*cfe = *q->cfe;
			STAILQ_INSERT_TAIL(&pf->cfe_head, cfe, cfe_list);
		} else {
			pf = kmalloc(sizeof(*pf), M_DEVBUF, M_NOWAIT);
			if (pf == NULL) {
				device_printf(dev,
					"no memory for pccard function\n");
				continue;
			}
			*pf = *q->pf;
			STAILQ_INIT(&pf->cfe_head);
			cfe = kmalloc(sizeof(*cfe), M_DEVBUF, M_NOWAIT);
			if (cfe == NULL) {
				kfree(pf, M_DEVBUF);
				pf_last = NULL;		/* FIX: pf no longer
							 * tracks pf_last */
				device_printf(dev, "no memory for quirk (2)\n");
				continue;
			}
			*cfe = *q->cfe;
			STAILQ_INSERT_TAIL(&pf->cfe_head, cfe, cfe_list);
			STAILQ_INSERT_TAIL(&sc->card.pf_head, pf, pf_list);
			pf_last = q->pf;
		}
	}
}

/* ---------------- scenario scaffolding --------------------------------- */

/* shipped templates, pccard_cis_quirks.c:51-176 (values verbatim) */
static struct pccard_function tpl_3cxem556_f0 = {
	0, 6 /*PCCARD_FUNCTION_NETWORK*/, 0x07, 0x800, 0x63,
};
static struct pccard_config_entry tpl_3cxem556_f0_cfe0 = {
	0x07, 0x0010|0x0020|0x0100, 1 /*PCCARD_IFTYPE_IO*/, 1, 4,
	{ { 0x0010, 0 } }, 0xffff, 0, { }, 0,
};
static struct pccard_function tpl_3cxem556_f1 = {
	1, 2 /*PCCARD_FUNCTION_SERIAL*/, 0x27, 0x900, 0x63,
};
static struct pccard_config_entry tpl_3cxem556_f1_cfe0 = {
	0x27, 0x0010|0x0100, 3, 1, 3, { { 0x0008, 0 } }, 0xffff, 0, { }, 0,
};

/* a "fake CIS" function the wipe loop must free (simulates pccard_read_cis
 * output present at pccard.c:198 call time) */
static void
seed_card_functions(struct pccard_softc *sc, int npf, int ncfe_per_pf)
{
	struct pccard_function *pf;
	struct pccard_config_entry *cfe;
	int i, j;

	STAILQ_INIT(&sc->card.pf_head);
	for (i = 0; i < npf; i++) {
		pf = qk_alloc(sizeof(*pf));
		memset(pf, 0, sizeof(*pf));
		pf->number = i;
		STAILQ_INIT(&pf->cfe_head);
		for (j = 0; j < ncfe_per_pf; j++) {
			cfe = qk_alloc(sizeof(*cfe));
			memset(cfe, 0, sizeof(*cfe));
			cfe->number = j;
			STAILQ_INSERT_TAIL(&pf->cfe_head, cfe, cfe_list);
		}
		STAILQ_INSERT_TAIL(&sc->card.pf_head, pf, pf_list);
	}
}

static void
rig_reset(void)
{
	int i;

	for (i = 0; i < qk_used; i++)
		free(qk_pool[i].p);
	memset(qk_pool, 0, sizeof(qk_pool));
	qk_used = 0;
	qk_alloc_calls = 0;
	qk_fail_nth = -1;
	pccard_cis_quirks = NULL;
	n_pccard_cis_quirks = 0;
}

static int
list_card(struct pccard_softc *sc, const char *tag)
{
	struct pccard_function *pf;
	struct pccard_config_entry *cfe;
	int n = 0;

	STAILQ_FOREACH(pf, &sc->card.pf_head, pf_list) {
		n++;
		qlog("  %s live pf#%d ccr_base=0x%x cfe:", tag,
		    pf->number, pf->ccr_base);
		STAILQ_FOREACH(cfe, &pf->cfe_head, cfe_list)
			qlog(" #%d", cfe->number);
		qlog("\n");
	}
	return n;
}

/* ---------------- scenarios -------------------------------------------- */

static int sc_negative(void)
{
	struct pccard_softc s, *sc = &s;
	struct pccard_cis_quirk tbl[] = {
		{ 0x0101, 0x0035, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f0, &tpl_3cxem556_f0_cfe0 },
		{ 0x0101, 0x0035, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f1, &tpl_3cxem556_f1_cfe0 },
	};

	rig_reset();
	qlog("== negative: shipped-table shape (func0,func1), no alloc failure\n");
	memset(sc, 0, sizeof(*sc));
	seed_card_functions(sc, 2, 2);	/* counts as kmalloc calls 1..6 */
	sc->card.manufacturer = 0x0101;
	sc->card.product = 0x0035;
	pccard_cis_quirks = tbl;
	n_pccard_cis_quirks = 2;
	pccard_check_cis_quirks(sc);
	list_card(sc, "neg");
	return qk_audit("negative");
}

static int sc_shippedfail(void)
{
	struct pccard_softc s, *sc = &s;
	struct pccard_cis_quirk tbl[] = {
		{ 0x0101, 0x0035, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f0, &tpl_3cxem556_f0_cfe0 },
		{ 0x0101, 0x0035, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f1, &tpl_3cxem556_f1_cfe0 },
	};
	int bad;

	rig_reset();
	qlog("== shippedfail: shipped-table shape, cfe alloc of quirk#2 fails "
	    "(fail_nth=10)\n");
	/* seed 6 blocks, then quirk#1 allocates pf(7),cfe(8->would be OK)...
	 * we want quirk#2's cfe: pf(7),cfe(8),pf(9),cfe(10)FAIL -> fail_nth=10 */
	memset(sc, 0, sizeof(*sc));
	seed_card_functions(sc, 2, 2);
	sc->card.manufacturer = 0x0101;
	sc->card.product = 0x0035;
	pccard_cis_quirks = tbl;
	n_pccard_cis_quirks = 2;
	qk_fail_nth = 10;
	pccard_check_cis_quirks(sc);
	list_card(sc, "shipped");
	bad = qk_audit("shippedfail");
	qlog("  (expected: CLEAN -- with the shipped 2-entry shape the loop "
	    "ends after the failure; fast path never runs)\n");
	return bad;
}

static int sc_repeat(int fixed)
{
	struct pccard_softc s, *sc = &s;
	/* future-maintainer table: a third quirk for the same card re-using
	 * the func0 template (the exact pattern quirks.c:179-186 already
	 * uses to share templates across two product ids) */
	struct pccard_cis_quirk tbl[] = {
		{ 0x0101, 0x0035, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f0, &tpl_3cxem556_f0_cfe0 },
		{ 0x0101, 0x0035, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f1, &tpl_3cxem556_f1_cfe0 },
		{ 0x0101, 0x0035, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f0, &tpl_3cxem556_f0_cfe0 },
	};
	int bad;

	rig_reset();
	qlog("== %srepeat: 3-entry table re-using func0 template, quirk#2 cfe "
	    "alloc fails (fail_nth=10)\n", fixed ? "fixed_" : "");
	memset(sc, 0, sizeof(*sc));
	seed_card_functions(sc, 2, 2);
	sc->card.manufacturer = 0x0101;
	sc->card.product = 0x0035;
	pccard_cis_quirks = tbl;
	n_pccard_cis_quirks = 3;
	qk_fail_nth = 10;
	if (fixed)
		pccard_check_cis_quirks_fixed(sc);
	else
		pccard_check_cis_quirks(sc);
	list_card(sc, fixed ? "fixed" : "repeat");
	bad = qk_audit(fixed ? "fixed_repeat" : "repeat");
	qlog("  offsetof(struct pccard_function, cfe_head) = %zu "
	    "(STAILQ_INSERT_TAIL writes sqh_first at +0 and sqh_last at +8 "
	    "of cfe_head)\n", offsetof(struct pccard_function, cfe_head));
	if (!fixed && bad)
		qlog("RESULT: DF-5585 repeat: quirk#3 fast path "
		    "(pf_last==q->pf) STAILQ_INSERT_TAIL'd a cfe into the pf "
		    "kfree'd at pccard_cis_quirks.c:282 -> write-after-free\n");
	if (fixed && !bad)
		qlog("RESULT: DF-5585 fixed_repeat: pf_last=NULL guard keeps "
		    "the invariant; no freed-memory writes\n");
	return bad;
}

static int sc_nulltemplate(int fixed)
{
	struct pccard_softc s, *sc = &s;
	struct pccard_cis_quirk tbl[] = {
		{ 0x0101, 0x0035, { NULL, NULL, NULL, NULL },
		  NULL, &tpl_3cxem556_f0_cfe0 },	/* pf == NULL */
	};
	int bad, st;
	pid_t pid;

	rig_reset();
	qlog("== %snulltemplate: entry with pf==NULL; the wipe loop "
	    "(:248-257) always exits with pf==NULL (it is the loop "
	    "variable), so pf_last==NULL==q->pf takes the fast path and "
	    "INSERT_TAIL dereferences &pf->cfe_head == 0x28\n",
	    fixed ? "fixed_" : "");
	if (!fixed) {
		/* the unfixed run writes to the NULL page: run it in a child
		 * so the rig can report the fault instead of dying */
		fflush(stdout);
		pid = fork();
		if (pid == 0) {
			memset(sc, 0, sizeof(*sc));
			seed_card_functions(sc, 2, 2);
			sc->card.manufacturer = 0x0101;
			sc->card.product = 0x0035;
			pccard_cis_quirks = tbl;
			n_pccard_cis_quirks = 1;
			pccard_check_cis_quirks(sc);
			_exit(0);	/* not reached if it faults */
		}
		waitpid(pid, &st, 0);
		if (WIFSIGNALED(st) && WTERMSIG(st) == 11) {
			qlog("  child died on SIGSEGV writing near address "
			    "0x28/0x30 -- the kernel equivalent is a NULL-"
			    "page write (deterministic panic)\n");
			qlog("RESULT: DF-5585 nulltemplate: NULL pf template "
			    "+ pf_last(NULL)==q->pf fast path -> "
			    "&pf->cfe_head deref at offset 40 of NULL\n");
			return 1;
		}
		qlog("  child exited status=%d (unexpected)\n", st);
		return 0;
	}
	memset(sc, 0, sizeof(*sc));
	seed_card_functions(sc, 2, 2);
	sc->card.manufacturer = 0x0101;
	sc->card.product = 0x0035;
	pccard_cis_quirks = tbl;
	n_pccard_cis_quirks = 1;
	pccard_check_cis_quirks_fixed(sc);
	list_card(sc, "fixed");
	bad = qk_audit("fixed_nulltemplate");
	if (!bad)
		qlog("RESULT: DF-5585 fixed_nulltemplate: NULL-template guard "
		    "skips the entry; no freed-memory writes, no fault\n");
	return bad;
}

/* machine-checked unreachability proof for the SHIPPED table */
static int sc_matchmatrix(void)
{
	/* full shipped table, pccard_cis_quirks.c:178-197; CIS strings per
	 * pccarddevs:327,332 (&sp == space, see pccarddevs2h.awk) */
	static struct pccard_function tpl_bi_f0 = {
		0, 6, 0x07, 0x1000, 0x267 };
	static struct pccard_config_entry tpl_bi_f0_cfe0 = {
		0x07, 0x0010|0x0020|0x0100, 3, 1, 5, { { 0x0020, 0 } },
		0xffff, 0, { }, 0 };
	static struct pccard_function tpl_bi_f1 = {
		1, 2, 0x27, 0x1100, 0x277 };
	static struct pccard_config_entry tpl_bi_f1_cfe0 = {
		0x27, 0x0010|0x0100, 3, 1, 3, { { 0x0008, 0 } },
		0xffff, 0, { }, 0 };
	static struct pccard_function tpl_svec_f0 = {
		0, 6, 0x1, 0x100, 0x1 };
	static struct pccard_config_entry tpl_svec_f0_cfe0 = {
		0x1, 0x0001|0x0002|0x0004|0x0008|0x0020, 1, 1, 5,
		{ { 0x20, 0x300 } }, 0xdeb8, 0, { }, 0 };
	static struct pccard_function tpl_ndc_f0 = {
		0, 6, 0x23, 0x3f8, 0x3 };
	static struct pccard_config_entry tpl_ndc_f0_cfe0 = {
		0x20, 0x0001|0x0020|0x0100, 1, 1, 5,
		{ { 0x20, 0x300 } }, 0xdeb8, 0, { }, 0 };

	static struct pccard_cis_quirk shipped[] = {
		{ 0x0101, 0x0035, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f0, &tpl_3cxem556_f0_cfe0 },
		{ 0x0101, 0x0035, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f1, &tpl_3cxem556_f1_cfe0 },
		{ 0x0101, 0x003d, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f0, &tpl_3cxem556_f0_cfe0 },
		{ 0x0101, 0x003d, { NULL, NULL, NULL, NULL },
		  &tpl_3cxem556_f1, &tpl_3cxem556_f1_cfe0 },
		{ 0x0101, 0x0556, { NULL, NULL, NULL, NULL },
		  &tpl_bi_f0, &tpl_bi_f0_cfe0 },
		{ 0x0101, 0x0556, { NULL, NULL, NULL, NULL },
		  &tpl_bi_f1, &tpl_bi_f1_cfe0 },
		{ -1, -1, { "SVEC", "FD605 PCMCIA EtherNet Card", NULL, NULL },
		  &tpl_svec_f0, &tpl_svec_f0_cfe0 },
		{ -1, -1, { "NDC", "Ethernet", NULL, NULL },
		  &tpl_ndc_f0, &tpl_ndc_f0_cfe0 },
	};
	/* every card identity the shipped table can possibly match */
	struct {
		const char *name;
		int32_t man, prod;
		char *s0, *s1;
	} ids[] = {
		{ "3COM 3CXEM556     ", 0x0101, 0x0035, NULL, NULL },
		{ "3COM 3CXEM556INT  ", 0x0101, 0x003d, NULL, NULL },
		{ "3COM 3CCFEM556BI  ", 0x0101, 0x0556, NULL, NULL },
		{ "SVEC LANCARD      ", -1, -1,
		  (char *)"SVEC", (char *)"FD605 PCMCIA EtherNet Card" },
		{ "NDC ND5100_E      ", -1, -1,
		  (char *)"NDC", (char *)"Ethernet" },
		{ "no-match card     ", 0xdead, 0xbeef, NULL, NULL },
	};
	int n_ids = sizeof(ids) / sizeof(ids[0]);
	int n_q = sizeof(shipped) / sizeof(shipped[0]);
	int c, i, nm, fastpath_ever = 0;
	struct pccard_softc s, *sc = &s;
	struct pccard_function *prev_tpl;

	rig_reset();
	qlog("== matchmatrix: every card identity the SHIPPED 8-entry table "
	    "can match\n");
	for (c = 0; c < n_ids; c++) {
		memset(sc, 0, sizeof(*sc));
		STAILQ_INIT(&sc->card.pf_head);
		sc->card.manufacturer = ids[c].man;
		sc->card.product = ids[c].prod;
		if (ids[c].s0) {
			snprintf(sc->card.cis1_info_buf, 128, "%s%c%s%c",
			    ids[c].s0, 0, ids[c].s1, 0);
			sc->card.cis1_info[0] = sc->card.cis1_info_buf;
			sc->card.cis1_info[1] = sc->card.cis1_info_buf +
			    strlen(ids[c].s0) + 1;
		}
		qlog("  card %s -> matches:", ids[c].name);
		nm = 0;
		prev_tpl = NULL;
		for (i = 0; i < n_q; i++) {
			if (pccard_cis_quirk_match(sc, &shipped[i])) {
				qlog(" [%d:pf=%s]", i,
				    shipped[i].pf == &tpl_3cxem556_f0 ? "xemf0" :
				    shipped[i].pf == &tpl_3cxem556_f1 ? "xemf1" :
				    shipped[i].pf == &tpl_bi_f0 ? "bif0" :
				    shipped[i].pf == &tpl_bi_f1 ? "bif1" :
				    shipped[i].pf == &tpl_svec_f0 ? "svec" :
				    shipped[i].pf == &tpl_ndc_f0 ? "ndc" :
				    "??");
				if (nm > 0 && prev_tpl == shipped[i].pf) {
					fastpath_ever = 1;
					qlog(" FAST-PATH-REACHED");
				}
				prev_tpl = shipped[i].pf;
				nm++;
			}
		}
		if (nm == 0)
			qlog(" none");
		qlog("  (set size %d)\n", nm);
	}
	qlog("RESULT: DF-5585 matchmatrix: shipped table fast-path reachable "
	    "by ANY card identity: %s (match sets <= 2 entries, templates "
	    "within a set always differ)\n",
	    fastpath_ever ? "YES" : "NO");
	return fastpath_ever;
}

int
main(int argc, char **argv)
{
	const char *w = (argc > 1) ? argv[1] : "all";
	int bad = 0;

	if (!strcmp(w, "all")) {
		bad += sc_negative();
		bad += sc_shippedfail();
		bad += sc_repeat(0);
		bad += sc_repeat(1);
		bad += sc_nulltemplate(0);
		bad += sc_nulltemplate(1);
		bad += sc_matchmatrix();
	} else if (!strcmp(w, "negative")) {
		bad += sc_negative();
	} else if (!strcmp(w, "shippedfail")) {
		bad += sc_shippedfail();
	} else if (!strcmp(w, "repeat")) {
		bad += sc_repeat(0);
	} else if (!strcmp(w, "fixed_repeat")) {
		bad += sc_repeat(1);
	} else if (!strcmp(w, "nulltemplate")) {
		bad += sc_nulltemplate(0);
	} else if (!strcmp(w, "fixed_nulltemplate")) {
		bad += sc_nulltemplate(1);
	} else if (!strcmp(w, "matchmatrix")) {
		bad += sc_matchmatrix();
	} else {
		fprintf(stderr, "unknown scenario %s\n", w);
		return 2;
	}
	qlog("DF-5585 rig done (%s): freed-write findings=%d\n", w, bad);
	return bad ? 1 : 0;
}
