/*
 * camqtest.ko -- pass-2 DF-5550/DF-5551 evidence harness.
 *
 * Exercises the PRODUCTION kernel camq_* primitives
 * (sys/bus/cam/cam_queue.c) exactly the way in-tree callers do when the
 * documented preconditions are violated, and proves on the running
 * stock kernel (no DIAGNOSTIC) that:
 *
 *   T1  camq_resize(new_size < entries)  -- the #ifdef DIAGNOSTIC panic
 *       at cam_queue.c:117 is compiled out -- silently:
 *         (a) bcopy()s entries*8 bytes into a new_size*8 kmalloc buffer
 *             (heap OOB WRITE), and
 *         (b) leaves queue->entries > queue->array_size, so every
 *             subsequent camq operation indexes past the array.
 *   T2  canary proof of the OOB write: flood the same M_CAMQ size class
 *       with canary chunks, punch a hole, make the resize land in it,
 *       then scan the neighbours for the overflow.
 *   T3  camq_insert() into a queue whose entries == array_size (the
 *       #ifdef DIAGNOSTIC panic at cam_queue.c:149 is compiled out):
 *       writes queue_array[array_size+1] -- one pointer PAST the array
 *       (heap OOB WRITE of a kernel pointer).
 *   T4  (panic demo, off by default) drive the corrupted queue into
 *       camq_remove() until it dereferences a NULL/OOB slot -- the
 *       DF-5462 lead's exact `camq_remove+0x35  movl $0xffffffff,0x8(%r12)`
 *       signature.
 *
 * Knobs (sysctl debug.camqtest):
 *   1 = T1  (state proof, no panic expected)
 *   2 = T2  (canary OOB-write proof)
 *   3 = T3  (insert-full OOB write + canary)
 *   4 = T4  (DELIBERATE PANIC -- camq_remove NULL-slot signature)
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/types.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/cam_queue.h>

MALLOC_DEFINE(CAMQTEST_MALLOC, "camqtest", "camqtest canaries");

#define NENTRY		16
#define NCANARY		64
#define CANARY_BYTE	0xA5

struct camq	 tq;
static cam_pinfo	*pe[NENTRY];
static u_char		*canary[NCANARY];
static int		 canary_len;

static void
t_clear(void)
{
	camq_fini(&tq);
	bzero(&tq, sizeof(tq));
}

static void
canary_alloc(int n, size_t sz)
{
	int i;

	canary_len = sz;
	for (i = 0; i < n; i++) {
		canary[i] = kmalloc(sz, CAMQTEST_MALLOC, M_INTWAIT);
		memset(canary[i], CANARY_BYTE, sz);
	}
}

static int
canary_scan(const char *tag)
{
	int i, j, bad = 0;

	for (i = 0; i < NCANARY; i++) {
		if (canary[i] == NULL)
			continue;
		for (j = 0; j < canary_len; j++) {
			if (canary[i][j] != CANARY_BYTE) {
				kprintf("[CAMQTEST] %s: canary %d (%p) "
				    "CORRUPTED at +%d: %02x (was %02x)\n",
				    tag, i, canary[i], j,
				    canary[i][j], CANARY_BYTE);
				bad++;
				break;
			}
		}
	}
	if (bad == 0)
		kprintf("[CAMQTEST] %s: all %d canaries intact\n", tag,
		    NCANARY);
	else
		kprintf("[CAMQTEST] %s: %d CANARY(IES) OVERWRITTEN "
		    "-> heap OOB WRITE proven\n", tag, bad);
	return (bad);
}

static void
canary_free_all(void)
{
	int i;

	for (i = 0; i < NCANARY; i++) {
		if (canary[i] != NULL) {
			kfree(canary[i], CAMQTEST_MALLOC);
			canary[i] = NULL;
		}
	}
}

static void
mkentry(int i, u_int32_t prio)
{
	pe[i] = kmalloc(sizeof(cam_pinfo), CAMQTEST_MALLOC,
	    M_INTWAIT | M_ZERO);
	pe[i]->priority = prio;
	pe[i]->generation = i + 1;
	pe[i]->index = CAM_UNQUEUED_INDEX;
}

static void
pe_free_all(void)
{
	int i;

	for (i = 0; i < NENTRY; i++) {
		if (pe[i] != NULL) {
			kfree(pe[i], CAMQTEST_MALLOC);
			pe[i] = NULL;
		}
	}
}

/*
 * T1: state proof.  queue of capacity 4, 4 entries inserted,
 * then camq_resize(..., 3) exactly like xpt_remove_periph()
 * (cam_xpt.c:1613) does for a periph that is still queued on drvq.
 */
