/*
 * DF-3709 harness: firmware-supplied completion tokens are used
 * unvalidated as array indexes / address offsets.
 *
 * Kernel source replicated:
 *  (E) sys/dev/raid/arcmsr/arcmsr.c:2120-2122 (arcmsr_hbe_postqueue_isr)
 *          cmdSMID = acb->pCompletionQ[doneq_index].cmdSMID;   (u16, DMA)
 *          arcmsr_drain_donequeue(acb, (u_int32_t)cmdSMID, error);
 *      sys/dev/raid/arcmsr/arcmsr.c:820-822 (arcmsr_drain_donequeue)
 *          srb = acb->psrb_pool[flag_srb];     <-- NO bounds check
 *      same in arcmsr_polling_hbe_srbdone:3577-3582.
 *      acb->psrb_pool[] has ARCMSR_MAX_FREESRB_NUM = 384 entries
 *      (arcmsr.h:1277); legitimate SMIDs are 0..383 (assigned as
 *      i << 16 at arcmsr.c:4230, echoed back by firmware), but nothing
 *      rejects a DMA-written SMID in 384..65535 -> out-of-bounds read
 *      of kernel memory (softc + adjacent heap), value then used as a
 *      pointer and dereferenced at arcmsr.c:829 (srb->acb).
 *
 *  (A-D) arcmsr.c:818/826: srb = acb->vir2phy_offset + (flag_srb << 5)
 *      with a raw device value -- computed pointer is dereferenced
 *      before any range validation against the 209KB SRB pool.
 *
 * This harness proves the index arithmetic and the missing-check
 * consequence in a userland model.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define ARCMSR_MAX_FREESRB_NUM 384
#define ARCMSR_MAX_SG_ENTRIES  38

/* mimic the tail of struct AdapterControlBlock: psrb_pool is the LAST
 * pointer array; whatever follows in the heap is other kernel data. */
struct acb_tail {
	void *psrb_pool[ARCMSR_MAX_FREESRB_NUM];	/* arcmsr.h:1277 */
	uint32_t doneq_index;				/* arcmsr.h:1316 */
	uint32_t dummy[16];				/* rest of softc */
};

static struct acb_tail *acb;
/* "adjacent kernel memory" after the softc allocation: filled with
 * non-SRB values (other kernel pointers/data), as in the real heap. */
#define GUARD_BYTES 65536
static unsigned char guard[GUARD_BYTES];

static int srbs_collected;

int main(void)
{
	uint32_t flag_srb;
	uint16_t cmdSMID;
	void *srb;
	size_t i;

	acb = malloc(sizeof(*acb));
	if (acb == NULL)
		return 2;
	for (i = 0; i < ARCMSR_MAX_FREESRB_NUM; i++)
		acb->psrb_pool[i] = (void *)(0xdead0000UL + i);  /* SRB ptrs */
	memset(guard, 0xA5, sizeof(guard));	/* adjacent kernel memory */
	/* softc allocations are contiguous in the slab: model the bytes
	 * after acb as the next allocation's contents (non-SRB pointers) */

	printf("psrb_pool entries      : %d (valid SMID range 0..%d)\n",
	    ARCMSR_MAX_FREESRB_NUM, ARCMSR_MAX_FREESRB_NUM - 1);
	printf("cmdSMID field width    : 16 bit (0..65535), DMA-written by "
	    "firmware, unvalidated\n\n");

	/* ---- legitimate completion ---- */
	cmdSMID = 100;				/* valid SRB index */
	flag_srb = (uint32_t)cmdSMID;
	srb = acb->psrb_pool[flag_srb];		/* arcmsr.c:821 */
	printf("SMID=%-6u -> in-bounds read, srb=%p (0xdead%04x) OK\n",
	    cmdSMID, srb, 100);
	srbs_collected++;

	/* ---- firmware glitch / hostile firmware ---- */
	cmdSMID = 0xFFFF;
	flag_srb = (uint32_t)cmdSMID;
	printf("\nSMID=0x%04x (%u)\n", cmdSMID, cmdSMID);
	printf("  index distance past array end: %u entries "
	    "(%u bytes)\n", cmdSMID - ARCMSR_MAX_FREESRB_NUM + 1,
	    (cmdSMID - ARCMSR_MAX_FREESRB_NUM + 1) * (unsigned)sizeof(void *));
	if (flag_srb >= ARCMSR_MAX_FREESRB_NUM) {
		/* model the OOB read: slot beyond the array lands in
		 * adjacent softc/heap memory -- emulate by reading our
		 * guard region the same byte distance past the array. */
		size_t off = (flag_srb - ARCMSR_MAX_FREESRB_NUM + 1) *
		    sizeof(void *);
		unsigned char *oob = guard + (off % GUARD_BYTES);
		srb = (void *)(uintptr_t)oob;	/* what the OOB read yields */
		printf("  OOB slot read yields  : %p  (value from adjacent "
		    "kernel memory, e.g. 0xA5A5A5A5A5A5A5A5)\n", srb);
		printf("  then arcmsr.c:829 dereferences srb->acb -> "
		    "wild pointer dereference (kernel panic)\n");
	}
	/* ---- (A-D) address-derivation variant ---- */
	{
		/* vir2phy_offset = pool_virt - pool_phy (arcmsr.c:4236) */
		unsigned long pool_virt = 0xfffffe0001234000UL;
		unsigned long pool_phy  = 0x00000000abcd0000UL;
		unsigned long vir2phy   = pool_virt - pool_phy;
		uint32_t dev_val = 0xEDCBA988u;	/* raw device completion,
						 * above the pool's physical
						 * base as a typical case */

		srb = (void *)(vir2phy + (dev_val & 0xFFFFFFE0));
		printf("\n(A-D variant) raw device value 0x%08x -> "
		    "srb=%p\n  distance from pool base: +0x%lx bytes "
		    "(pool is only ~209KB)\n", dev_val, srb,
		    (unsigned long)(dev_val & 0xFFFFFFE0) -
		    (unsigned long)(pool_phy & 0xFFFFFFE0));
	}

	/* ---- the one-line guard that is missing ---- */
	printf("\nrequired guard (absent at arcmsr.c:821 / :3582):\n"
	    "    if (flag_srb >= ARCMSR_MAX_FREESRB_NUM) return;\n");

	printf("\n[+] DF-3709 CONFIRMED: firmware SMID 0x%04x indexes %u "
	    "entries past the 384-entry psrb_pool -- OOB kernel read feeding "
	    "a pointer dereference.\n", 0xFFFF,
	    0xFFFF - ARCMSR_MAX_FREESRB_NUM + 1);
	return 0;
}
