/*
 * h41.c - combined KLD verification harness for the DF-2767..DF-2978
 * Info-tier hardening batch (batch 2).  Sysctl-driven, dmesg-logged.
 *
 *   sysctl -w debug.h41.cmd=N     -> runs test N
 *
 * Tests (non-destructive): see h41_run().  Test 15 PANICS by design
 * (lwkt_token_swap with <2 tokens trips the INVARIANTS KKASSERT) and
 * must be run last.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/kthread.h>
#include <sys/ucred.h>
#include <sys/blist.h>
#include <sys/udev.h>
#include <sys/rman.h>
#include <sys/sglist.h>
#include <sys/xio.h>
#include <sys/kobj.h>
#include <sys/mutex.h>
#include <sys/mutex2.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/spinlock.h>
#include <sys/systimer.h>
struct intrframe;
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/udev.h>
#include <sys/mutex.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.h>

MALLOC_DEFINE(M_H41, "h41", "h41 harness");

#define P(fmt, ...)	kprintf("[H41] " fmt, ##__VA_ARGS__)

static int h41_cmd;
static int sysctl_h41_cmd(SYSCTL_HANDLER_ARGS);

SYSCTL_NODE(_debug, OID_AUTO, h41, 0, NULL, "h41 cmds");
SYSCTL_PROC(_debug_h41, OID_AUTO, cmd, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, sysctl_h41_cmd, "I", "h41 command");

static void h41_run(int cmd);

static int
sysctl_h41_cmd(SYSCTL_HANDLER_ARGS)
{
	int cmd, error;

	cmd = h41_cmd;
	error = sysctl_handle_int(oidp, &cmd, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	h41_cmd = cmd;
	h41_run(cmd);
	return (0);
}

/* ------------------------------------------------------------------ */
/* DF-2790: blist count==0 domain violations                           */
/* ------------------------------------------------------------------ */
static void
t1_blist_c0_allfree(void)
{
	blist_t bl = blist_create(64);
	swblk_t r0, r1;

	blist_free(bl, 0, 64);		/* all 64 blocks free */
	r0 = blist_alloc(bl, 0);	/* count==0: UB shift */
	r1 = blist_alloc(bl, 1);	/* should get a block */
	P("T1 blist(64) all-free: alloc(0)=%lld alloc(1)=%lld (want alloc(1)>=0; "
	  "capacity leak shows as -1)\n", (long long)r0, (long long)r1);
	blist_destroy(bl);
}

static void
t2_blist_c0_partial(void)
{
	blist_t bl = blist_create(64);
	swblk_t r0, r1;

	blist_free(bl, 0, 32);		/* half free */
	r0 = blist_alloc(bl, 0);	/* count==0 on partial leaf */
	r1 = blist_alloc(bl, 1);	/* 32 free bits remain */
	P("T2 blist(64) half-free: alloc(0)=%lld alloc(1)=%lld (want >=0; "
	  "bighint poison shows as -1)\n", (long long)r0, (long long)r1);
	blist_destroy(bl);
}

/* ------------------------------------------------------------------ */
/* DF-2968: kthread td_ucred reference leaked per kthread exit         */
/* ------------------------------------------------------------------ */
static volatile int t3_done;
static volatile long t3_refs0;

static void
t3_thread(void *arg)
{
	atomic_add_int(&t3_done, 1);
	/* no kthread_exit needed: return from entry is same as kthread_exit */
	kthread_exit();
}

static void
t3_kthread_ucred(void)
{
	long before, after;
	int i;

	before = proc0.p_ucred->cr_ref;
	t3_refs0 = before;
	t3_done = 0;
	for (i = 0; i < 8; ++i)
		kthread_create(t3_thread, NULL, NULL, "h41t3");
	while (t3_done < 8)
		tsleep(&t3_done, 0, "h41w", 1);
	tsleep(&t3_done, 0, "h41w2", 10);	/* let exits park */
	after = proc0.p_ucred->cr_ref;
	P("T3 proc0.p_ucred->cr_ref before=%ld after=%ld delta=%ld "
	  "(leak shows as +8)\n", before, after, after - before);
}

/* ------------------------------------------------------------------ */
/* DF-2914: dev_is_good() never rejects a destroyed device             */
/* DF-2806: udev_event_attach stale ENOMEM on existing dict            */
/* ------------------------------------------------------------------ */
static d_default_t h41_default;
static struct dev_ops h41_ops = {
	{ "h41", 0, D_MPSAFE },
	.d_default = h41_default,
};

static int
h41_default(struct dev_generic_args *ap)
{
	return (0);
}