static void
t1_state(void)
{
	int i;

	t_clear();
	camq_init(&tq, 4);
	for (i = 0; i < 4; i++) {
		mkentry(i, (u_int32_t)(5 * (i + 1)));
		camq_insert(&tq, pe[i]);
	}
	kprintf("[CAMQTEST] T1 before resize: entries=%d array_size=%d\n",
	    tq.entries, tq.array_size);
	kprintf("[CAMQTEST] T1 calling camq_resize(&q, 3) with entries=4 "
	    "(xpt_remove_periph shape)...\n");
	camq_resize(&tq, 3);
	kprintf("[CAMQTEST] T1 after resize: entries=%d array_size=%d "
	    "(INVARIANT VIOLATED: entries > array_size; no panic, "
	    "no DIAGNOSTIC guard on stock kernel)\n",
	    tq.entries, tq.array_size);
	/*
	 * Slot 4 is now past the 3-pointer array: reading the head of a
	 * 4-entry heap touches OOB memory.
	 */
	kprintf("[CAMQTEST] T1 OOB READ via CAMQ_GET_HEAD path: "
	    "queue_array[4]=%p (past %d-ptr array)\n",
	    tq.queue_array[4], tq.array_size);

	/* remove all 4 "entries": removals 1..3 in-bounds, removal 4 OOB */
	for (i = 0; i < 4; i++) {
		cam_pinfo *r = camq_remove(&tq, CAMQ_HEAD);
		kprintf("[CAMQTEST] T1 remove head -> %p (entries now %d)\n",
		    r, tq.entries);
	}
	t_clear();
	pe_free_all();
	kprintf("[CAMQTEST] T1 done (no panic: corruption is silent)\n");
}

/*
 * T2: canary proof of the resize OOB write.
 * Geometry: new_size=4 (32-byte chunk, exact zone-size multiple),
 * entries=6 -> bcopy 48 bytes into 32 -> 16 bytes into the next chunk.
 */
static void
t2_canary(void)
{
	int i;

	canary_free_all();
	t_clear();

	/* flood the 32-byte size class with canaries */
	canary_alloc(NCANARY, 32);
	/* punch holes so the resize allocation lands between canaries */
	for (i = 0; i < NCANARY; i += 2)
		kfree(canary[i], CAMQTEST_MALLOC), canary[i] = NULL;

	camq_init(&tq, 6);
	for (i = 0; i < 6; i++) {
		mkentry(i, (u_int32_t)(3 * (i + 1)));
		camq_insert(&tq, pe[i]);
	}
	kprintf("[CAMQTEST] T2 before: entries=%d array_size=%d\n",
	    tq.entries, tq.array_size);
	kprintf("[CAMQTEST] T2 camq_resize(&q, 4) with entries=6: "
	    "bcopy(6*8=48) into 4*8=32 buffer\n");
	camq_resize(&tq, 4);
	kprintf("[CAMQTEST] T2 after: entries=%d array_size=%d\n",
	    tq.entries, tq.array_size);
	canary_scan("T2");
	t_clear();
	pe_free_all();
	canary_free_all();
}

