/*
 * v26harness.ko - sysctl-triggered verification harness for DF-2855, DF-2735,
 * DF-2789, DF-2678, DF-2814, DF-2722 (batch DF-2678..DF-2880 verify run).
 *
 * Trigger:  sysctl -w debug.v26harness=N
 *   1 = DF-2855 rman_reserve_resource window escape + inverted node
 *   2 = DF-2735 socket_wait() fd leak (leaks into the sysctl'ing process)
 *   3 = DF-2789 blist(65): out-of-range free is NOT caught; out-of-range block granted
 *   4 = DF-2678 vm_page_free_contig() leaks PG_FICTITIOUS alist pages above watermark
 *   5 = DF-2814 objcache_destroy() vs in-flight remote-cpu get/put (UAF)
 *   7 = DF-2789 blist(2081): phantom free of uninitialized leaf slot: out-of-range
 *       block + double-grant (no OOB)
 *   8 = DF-2789 blist(2081): free into unallocated child slot -> OOB walk on
 *       groomed garbage -> panic (RUN LAST)
 *   9 = DF-2722 kstrndup() reads str[maxlen] past a guard page -> panic (RUN LAST)
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/filedesc.h>
#include <sys/socketvar.h>
#include <sys/time.h>
#include <sys/blist.h>
#include <sys/rman.h>
#include <sys/socket.h>
#include <sys/objcache.h>

#include <sys/thread.h>
#include <sys/thread2.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
#include <vm/vm_page.h>
#include <vm/pmap.h>
#include <sys/spinlock.h>
#include <sys/spinlock2.h>

extern struct fileops socketops;	/* sys/kern/sys_socket.c */
extern vm_paddr_t vm_low_phys_reserved; /* sys/vm/vm_page.c:208 */

MALLOC_DEFINE(M_V26, "v26harness", "DF-2678..2880 verification harness");

/* ------------------------------------------------------------------ */
static int
count_fds(struct proc *p)
{
	struct filedesc *fdp = p->p_fd;
	int n = 0, i;

	spin_lock(&fdp->fd_spin);
	for (i = 0; i < fdp->fd_nfiles; i++)
		if (fdp->fd_files[i].fp)
			n++;
	spin_unlock(&fdp->fd_spin);
	return (n);
}

/* DF-2855: rman window arithmetic */
static int
t1_rman(void)
{
	struct rman rm;
	struct resource *r;
	int rc;

	bzero(&rm, sizeof(rm));
	rm.rm_type = RMAN_ARRAY;
	rm.rm_descr = "v26harness window";
	if ((rc = rman_init(&rm, mycpu->gd_cpuid)) != 0) {
		kprintf("V26[1] rman_init failed %d\n", rc);
		return (rc);
	}
	rman_manage_region(&rm, 0x0, 0x10000);

	/* case A: count-1 > end -> window escape */
	r = rman_reserve_resource(&rm, 0x0, 0x100, 0x2000, 0, NULL);
	kprintf("V26[1] rman req window [0x0,0x100] count=0x2000 -> %s\n",
		r ? "GRANTED" : "denied");
	if (r) {
		kprintf("V26[1] granted r_start=%#lx r_end=%#lx "
			"(r_end EXCEEDS caller window end 0x100 by %#lx)\n",
			r->r_start, r->r_end, r->r_end - 0x100UL);
	}

	/* case B: start+count-1 overflow -> inverted node */
	r = rman_reserve_resource(&rm, 0xFFFFFFFFFFFF0000UL, 0x1000, 0x20000, 0, NULL);
	kprintf("V26[1] rman req start=0xFFFFFFFFFFFF0000 count=0x20000 -> %s\n",
		r ? "GRANTED" : "denied");
	if (r) {
		kprintf("V26[1] granted r_start=%#lx r_end=%#lx INVERTED(r_end<r_start)=%d\n",
			r->r_start, r->r_end, r->r_end < r->r_start);
	}
	kprintf("V26[1] done (tree intentionally left; module test)\n");
	return (0);
}