static void
t4_t5_dev_is_good_attach(void)
{
	cdev_t dev;
	int good_before, good_after, rv;

	dev = make_dev(&h41_ops, 0, UID_ROOT, GID_WHEEL, 0600, "h41");
	/* let devfs worker create the udev dict for the new cdev */
	tsleep(&h41_cmd, 0, "h41w5", 200);
	good_before = dev_is_good(dev);

	/* DF-2806: dict already exists from make_dev -> re-attach path */
	rv = udev_event_attach(dev, NULL, 0);
	P("T5 udev_event_attach(re-attach, dict exists) returned %d "
	  "(0=success; stale-ENOMEM bug shows as 12)\n", rv);

	reference_dev(dev);
	destroy_dev(dev);
	/* devfs destroy runs in a worker: wait for it */
	tsleep(&h41_cmd, 0, "h41w6", 200);
	good_after = dev_is_good(dev);
	P("T4 dev_is_good: before-destroy=%d after-destroy=%d "
	  "(fence works shows after=0; dead sentinel shows 1)\n",
	  good_before, good_after);
	release_dev(dev);
}

/* ------------------------------------------------------------------ */
/* DF-2856: rman whole-chunk reuse inherits previous owner fields      */
/* ------------------------------------------------------------------ */
static struct rman t6_rman;

static void
t6_rman_stale(void)
{
	struct resource *r1, *r2;
	device_t dummy1 = (device_t)0x1001, dummy2 = (device_t)0x1002;
	void *v;

	bzero(&t6_rman, sizeof(t6_rman));
	t6_rman.rm_type = RMAN_ARRAY;
	t6_rman.rm_start = 0;
	t6_rman.rm_end = 63;
	if (rman_init(&t6_rman, -1)) {
		P("T6 rman_init failed\n");
		return;
	}
	if (rman_manage_region(&t6_rman, 0, 63)) {
		P("T6 rman_manage_region failed\n");
		rman_fini(&t6_rman);
		return;
	}
	r1 = rman_reserve_resource(&t6_rman, 0, 63, 64, 0, dummy1);
	if (r1 == NULL) {
		P("T6 reserve1 failed\n");
		return;
	}
	rman_set_virtual(r1, (void *)0xdeadbeef);
	rman_release_resource(r1);

	r2 = rman_reserve_resource(&t6_rman, 0, 63, 64, 0, dummy2);
	if (r2 == NULL) {
		P("T6 reserve2 failed\n");
		return;
	}
	v = rman_get_virtual(r2);
	P("T6 whole-chunk reuse: rman_get_virtual(res2)=%p "
	  "(hygiene shows NULL; stale leak shows 0xdeadbeef)\n", v);
	rman_release_resource(r2);
}

/* ------------------------------------------------------------------ */
/* DF-2860/2861: sglist_consume_uio on unmapped user buffer            */
/* ------------------------------------------------------------------ */
static void
t7_sglist_user(void)
{
	struct sglist *sg;
	struct uio uio;
	struct iovec iov;
	int rv;

	iov.iov_base = (void *)0x3f000000000ULL;	/* user range, unmapped */
	iov.iov_len = 16;
	bzero(&uio, sizeof(uio));
	uio.uio_iov = &iov;
	uio.uio_iovcnt = 1;
	uio.uio_resid = 16;
	uio.uio_segflg = UIO_USERSPACE;
	uio.uio_td = curthread;

	sg = sglist_alloc(4, M_WAITOK);
	rv = sglist_consume_uio(sg, &uio, 16);
	P("T7 sglist_consume_uio(unmapped user buf) ret=%d nseg=%d "
	  "seg0.paddr=0x%jx seg0.len=%zu resid=%lu "
	  "(EFAULT-honest shows ret=14/EFAULT nseg=0; "
	  "page0-DMA bug shows ret=0 paddr=0 len=16)\n",
	  rv, sg->sg_nseg,
	  sg->sg_nseg ? (uintmax_t)sg->sg_segs[0].ss_paddr : 0,
	  (size_t)(sg->sg_nseg ? sg->sg_segs[0].ss_len : 0),
	  (unsigned long)uio.uio_resid);
	sglist_free(sg);
}

/* ------------------------------------------------------------------ */
/* DF-2887: uiomove walks past the declared iov array on zero-len iov  */
/* ------------------------------------------------------------------ */
static void
t8_uiomove_walk(void)
{
	static char src[8] = "ABCDEFGH";
	static char dst[8];
	struct uio uio;
	struct iovec iovs[2];
	int rv;

	dst[0] = 0;
	bzero(iovs, sizeof(iovs));
	iovs[1].iov_base = dst;		/* lies past the declared array */
	iovs[1].iov_len = 8;

	bzero(&uio, sizeof(uio));
	uio.uio_iov = iovs;
	uio.uio_iovcnt = 1;		/* declare ONE iov */
	uio.uio_resid = 8;
	uio.uio_segflg = UIO_SYSSPACE;
	uio.uio_td = curthread;

	rv = uiomove(src, 8, &uio);
	P("T8 uiomove 8B with 1 zero-len iov: ret=%d iovcnt-after=%d "
	  "dst[0]=%c (guard shows ret=14/EFAULT iovcnt>=0; "
	  "unguarded walk shows ret=0 iovcnt=-1 dst=A)\n",
	  rv, uio.uio_iovcnt, dst[0] ? dst[0] : '-');
}

