/*
 * DF-3584 trigger logic — the unchecked target index in isp_control():
 *
 *   case ISPCTL_RESET_DEV:
 *           chan = va_arg(ap, int);
 *           tgt  = va_arg(ap, int);
 *           if (IS_24XX(isp)) {
 *                   fcparam *fcp = FCPARAM(isp, chan);
 *                   hdlidx = fcp->isp_dev_map[tgt] - 1;      <-- tgt unchecked
 *
 * reached from the CAM XPT_RESET_DEV glue (isp_freebsd.c:4519-4523):
 *
 *           bus = cam_sim_bus(xpt_path_sim(ccb->ccb_h.path));
 *           tgt = ccb->ccb_h.target_id;
 *           tgt |= (bus << 16);               <-- bus bits left in tgt
 *           error = isp_control(isp, ISPCTL_RESET_DEV, bus, tgt);
 *
 * For a device on channel 1 the read lands at
 *   offsetof(fcparam, isp_dev_map) + 2*(65536 + target_id)
 *   = 20536 + 131072 (+2*target_id)  >= 151608 bytes into an allocation of
 * nchan*sizeof(fcparam) (21576 for 1 chan, 43152 for 2) — i.e. >= ~108 KB
 * past the end: a wild kernel read on every BDR issued to a channel>0 device.
 *
 * Harness: one RW page holding an fcparam-sized stand-in followed by
 * PROT_NONE; performs the identical isp_dev_map[tgt] load.
 */
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>

typedef void *XS_T;
typedef uint64_t XS_DMA_ADDR_T;
typedef struct { uint64_t t; } NANOTIME_T;
typedef struct { uint16_t param[16]; } mbreg_t;
#define MAILBOX_STORAGE 16
struct isposinfo { char pad[128]; };
#define ISP_KERNEL 1
#include <dev/disk/isp/isp_stds.h>
#include <dev/disk/isp/ispmbox.h>
#include <dev/disk/isp/ispvar.h>

static sigjmp_buf jb;
static char faultbuf[64];
static void *fault_at;

static void
handler(int sig, siginfo_t *si, void *uc)
{
	snprintf(faultbuf, sizeof (faultbuf), "SIGSEGV/%s", sig == SIGBUS ? "BUS" : "SEGV");
	fault_at = si->si_addr;
	siglongjmp(jb, 1);
}

int
main(void)
{
	size_t pagesz = 4096, allocsz = 2 * sizeof(fcparam);	/* 2-channel 24xx */
	uint8_t *map;
	fcparam *fcp;
	struct sigaction sa;
	int chan = 1, tgt, hdlidx;
	volatile int sink = 0;

	printf("sizeof(fcparam) = %zu; 2-channel allocation = %zu bytes\n",
	    sizeof(fcparam), allocsz);
	printf("offsetof(fcparam, isp_dev_map) = %zu (entries: %zu)\n",
	    offsetof(fcparam, isp_dev_map),
	    sizeof(((fcparam *)0)->isp_dev_map) / sizeof(uint16_t));

	map = mmap(NULL, 1 << 20, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
	if (map == MAP_FAILED) { perror("mmap"); return (1); }
	/* allocation stand-in: 43152 bytes spread over RW pages at the start */
	{
		size_t rw = (allocsz + pagesz - 1) / pagesz * pagesz;
		if (mprotect(map, rw, PROT_READ|PROT_WRITE) != 0) {
			perror("mprotect"); return (1);
		}
	}
	memset(map, 0, allocsz);
	fcp = (fcparam *)(void *)map;

	memset(&sa, 0, sizeof (sa));
	sa.sa_sigaction = handler;
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	tgt = 0 | (chan << 16);		/* exactly what XPT_RESET_DEV passes */
	printf("\nXPT_RESET_DEV on chan 1, target 0 => tgt = 0x%x\n", tgt);
	printf("read address = base + %zu + 2*%u = base + %zu\n",
	    offsetof(fcparam, isp_dev_map), tgt,
	    offsetof(fcparam, isp_dev_map) + 2 * (size_t)tgt);
	printf("=> %zd bytes past the end of the %zu-byte allocation\n\n",
	    (ssize_t)(offsetof(fcparam, isp_dev_map) + 2 * (size_t)tgt) - (ssize_t)allocsz,
	    allocsz);

	if (sigsetjmp(jb, 1) != 0) {
		printf("FAULT (%s) at %p => %zd bytes past allocation end\n",
		    faultbuf, fault_at, (ssize_t)((uint8_t *)fault_at - (map + allocsz)));
		printf("kernel impact: wild kernel read during BDR handling "
		    "(panic if unmapped / garbage index otherwise)\n");
		return (0);
	}
	/* the kernel read, verbatim (isp.c:4522) */
	hdlidx = fcp->isp_dev_map[tgt] - 1;
	sink += hdlidx;
	printf("ERROR: no fault occurred (sink=%d)\n", sink);
	return (1);
}