/* DF-2735: socket_wait fd leak */
static int
t2_socket_wait(void)
{
	struct socket *so;
	struct timespec ts;
	int i, error, res;
	int before, after;

	error = socreate(AF_INET, &so, SOCK_DGRAM, 0, curthread);
	if (error) {
		kprintf("V26[2] socreate failed %d\n", error);
		return (error);
	}
	ts.tv_sec = 0;
	ts.tv_nsec = 10000000;		/* immediate timeout */

	before = count_fds(curproc);
	for (i = 0; i < 50; i++) {
		error = socket_wait(so, &ts, &res);
		if (error && i == 0)
			kprintf("V26[2] socket_wait returned %d on first call\n", error);
	}
	after = count_fds(curproc);
	kprintf("V26[2] socket_wait x50 (ts=2ns): fds before=%d after=%d "
		"LEAKED=%d (expected +50)\n", before, after, after - before);
	soclose(so, 0);
	return (0);
}

/* DF-2789 case 1: dead terminator guard, small tree */
static int
t3_blist_small(void)
{
	blist_t bl;
	swblk_t a0, a1, a2, a3, a4;
	char *g;
	size_t sz;

	/* learn size, then groom the bl_root chunk with zeros so the
	 * terminator bitmap reads all-allocated (leaf-free check passes) */
	bl = blist_create(65);
	sz = (size_t)bl->bl_rootblks * sizeof(blmeta_t);
	blist_destroy(bl);
	g = kmalloc(sz, M_V26, M_WAITOK);
	bzero(g, sz);
	kfree(g, M_V26);

	bl = blist_create(65);
	kprintf("V26[3] blist(65): bl_blocks=%ld bl_rootblks=%ld\n",
	    (long)bl->bl_blocks, (long)bl->bl_rootblks);
	blist_free(bl, 0, 65);		/* standard init: mark all free */
	a1 = blist_alloc(bl, 64);
	a2 = blist_alloc(bl, 1);
	kprintf("V26[3] after init: alloc(64) -> %ld, alloc(1) -> %ld "
	    "(all 65 real blocks allocated)\n", (long)a1, (long)a2);
	blist_free(bl, 128, 64);	/* OUT OF RANGE: terminator leaf */
	kprintf("V26[3] blist_free(128,64) OUT OF RANGE returned with NO "
	    "\"freeing unexpected range\" panic (meta guard checks PARENT: "
	    "dead code)\n");
	a3 = blist_alloc(bl, 64);
	kprintf("V26[3] alloc(64) -> %ld %s\n", (long)a3,
	    (a3 != SWAPBLK_NONE && a3 >= 65) ?
	    "*** OUT-OF-RANGE BLOCK GRANTED (bl_blocks=65) ***" : "(in-range)");
	blist_free(bl, 128, 64);	/* phantom free again */
	a4 = blist_alloc(bl, 64);
	kprintf("V26[3] alloc(64) after re-free -> %ld %s\n", (long)a4,
	    (a4 != SWAPBLK_NONE && a4 == a3) ?
	    "*** SAME BLOCK GRANTED TWICE (overlapping allocation) ***" : "");
	a0 = a1 + a2 + a3 + a4;	/* silence unused */
	blist_destroy(bl);
	return (0);
}