/* ------------------------------------------------------------------ */
/* DF-2930: SYSTF_100KHZSYNC branch dead                               */
/* ------------------------------------------------------------------ */
static void
t9_systimer_cb(struct systimer *st, int inintr, struct intrframe *frame) { }

static void
t9_systimer_100khz(void)
{
	struct systimer st;
	sysclock_t plain, ganged, got;

	plain = sys_cputimer->fromhz(10000);
	ganged = sys_cputimer->fromhz(100000) * (100000 / 10000);

	bzero(&st, sizeof(st));
	systimer_init_periodic_nq100khz(&st, t9_systimer_cb, NULL, 10000);
	got = st.periodic;
	systimer_del(&st);

	P("T9 nq100khz(10kHz): periodic=%ju plain-fromhz=%ju ganged=%ju "
	  "(dead-branch bug shows got==plain; fixed shows got==ganged)\n",
	  (uintmax_t)got, (uintmax_t)plain, (uintmax_t)ganged);
}

/* ------------------------------------------------------------------ */
/* DF-2957: fattime2timespec out-of-range month/hour/minute            */
/* ------------------------------------------------------------------ */
static void
t10_fattime(void)
{
	struct timespec ts;
	unsigned dd, dt, dh;

	/* month nibble 0 (dd bits 9-5), day-of-month 15, year 127 */
	dd = (127u << 9) | (0u << 5) | 15u;	/* month 0 */
	dt = (23u << 11) | (59u << 5) | (29u >> 1);
	dh = 50;
	fattime2timespec(dd, dt, dh, 1, &ts);
	P("T10 fattime month=0 dd=0x%x -> tv_sec=%jd (garbage decode shows "
	  "2104-01-15; clamp shows sane)\n", dd, (intmax_t)ts.tv_sec);

	/* month nibble 13 (out of range) */
	dd = (30u << 9) | (13u << 5) | 15u;	/* month 13, year 30 */
	fattime2timespec(dd, dt, dh, 1, &ts);
	P("T10 fattime month=13 dd=0x%x -> tv_sec=%jd\n",
	  dd, (intmax_t)ts.tv_sec);

	/* hour 31 (0xf800 field max), minute 63 */
	dt = (31u << 11) | (63u << 5) | 0u;
	dd = (0u << 9) | (1u << 5) | 1u;	/* 1980-01-01 */
	fattime2timespec(dd, dt, dh, 1, &ts);
	P("T10 fattime hh=31 mm=63 -> tv_sec=%jd (%jd h > 24h spill)\n",
	  (intmax_t)ts.tv_sec, (intmax_t)(ts.tv_sec / 3600));
}

/* ------------------------------------------------------------------ */
/* DF-2933: xio_init_kbuf size_t->int truncation                       */
/* ------------------------------------------------------------------ */
static void
t11_xio(void)
{
	static char *kbuf;

	int rv;

	kbuf = kmalloc(2 * PAGE_SIZE, M_H41, M_WAITOK | M_ZERO);
	{
	struct xio x;
	xio_t xio = &x;
	rv = xio_init_kbuf(xio, kbuf, 0x80001001UL);	/* 2GiB + 4097 */
	P("T11 xio_init_kbuf(2GiB+4097) ret=%d xio_bytes=%d npages=%d "
	  "(truncation bug shows ret=0 bytes<0; guard shows ret=14/EINVAL)\n",
	  rv, xio->xio_bytes, xio->xio_npages);
	if (rv == 0)
		xio_release(xio);
	}
	kfree(kbuf, M_H41);
}

/* ------------------------------------------------------------------ */
/* DF-2962: kobj double-init leaks a class reference                   */
/* ------------------------------------------------------------------ */
static kobj_method_t t12_methods[] = {
	{ 0, 0 }
};
static struct kobj_class t12_class = {
	"h41cls", t12_methods, sizeof(struct kobj), NULL, 0, NULL, 0
};
struct t12_obj {
	KOBJ_FIELDS;
	int pad;
};

static void
t12_kobj(void)
{
	static struct t12_obj obj;
	u_int r1, r2, r3;

	bzero(&obj, sizeof(obj));
	kobj_init((kobj_t)&obj, &t12_class);
	r1 = t12_class.refs;
	kobj_init((kobj_t)&obj, &t12_class);	/* double init */
	r2 = t12_class.refs;
	kobj_init((kobj_t)&obj, &t12_class);	/* triple init */
	r3 = t12_class.refs;
	P("T12 kobj refs after 1/2/3 inits: %u %u %u "
	  "(leak shows monotonic growth; release-prev shows 1 1 1)\n",
	  r1, r2, r3);
	kobj_delete((kobj_t)&obj, NULL);
	P("T12 after kobj_delete refs=%u\n", t12_class.refs);
}