/*
 * T3: camq_insert on a full queue -- the guard at cam_queue.c:148-151
 * is #ifdef DIAGNOSTIC; on stock kernels the insert writes
 * queue_array[array_size+1], one pointer past the array.
 * Geometry: array_size=4 (32-byte chunk), canaries around.
 */
static void
t3_insert_full(void)
{
	int i;

	canary_free_all();
	t_clear();
	canary_alloc(NCANARY, 32);
	for (i = 0; i < NCANARY; i += 2)
		kfree(canary[i], CAMQTEST_MALLOC), canary[i] = NULL;

	camq_init(&tq, 4);
	for (i = 0; i < 4; i++) {
		mkentry(i, (u_int32_t)(7 * (i + 1)));
		camq_insert(&tq, pe[i]);
	}
	mkentry(4, 1);
	kprintf("[CAMQTEST] T3 inserting 5th entry into 4-capacity queue "
	    "(guard is DIAGNOSTIC-only)...\n");
	camq_insert(&tq, pe[4]);
	kprintf("[CAMQTEST] T3 after: entries=%d array_size=%d "
	    "(wrote queue_array[%d] == %p PAST the array)\n",
	    tq.entries, tq.array_size, tq.entries, pe[4]);
	canary_scan("T3");
	t_clear();
	pe_free_all();
	canary_free_all();
}

/*
 * T4: deliberate panic reproducing the DF-5462 lead signature:
 * a queue whose entries counts a slot that is NULL / beyond the array,
 * then camq_remove() -> removed_entry == NULL ->
 * `movl $0xffffffff,0x8(%r12)` at cam_queue.c:183.
 * Shape: entries=1==index, slot 1 NULL.
 */
static void
t4_panic(void)
{
	cam_pinfo *r;

	t_clear();
	camq_init(&tq, 1);
	mkentry(0, 1);
	camq_insert(&tq, pe[0]);
	/* simulate the xpt_remove_periph shrink of a still-queued periph */
	camq_resize(&tq, 0);
	kprintf("[CAMQTEST] T4 state: entries=%d array_size=%d; "
	    "slot1=%p\n", tq.entries, tq.array_size, tq.queue_array[1]);
	/* NULL the slot the way a racing bcopy/swap leaves it */
	tq.queue_array[1] = NULL;
	kprintf("[CAMQTEST] T4 calling camq_remove(&q, CAMQ_HEAD) -> "
	    "expect NULL->index write panic (camq_remove+0x35)\n");
	r = camq_remove(&tq, CAMQ_HEAD);
	kprintf("[CAMQTEST] T4 no panic?! removed=%p\n", r);
	t_clear();
	pe_free_all();
}

static int
camqtest_sysctl(SYSCTL_HANDLER_ARGS)
{
	int v = 0, error;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL || v == 0)
		return (error);
	switch (v) {
	case 1: t1_state(); break;
	case 2: t2_canary(); break;
	case 3: t3_insert_full(); break;
	case 4: t4_panic(); break;
	default:
		kprintf("[CAMQTEST] unknown test %d\n", v);
	}
	return (0);
}

SYSCTL_NODE(_debug, OID_AUTO, camqtest, CTLFLAG_RW, 0, "camqtest");
SYSCTL_PROC(_debug_camqtest, OID_AUTO, run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, camqtest_sysctl, "I", "run test 1=state 2=canary-resize "
    "3=canary-insert 4=panic-demo");

static int
camqtest_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		bzero(&tq, sizeof(tq));
		bzero(pe, sizeof(pe));
		bzero(canary, sizeof(canary));
		kprintf("[CAMQTEST] loaded (production camq_* primitives)\n");
		break;
	case MOD_UNLOAD:
		t_clear();
		pe_free_all();
		canary_free_all();
		kprintf("[CAMQTEST] unloaded\n");
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t camqtest_mod = { "camqtest", camqtest_modevent, NULL };
DECLARE_MODULE(camqtest, camqtest_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