/* DF-2678: vm_page_free_contig leaks fictitious pages above watermark */
static int
t4_vm_page(void)
{
	vm_paddr_t save = vm_low_phys_reserved;
	vm_page_t m;
	void *p;
	int i, leaks = 0;
	long f0, f1, w0, w1;

	kprintf("V26[4] vm_low_phys_reserved=%#jx dma_pages=%ld\n",
	    (uintmax_t)save, (long)vmstats.v_dma_pages);

	f0 = vmstats.v_free_count;
	w0 = vmstats.v_wire_count;
	p = contigmalloc(65536, M_V26, M_WAITOK, 0ul, save - 1, PAGE_SIZE, 0ul);
	if (p == NULL) {
		kprintf("V26[4] contigmalloc below watermark failed\n");
		return (ENOMEM);
	}
	m = PHYS_TO_VM_PAGE(pmap_kextract((vm_offset_t)p));
	kprintf("V26[4] contigmalloc 64K (pa=%#jx): PG_FICTITIOUS=%d wire=%d\n",
	    (uintmax_t)m->phys_addr,
	    (m->flags & PG_FICTITIOUS) ? 1 : 0, m->wire_count);

	/* simulate the post-vm_page_startup_finish geometry: watermark lowered
	 * below this still-allocated fictitious block */
	vm_low_phys_reserved = 0;
	vm_page_free_contig(m, 65536);
	vm_low_phys_reserved = save;

	f1 = vmstats.v_free_count;
	w1 = vmstats.v_wire_count;
	kprintf("V26[4] after free_contig via ELSE branch: PG_FICTITIOUS=%d "
	    "wire=%d  vmstats: free %ld->%ld (delta %ld, NOT +16) wire %ld->%ld\n",
	    (m->flags & PG_FICTITIOUS) ? 1 : 0, m->wire_count,
	    f0, f1, f1 - f0, w0, w1);
	kprintf("V26[4] page leaked from both alist and queues: "
	    "16 pages of DMA reserve gone\n");

	/* exhaustion loop proves cumulative loss from the alist */
	for (i = 0; i < 8192; i++) {
		p = contigmalloc(65536, M_V26, M_NOWAIT, 0ul, save - 1,
				 PAGE_SIZE, 0ul);
		if (p == NULL)
			break;
		m = PHYS_TO_VM_PAGE(pmap_kextract((vm_offset_t)p));
		vm_low_phys_reserved = 0;
		vm_page_free_contig(m, 65536);
		vm_low_phys_reserved = save;
		leaks++;
	}
	kprintf("V26[4] exhaustion loop: %d x 64KB blocks leaked from the DMA "
	    "alist before contigmalloc(below watermark) failed (%dKB lost)\n",
	    leaks, leaks * 64);
	return (0);
}

/* DF-2814: objcache_destroy vs remote-cpu in-flight get/put */
static struct objcache_malloc_args v26_oc_args = { 64, NULL };
static struct objcache *v26_oc;
static volatile int v26_hammer_run;

static void
v26_hammer_td(void *arg __unused)
{
	while (v26_hammer_run) {
		void *o = objcache_get(v26_oc, M_WAITOK);
		if (o)
			objcache_put(v26_oc, o);
	}
	lwkt_exit();
}

static int
t5_objcache(int rounds)
{
	struct thread *td;
	char *poison;
	int r, i;

	v26_oc_args.mtype = M_V26;

	/* control: quiesced destroy is safe */
	v26_oc = objcache_create("v26oc", 0, 0, NULL, NULL, NULL,
				 objcache_malloc_alloc, objcache_malloc_free,
				 &v26_oc_args);
	if (v26_oc) {
		void *o = objcache_get(v26_oc, M_WAITOK);
		if (o)
			objcache_put(v26_oc, o);
		objcache_destroy(v26_oc);
		kprintf("V26[5] CONTROL: quiesced create/get/put/destroy OK\n");
	}

	for (r = 0; r < rounds; r++) {
		v26_oc = objcache_create("v26oc", 0, 0, NULL, NULL, NULL,
					 objcache_malloc_alloc,
					 objcache_malloc_free, &v26_oc_args);
		if (v26_oc == NULL) {
			kprintf("V26[5] create failed\n");
			return (ENOMEM);
		}
		v26_hammer_run = 1;
		lwkt_create(v26_hammer_td, NULL, &td, NULL, TDF_NOSTART, 1,
			    "v26hammer");
		lwkt_schedule(td);
		tsleep(&v26_hammer_run, 0, "v26w1", hz);
		kprintf("V26[5] round %d: destroying cache while CPU1 hammers "
		    "get/put...\n", r);
		objcache_destroy(v26_oc);
		kprintf("V26[5] round %d: destroy returned; poisoning freed "
		    "memory with 0x41\n", r);
		poison = kmalloc(65536, M_V26, M_WAITOK);
		memset(poison, 0x41, 65536);
		kfree(poison, M_V26);
		tsleep(&v26_hammer_run, 0, "v26w2", hz);
		v26_hammer_run = 0;
		tsleep(&v26_hammer_run, 0, "v26w3", hz / 2);
		for (i = 0; i < 64; i++) {
			/* brief quiesce between rounds */
		}
	}
	kprintf("V26[5] SURVIVED %d race rounds (no UAF fault observed)\n",
		rounds);
	return (0);
}