/* ------------------------------------------------------------------ */
/* DF-2787: mtx_notlocked_ex inverted contract                         */
/* ------------------------------------------------------------------ */
static struct mtx t13_lock;

static void
t13_mtx(void)
{
	int r_excl, r_free;

	mtx_init(&t13_lock, "h41mtx");
	mtx_lock(&t13_lock);
	r_excl = mtx_notlocked_ex(&t13_lock);	/* doc: TRUE when NOT ex-locked */
	mtx_unlock(&t13_lock);
	r_free = mtx_notlocked_ex(&t13_lock);

	P("T13 mtx_notlocked_ex: while-ex-locked=%d after-unlock=%d "
	  "(doc-correct shows 0 then 1; inverted shows 1 then 0)\n",
	  r_excl, r_free);
}

/* ------------------------------------------------------------------ */
/* DF-2818: lwkt_token_init on a live token drops the exclusive claim  */
/* ------------------------------------------------------------------ */
static struct lwkt_token t14_tok = LWKT_TOKEN_INITIALIZER(t14_tok);
static volatile int t14_state;

static void
t14_holder(void *arg)
{
	lwkt_gettoken(&t14_tok);
	atomic_swap_int(&t14_state, 1);
	while (t14_state == 1)
		tsleep(&t14_state, 0, "h14a", 1);
	lwkt_reltoken(&t14_tok);
	atomic_swap_int(&t14_state, 3);
	kthread_exit();
}

static void
t14_taker(void *arg)
{
	int got;

	while (t14_state != 1)
		tsleep(&t14_state, 0, "h14b", 1);

	/* the bug: re-init a token another cpu holds exclusively */
	lwkt_token_init(&t14_tok, "h41re");

	got = lwkt_trytoken(&t14_tok);
	P("T14 holder(cpu0) holds token; taker re-init + trytoken=%d "
	  "(mutual-exclusion-broken shows 1; correct shows 0)\n", got);
	if (got)
		lwkt_reltoken(&t14_tok);

	atomic_swap_int(&t14_state, 2);
	while (t14_state != 3)
		tsleep(&t14_state, 0, "h14c", 1);
	kthread_exit();
}

static void
t14_token_reinit(void)
{
	struct thread *td0, *td1;

	atomic_swap_int(&t14_state, 0);
	kthread_create_cpu(t14_holder, NULL, &td0, 0, "h14hold");
	kthread_create_cpu(t14_taker, NULL, &td1, 1, "h14take");
	while (t14_state != 3)
		tsleep(&t14_state, 0, "h14d", 2);
}

/* ------------------------------------------------------------------ */
/* DF-2817: lwkt_token_swap with <2 held tokens (PANICS on INVARIANTS) */
/* ------------------------------------------------------------------ */
static struct lwkt_token t15_tok = LWKT_TOKEN_INITIALIZER(t15_tok);

static void
t15_token_swap(void)
{
	P("T15 calling lwkt_token_swap() with exactly ONE token held...\n");
	lwkt_gettoken(&t15_tok);
	lwkt_token_swap();		/* ref2 = base-1 */
	P("T15 SURVIVED token_swap(1 token) - only possible on a "
	  "non-INVARIANTS kernel or with the guard fix\n");
	lwkt_reltoken(&t15_tok);
}

/* ------------------------------------------------------------------ */

static void
h41_run(int cmd)
{
	switch (cmd) {
	case 1:  t1_blist_c0_allfree(); break;
	case 2:  t2_blist_c0_partial(); break;
	case 3:  t3_kthread_ucred(); break;
	case 4: case 5: t4_t5_dev_is_good_attach(); break;
	case 6:  t6_rman_stale(); break;
	case 7:  t7_sglist_user(); break;
	case 8:  t8_uiomove_walk(); break;
	case 9:  t9_systimer_100khz(); break;
	case 10: t10_fattime(); break;
	case 11: t11_xio(); break;
	case 12: t12_kobj(); break;
	case 13: t13_mtx(); break;
	case 14: t14_token_reinit(); break;
	case 15: t15_token_swap(); break;	/* PANIC */
	default:
		P("unknown cmd %d\n", cmd);
		break;
	}
}

static int
h41_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		P("loaded (cmds 1-14 safe, 15 panics)\n");
		return 0;
	case MOD_UNLOAD:
		P("unloaded\n");
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t h41_mod = { "h41", h41_modevent, 0 };
DECLARE_MODULE(h41, h41_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