/* DF-2789 case 2: phantom free of uninitialized leaf slot (in-bounds) */
static int
t7_blist_phantom(void)
{
	blist_t bl;
	swblk_t a1, a2, b1, b2;
	char *g;
	size_t sz;

	bl = blist_create(2081);
	sz = (size_t)bl->bl_rootblks * sizeof(blmeta_t);
	blist_destroy(bl);
	g = kmalloc(sz, M_V26, M_WAITOK);
	bzero(g, sz);
	kfree(g, M_V26);

	bl = blist_create(2081);
	kprintf("V26[7] blist(2081): rootblks=%ld\n", (long)bl->bl_rootblks);
	blist_free(bl, 0, 2081);	/* standard init */
	{
		swblk_t r, tot = 0;
		while ((r = blist_alloc(bl, 64)) != SWAPBLK_NONE)
			tot += 64;
		while ((r = blist_alloc(bl, 1)) != SWAPBLK_NONE)
			tot++;
		a1 = a2 = tot;
	}
	kprintf("V26[7] drained blist via alloc(64)/alloc(1): %ld blocks "
	    "(all real blocks allocated)\n", (long)a1);
	blist_free(bl, 2112, 64);	/* terminator leaf of child 2 */
	kprintf("V26[7] free(2112,64) into terminator slot: NO panic\n");
	b1 = blist_alloc(bl, 64);
	kprintf("V26[7] alloc(64) #1 -> %ld %s\n", (long)b1,
	    (b1 != SWAPBLK_NONE && b1 >= 2081) ?
	    "*** OUT-OF-RANGE BLOCK (bl_blocks=2081) ***" : "(in-range)");
	blist_free(bl, 2112, 64);	/* phantom free again */
	b2 = blist_alloc(bl, 64);
	kprintf("V26[7] alloc(64) #2 -> %ld %s\n", (long)b2,
	    (b1 != SWAPBLK_NONE && b2 == b1) ?
	    "*** SAME BLOCK GRANTED TWICE (overlapping allocation) ***" : "");
	blist_destroy(bl);
	return (0);
}

/* DF-2789 case 3: free into unallocated child slot -> OOB walk (groomed) */
static int
t8_blist_oob(void)
{
	blist_t probe, bl;
	size_t sz;
	char *groom;

	/* learn allocation size class, then groom freed memory with 0x41 */
	probe = blist_create(2081);
	sz = (size_t)probe->bl_rootblks * sizeof(blmeta_t);
	blist_destroy(probe);
	groom = kmalloc(sz, M_V26, M_WAITOK);
	memset(groom, 0x41, sz);
	kfree(groom, M_V26);
	kprintf("V26[8] groomed %zu bytes with 0x41, creating blist(2081)\n",
	    sz);

	bl = blist_create(2081);
	kprintf("V26[8] blist(2081) rootblks=%ld root=%p\n",
	    (long)bl->bl_rootblks, bl->bl_root);
	blist_free(bl, 0, 2081);	/* standard init */
	kprintf("V26[8] init done. root[100] is %zu bytes past the %zu-byte "
	    "bl_root allocation\n",
	    (size_t)(100 * sizeof(blmeta_t)),
	    (size_t)(bl->bl_rootblks * sizeof(blmeta_t)));
	kprintf("V26[8] BEFORE: root[100].avail=%#lx bighint=%#lx "
	    "root[101].avail=%#lx\n",
	    (unsigned long)bl->bl_root[100].u.bmu_avail,
	    (unsigned long)bl->bl_root[100].bm_bighint,
	    (unsigned long)bl->bl_root[101].u.bmu_avail);
	kprintf("V26[8] calling blist_free(6144, 2048): root child index 100 "
	    "is PAST bl_rootblks=%ld - OOB walk...\n", (long)bl->bl_rootblks);
	blist_free(bl, 6144, 2048);
	kprintf("V26[8] AFTER: root[100].avail=%#lx bighint=%#lx "
	    "root[101].avail=%#lx\n",
	    (unsigned long)bl->bl_root[100].u.bmu_avail,
	    (unsigned long)bl->bl_root[100].bm_bighint,
	    (unsigned long)bl->bl_root[101].u.bmu_avail);
	kprintf("V26[8] SURVIVED free(6144,2048) (OOB writes land in slab "
	    "slack; values above prove the walk wrote past bl_root)\n");
	blist_destroy(bl);
	return (0);
}

/* DF-2722: kstrndup reads str[maxlen] past a guard page */
static int
t9_kstrndup(void)
{
	vm_offset_t kva;
	char *s, *d;

	kva = kmem_alloc(kernel_map, 2 * PAGE_SIZE, VM_SUBSYS_KMALLOC);
	if (kva == 0) {
		kprintf("V26[9] kmem_alloc failed\n");
		return (ENOMEM);
	}
	kmem_free(kernel_map, kva + PAGE_SIZE, PAGE_SIZE);
	s = (char *)kva + PAGE_SIZE - 16;
	memset(s, 'A', 16);
	kprintf("V26[9] str=[16 x A] at %p, str[16] lands on UNMAPPED page "
	    "%p; calling kstrndup(str, maxlen=16)\n", s, (void *)(kva + PAGE_SIZE));
	d = kstrndup(s, 16, M_V26);
	kprintf("V26[9] SURVIVED kstrndup (d=%p) - byte past maxlen was "
	    "mapped or fault swallowed?! \n", d);
	/* not reached if the OOB read faults as expected */
	if (d)
		kfree(d, M_V26);
	kmem_free(kernel_map, kva, PAGE_SIZE);
	return (0);
}

/* ------------------------------------------------------------------ */
static int
v26_sysctl(SYSCTL_HANDLER_ARGS)
{
	int cmd, error;

	if (req->newptr == NULL) {
		cmd = 0;
		return (SYSCTL_OUT(req, &cmd, sizeof(cmd)));
	}
	error = SYSCTL_IN(req, &cmd, sizeof(cmd));
	if (error)
		return (error);
	kprintf("V26: cmd=%d invoked by pid %d (%s)\n", cmd,
		curproc->p_pid, curthread->td_comm);

	switch (cmd) {
	case 1: error = t1_rman(); break;
	case 2: error = t2_socket_wait(); break;
	case 3: error = t3_blist_small(); break;
	case 4: error = t4_vm_page(); break;
	case 5: error = t5_objcache(5); break;
	case 7: error = t7_blist_phantom(); break;
	case 8: error = t8_blist_oob(); break;
	case 9: error = t9_kstrndup(); break;
	default:
		kprintf("V26: unknown cmd %d\n", cmd);
		error = EINVAL;
	}
	return (error ? 0 : 0);	/* report success; results are in dmesg */
}

SYSCTL_NODE(_debug, OID_AUTO, v26harness, CTLFLAG_RW, 0,
    "v26 verification harness");
SYSCTL_PROC(_debug_v26harness, OID_AUTO, cmd,
    CTLTYPE_INT | CTLFLAG_RW, 0, 0, v26_sysctl, "I",
    "write test number 1-9 to run");

static int
v26harness_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("V26 harness loaded (cmds 1,2,3,4,5,7 safe; "
		    "8,9 PANIC by design - run last)\n");
		return (0);
	case MOD_UNLOAD:
		kprintf("V26 harness unloaded\n");
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t v26harness_mod = { "v26harness", v26harness_modevent, 0 };
DECLARE_MODULE(v26harness, v26harness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
