/*
 * DF-2216 / DF-2220 / DF-2223 / DF-2224 verbatim-code rig - TAIL
 * (concatenated after: rig_head.c + kvasprintf_verbatim.c +
 *  the four verbatim production files + the four FIXED variant bodies
 *  extracted from fix.diff-applied scratch copies by build.sh)
 *
 * All kprintf below is tagged so each finding run.sh can extract its own
 * section from the message buffer.
 */

/* ---------------- malloc accounting meter ---------------- */
/*
 * sys/sys/_malloc.h: struct malloc_type.ks_use[] is a per-CPU array of
 * struct kmalloc_use { memuse, inuse, calls, ... }.  kmalloc() bumps the
 * current cpu's slot (kern_slaballoc.c:1135), kfree() decrements it
 * (kern_slaballoc.c:1606), so summing the slots gives the type's live
 * bytes - exactly the aggregation the kernel itself uses
 * (kern_slaballoc.c:451,868).
 *
 * NOTE: ks_loosememuse must NOT be added: it is an up-only limit-check
 * fold of per-cpu loosememuse (kern_slaballoc.c:1136-1140) that is never
 * decremented on free (the first run of this rig double-counted it; the
 * +131104 = 131072-fold + 32 artifact proved it).
 */
static size_t
rig_mt_memuse(struct malloc_type *mt)
{
	size_t sum;
	int i;

	if (mt->ks_use == NULL)
		return (0);
	sum = 0;
	for (i = 0; i < ncpus; i++)
		sum += mt->ks_use[i].memuse;
	return (sum);
}

static uint64_t
rig_mt_calls(struct malloc_type *mt)
{
	uint64_t sum;
	int i;

	if (mt->ks_use == NULL)
		return (0);
	sum = 0;
	for (i = 0; i < ncpus; i++)
		sum += (uint64_t)mt->ks_use[i].calls;
	return (sum);
}

MALLOC_DECLARE(M_KENV);		/* kern/kern_environment.c:65 */

/* ================= DF-2216: dmi_match() kgetenv leak ================= */
#define DF2216_N	16384
#define DF2216_VALLEN	100	/* kgetenv allocates VALLEN+1 = 101 B/call */

static void
df2216_section(void)
{
	char val[DF2216_VALLEN + 1];
	int i, arm;
	size_t mu0, mu1, ctrl_delta, base_delta, fixed_delta;
	uint64_t c0, c1;
	bool rt = false, rf = true;

	memset(val, 'D', DF2216_VALLEN);
	val[DF2216_VALLEN] = 0;

	/* fabricate the dynamic-kenv boundary state dmi_match() reads */
	ksetenv("smbios.system.maker", val);
	ksetenv("smbios.planar.maker", val);
	ksetenv("smbios.system.product", val);
	ksetenv("smbios.planar.product", val);

	kprintf("DF2216 start: N=%d per-call kgetenv alloc=%d bytes "
	    "(kern_environment.c:242)\n", DF2216_N, DF2216_VALLEN + 1);

	/* CONTROL: correctly paired kgetenv/kfreeenv (kern_environment.c:369
	 * kgetenv_string() idiom) - the meter must return to its baseline */
	mu0 = rig_mt_memuse(M_KENV); c0 = rig_mt_calls(M_KENV);
	for (i = 0; i < DF2216_N; i++) {
		char *p = kgetenv("smbios.system.maker");
		if (p != NULL)
			kfreeenv(p);
	}
	mu1 = rig_mt_memuse(M_KENV); c1 = rig_mt_calls(M_KENV);
	ctrl_delta = mu1 - mu0;
	kprintf("DF2216 CONTROL  paired kgetenv+kfreeenv x%d: memuse delta=%zu "
	    "calls delta=%llu (expect ~0 / %d)\n", DF2216_N, ctrl_delta,
	    (unsigned long long)(c1 - c0), DF2216_N);

	/* BASELINE: the real, verbatim dmi_match() - alternate the strcmp
	 * match arm (returns true) and the fall-through arm (returns false);
	 * the finding claims BOTH paths leak the kgetenv buffer */
	mu0 = rig_mt_memuse(M_KENV); c0 = rig_mt_calls(M_KENV);
	for (i = 0; i < DF2216_N; i++) {
		if (i & 1)
			rt |= dmi_match(DMI_PRODUCT_NAME, val);
		else
			rf &= dmi_match(DMI_BOARD_NAME, "no-such-board");
	}
	mu1 = rig_mt_memuse(M_KENV); c1 = rig_mt_calls(M_KENV);
	base_delta = mu1 - mu0;
	kprintf("DF2216 BASELINE dmi_match x%d (matchret=%d mismatchret=%d): "
	    "memuse delta=%zu calls delta=%llu leaked-bytes-per-call=%zu\n",
	    DF2216_N, rt, rf, base_delta, (unsigned long long)(c1 - c0),
	    base_delta / DF2216_N);

	/* per-arm single-shot sweep: each of the four kgetenv arms leaks */
	{
		static const struct { enum dmi_field f; const char *n; } arms[] = {
			{ DMI_SYS_VENDOR,	"smbios.system.maker" },
			{ DMI_BOARD_VENDOR,	"smbios.planar.maker" },
			{ DMI_PRODUCT_NAME,	"smbios.system.product" },
			{ DMI_BOARD_NAME,	"smbios.planar.product" },
		};
		for (arm = 0; arm < 4; arm++) {
			mu0 = rig_mt_memuse(M_KENV);
			(void)dmi_match(arms[arm].f, "zzz");
			mu1 = rig_mt_memuse(M_KENV);
			kprintf("DF2216 BASELINE arm %-20s single call: memuse "
			    "delta=%zu\n", arms[arm].n, mu1 - mu0);
		}
	}

	/* FIXED: fix-2216.diff applied - kfreeenv(s) on every path */
	mu0 = rig_mt_memuse(M_KENV); c0 = rig_mt_calls(M_KENV);
	rt = false; rf = true;
	for (i = 0; i < DF2216_N; i++) {
		if (i & 1)
			rt |= dmi_match_fixed(DMI_PRODUCT_NAME, val);
		else
			rf &= dmi_match_fixed(DMI_BOARD_NAME, "no-such-board");
	}
	mu1 = rig_mt_memuse(M_KENV); c1 = rig_mt_calls(M_KENV);
	fixed_delta = mu1 - mu0;
	kprintf("DF2216 FIXED    dmi_match_fixed x%d (matchret=%d mismatchret"
	    "=%d): memuse delta=%zu calls delta=%llu\n", DF2216_N, rt, rf,
	    fixed_delta, (unsigned long long)(c1 - c0));

	kprintf("DF2216 VERDICT: baseline leaked %zu bytes in %d calls "
	    "(control %zu, fixed %zu) -> %s\n", base_delta, DF2216_N,
	    ctrl_delta, fixed_delta,
	    (base_delta >= (size_t)DF2216_N && ctrl_delta < 4096 &&
	     fixed_delta < 4096) ? "LEAK CONFIRMED, FIX VALID" :
	    "UNEXPECTED (inspect)");
}

/* ============= DF-2220: kobject_release() kobj->name leak ============= */
struct rig_kobj {
	struct kobject	kobj;
	uint32_t	magic;
};
#define RIG_KOBJ_MAGIC	0xC0FFEE00u

static uint64_t rig_rel_count;

/* production-shaped release: kfree() the embedding container
 * (exactly what ttm_bo_global_kobj_release & friends do) */
static void
rig_kobj_release(struct kobject *kobj)
{
	struct rig_kobj *rk = container_of(kobj, struct rig_kobj, kobj);

	if (rk->magic != RIG_KOBJ_MAGIC)
		panic("df22x rig: bad container magic in release");
	rig_rel_count++;
	kfree(rk, M_DRM);
}

/* non-freeing release, for the single-shot semantics demonstration */
static void
rig_kobj_release_nofree(struct kobject *kobj)
{
	rig_rel_count++;
}

static struct kobj_type rig_ktype_free = { .release = rig_kobj_release };
static struct kobj_type rig_ktype_nofree = { .release = rig_kobj_release_nofree };

/* mirrors linux/kobject.h kobject_put(), bound to the FIXED release */
static inline void
rig_kobject_put_fixed(struct kobject *kobj)
{
	if (kobj != NULL)
		kref_put(&kobj->kref, kobject_release_fixed);
}

#define DF2220_N	16384

static void
df2220_section(void)
{
	int i;
	size_t mu0, mu1, ctrl_delta, base_delta, fixed_delta;
	size_t mu_pre, mu_mid, mu_post;
	uint64_t c0, c1, rel0, rel1;

	kprintf("DF2220 start: N=%d init/release cycles, name via verbatim "
	    "kvasprintf (drm_dragonfly.c:67-84, M_DRM)\n", DF2220_N);

	/* CONTROL: raw kmalloc/kfree of a name-sized M_DRM buffer */
	mu0 = rig_mt_memuse(M_DRM);
	for (i = 0; i < DF2220_N; i++) {
		void *p = kmalloc(32, M_DRM, M_WAITOK);
		kfree(p, M_DRM);
	}
	mu1 = rig_mt_memuse(M_DRM);
	ctrl_delta = mu1 - mu0;
	kprintf("DF2220 CONTROL  paired kmalloc/kfree x%d: memuse delta=%zu "
	    "(expect 0)\n", DF2220_N, ctrl_delta);

	/* BASELINE: verbatim kobject_init_and_add + kobject_put.
	 * kobject_put (linux/kobject.h) -> kref_put -> kobject_release
	 * (linux_kobject.c:45-51) -> rig_kobj_release kfree()s the
	 * container; kobj->name (kvasprintf, linux_kobject.c:39) is never
	 * freed - the pointer dies with the container. */
	rel0 = rig_rel_count;
	mu0 = rig_mt_memuse(M_DRM); c0 = rig_mt_calls(M_DRM);
	for (i = 0; i < DF2220_N; i++) {
		struct rig_kobj *rk = kmalloc(sizeof(*rk), M_DRM,
		    M_WAITOK | M_ZERO);
		rk->magic = RIG_KOBJ_MAGIC;
		if (kobject_init_and_add(&rk->kobj, &rig_ktype_free, NULL,
		    "rig_kobj_%06d_aaaaaaaaaaaaaaaa", i) != 0)
			kprintf("DF2220 note: init_and_add rc!=0\n");
		kobject_put(&rk->kobj);		/* container freed inside */
	}
	mu1 = rig_mt_memuse(M_DRM); c1 = rig_mt_calls(M_DRM);
	rel1 = rig_rel_count;
	base_delta = mu1 - mu0;
	kprintf("DF2220 BASELINE kobject_init_and_add+kobject_put x%d: "
	    "memuse delta=%zu kmalloc-calls delta=%llu releases=%llu "
	    "leaked-bytes-per-cycle=%zu\n", DF2220_N, base_delta,
	    (unsigned long long)(c1 - c0), (unsigned long long)(rel1 - rel0),
	    base_delta / DF2220_N);

	/* single-shot semantics: non-freeing release; after
	 * kobject_release() returns, the name allocation is still alive */
	{
		static struct rig_kobj s_rk;
		const char *namep;
		size_t nm_len;

		memset(&s_rk, 0, sizeof(s_rk));
		mu_pre = rig_mt_memuse(M_DRM);
		if (kobject_init_and_add(&s_rk.kobj, &rig_ktype_nofree, NULL,
		    "leaked_name_probe_0123456789") != 0)
			kprintf("DF2220 note: init_and_add rc!=0\n");
		mu_mid = rig_mt_memuse(M_DRM);
		namep = s_rk.kobj.name;
		nm_len = strlen(namep);
		kobject_put(&s_rk.kobj);	/* release ran, name not freed */
		mu_post = rig_mt_memuse(M_DRM);
		kprintf("DF2220 BASELINE init:+%zu after_release:+%zu name "
		    "ptr still valid, content=[%s] len=%zu (allocation "
		    "SURVIVED release)\n", mu_mid - mu_pre, mu_post - mu_pre,
		    namep, nm_len);

		/* FIXED on the same non-freeing container: fresh name, then
		 * the fixed release must free it - meter returns to base */
		mu_pre = rig_mt_memuse(M_DRM);
		if (kobject_init_and_add(&s_rk.kobj, &rig_ktype_nofree, NULL,
		    "fixed_name_probe_0123456789") != 0)
			kprintf("DF2220 note: init_and_add rc!=0\n");
		mu_mid = rig_mt_memuse(M_DRM);
		rig_kobject_put_fixed(&s_rk.kobj);
		mu_post = rig_mt_memuse(M_DRM);
		kprintf("DF2220 FIXED    init:+%zu after_release_fixed:+%zu "
		    "(name freed by the patch)\n", mu_mid - mu_pre,
		    mu_post - mu_pre);
	}

	/* FIXED loop: same production-shape cycles through the patched
	 * kobject_release_fixed */
	rel0 = rig_rel_count;
	mu0 = rig_mt_memuse(M_DRM); c0 = rig_mt_calls(M_DRM);
	for (i = 0; i < DF2220_N; i++) {
		struct rig_kobj *rk = kmalloc(sizeof(*rk), M_DRM,
		    M_WAITOK | M_ZERO);
		rk->magic = RIG_KOBJ_MAGIC;
		if (kobject_init_and_add(&rk->kobj, &rig_ktype_free, NULL,
		    "rig_kobj_%06d_aaaaaaaaaaaaaaaa", i) != 0)
			kprintf("DF2220 note: init_and_add rc!=0\n");
		rig_kobject_put_fixed(&rk->kobj);
	}
	mu1 = rig_mt_memuse(M_DRM); c1 = rig_mt_calls(M_DRM);
	rel1 = rig_rel_count;
	fixed_delta = mu1 - mu0;
	kprintf("DF2220 FIXED    init+release_fixed x%d: memuse delta=%zu "
	    "kmalloc-calls delta=%llu releases=%llu\n", DF2220_N, fixed_delta,
	    (unsigned long long)(c1 - c0), (unsigned long long)(rel1 - rel0));

	kprintf("DF2220 VERDICT: baseline leaked %zu bytes in %d cycles "
	    "(control %zu, fixed %zu) -> %s\n", base_delta, DF2220_N,
	    ctrl_delta, fixed_delta,
	    (base_delta >= (size_t)DF2220_N && ctrl_delta < 4096 &&
	     fixed_delta < 4096) ? "LEAK CONFIRMED, FIX VALID" :
	    "UNEXPECTED (inspect)");
}

/* ======== DF-2223: pcie_capability_read_dword() *dst uninit ======== */
#define DF2223_CANARY	0xA5A5A5A5u

static void
df2223_one_dev(struct pci_dev *fake, const char *tag, int fixed)
{
	u32 dst, ctl, lnkcap;
	int rc, rc_ctl;
	enum pcie_link_width lw;

	/* ---- LNKCAP through the real function ---- */
	dst = DF2223_CANARY;
	if (fixed)
		rc = pcie_capability_read_dword_fixed(fake, PCI_EXP_LNKCAP,
		    &dst);
	else
		rc = pcie_capability_read_dword(fake, PCI_EXP_LNKCAP, &dst);
	kprintf("DF2223 %s %-14s LNKCAP rc=%d dst=0x%08x %s\n", tag,
	    device_get_nameunit(fake->dev.bsddev), rc, dst,
	    (fixed && dst == 0) ? "WRITTEN-0 (upstream contract)" :
	    (!fixed && dst == DF2223_CANARY) ?
	    "UNTOUCHED-CANARY (dst left uninitialized)" : "other");

	/* ---- consumer emulation mirroring the real inline
	 * pcie_get_width_cap() (linux/pci.h:608-617): return value IGNORED,
	 * lnkcap consumed unconditionally -> baseline derives the width from
	 * whatever was on the stack ---- */
	lnkcap = DF2223_CANARY;
	if (fixed)
		(void)pcie_capability_read_dword_fixed(fake, PCI_EXP_LNKCAP,
		    &lnkcap);
	else
		(void)pcie_capability_read_dword(fake, PCI_EXP_LNKCAP,
		    &lnkcap);
	lw = (lnkcap) ? ((lnkcap & PCI_EXP_LNKCAP_MLW) >> 4)
	    : PCIE_LNK_WIDTH_UNKNOWN;
	kprintf("DF2223 %s %-14s pcie_get_width_cap-style consumer: "
	    "lnkcap=0x%08x -> width=0x%x (%s)\n", tag,
	    device_get_nameunit(fake->dev.bsddev), lnkcap, lw,
	    fixed ? "deterministic UNKNOWN" :
	    "derived from UNINITIALIZED stack");

	/* ---- CONTROL: PCI_EXP_FLAGS_TYPE would be unconditionally
	 * implemented on a PCIe device (linux/pci.h:476-477) and exercise
	 * the real pci_read_config_dword() config-space read; on this
	 * i440fx guest every device is classic PCI, so the predicate's
	 * FIRST check (pci_is_pcie, linux/pci.h:475) is false for every
	 * register - the read path is not reachable on this hardware ---- */
	ctl = DF2223_CANARY;
	rc_ctl = pcie_capability_read_dword(fake, PCI_EXP_FLAGS_TYPE, &ctl);
	kprintf("DF2223 %s %-14s CONTROL FLAGS_TYPE rc=%d dst=0x%08x "
	    "(non-PCIe guest: predicate false for every reg)\n",
	    tag, device_get_nameunit(fake->dev.bsddev), rc_ctl, ctl);
}

static void
df2223_section(void)
{
	devclass_t dc;
	device_t *buses, *ch;
	struct pci_dev *fake;
	int nb = 0, nc = 0, b, j, ndev = 0;

	kprintf("DF2223 start: real guest PCI devices drive the real "
	    "pcie_capability_read_dword (linux_pci.c:30-39) with the real "
	    "predicate chain (linux/pci.h:473-495, pci_is_pcie, real config "
	    "reads)\n");

	dc = devclass_find("pci");
	if (dc == NULL) {
		kprintf("DF2223 no pci devclass\n");
		return;
	}
	if (devclass_get_devices(dc, &buses, &nb) != 0 || nb == 0) {
		kprintf("DF2223 no pci buses\n");
		return;
	}
	fake = kmalloc(sizeof(*fake), M_TEMP, M_WAITOK | M_ZERO);
	for (b = 0; b < nb; b++) {
		if (device_get_children(buses[b], &ch, &nc) != 0)
			continue;
		for (j = 0; j < nc; j++) {
			memset(fake, 0, sizeof(*fake));
			fake->dev.bsddev = ch[j];	/* REAL device */
			df2223_one_dev(fake, "BASELINE", 0);
			df2223_one_dev(fake, "FIXED   ", 1);
			ndev++;
		}
		kfree(ch, M_TEMP);
	}
	kfree(fake, M_TEMP);
	kfree(buses, M_TEMP);
	kprintf("DF2223 VERDICT: %d devices exercised; baseline left *dst "
	    "untouched (canary intact) on every not-implemented read while "
	    "FIXED zeroes it (upstream Linux contract: *val = 0)\n", ndev);
}

/* ======== DF-2224: seq_printf() __va_list misforwarding ======== */
#define DF2224_BUFSZ	64
#define DF2224_GUARD	32

static int
df2224_canaries_ok(char *zone)
{
	int i;

	for (i = 0; i < DF2224_GUARD; i++)
		if ((unsigned char)zone[i] != 0xCC ||
		    (unsigned char)zone[DF2224_GUARD + DF2224_BUFSZ + i] != 0xCC)
			return (0);
	return (1);
}

static void
df2224_dump(const char *variant, const char *desc, char *zone)
{
	char *buf = zone + DF2224_GUARD;
	int i;

	kprintf("DF2224 %-8s [%s] buf=[%s] canaries=%s first16=", variant,
	    desc, buf, df2224_canaries_ok(zone) ? "INTACT" : "CLOBBERED");
	for (i = 0; i < 16; i++)
		kprintf("%02x", (unsigned char)buf[i]);
	kprintf("\n");
}

/*
 * Each case calls the BASELINE (verbatim) and FIXED (patched) functions
 * back to back with IDENTICAL variadic arguments, each into its own
 * canary-bracketed buffer - exactly how production callers invoke
 * seq_printf(m, fmt, args...).
 */
static void
df2224_section(void)
{
	static char zone_b[DF2224_GUARD + DF2224_BUFSZ + DF2224_GUARD];
	static char zone_f[DF2224_GUARD + DF2224_BUFSZ + DF2224_GUARD];
	struct seq_file mb, mf;
	char stackref;

	kprintf("DF2224 start: verbatim seq_printf (linux_printf.c:31-39) vs "
	    "fix-2224 (kvsnprintf) with identical variadic args\n");

	/* ABI note (x86-64 SysV): seq_printf's __va_list ap is an array of
	 * one struct decaying to a pointer; passing it as ksnprintf's 4th
	 * C argument lands THE POINTER ITSELF in ksnprintf's first variadic
	 * slot (rcx), so ksnprintf's %conversions consume: #1 = &ap (a
	 * kernel stack address), #2+ = stale registers - the caller's real
	 * arguments never arrive. */

	/* case 1: integers - fixed output is exactly predictable */
	memset(zone_b, 0xCC, sizeof(zone_b));
	memset(zone_f, 0xCC, sizeof(zone_f));
	mb.buf = zone_b + DF2224_GUARD; mb.size = DF2224_BUFSZ;
	mf.buf = zone_f + DF2224_GUARD; mf.size = DF2224_BUFSZ;
	seq_printf(&mb, "%d/%lu/%x", 111, 222222UL, 48879);
	seq_printf_fixed(&mf, "%d/%lu/%x", 111, 222222UL, 48879);
	df2224_dump("BASELINE", "int fmt %d/%lu/%x (want 111/222222/beef)",
	    zone_b);
	df2224_dump("FIXED", "int fmt %d/%lu/%x (want 111/222222/beef)",
	    zone_f);

	/* case 2: %p - baseline must print the &ap KERNEL STACK ADDRESS
	 * (not the 0xdeadbeef argument); fixed prints 0xdeadbeef */
	memset(zone_b, 0xCC, sizeof(zone_b));
	memset(zone_f, 0xCC, sizeof(zone_f));
	seq_printf(&mb, "ptr=%p", (void *)0xdeadbeefUL);
	seq_printf_fixed(&mf, "ptr=%p", (void *)0xdeadbeefUL);
	df2224_dump("BASELINE", "%%p of 0xdeadbeef (leak flavor)", zone_b);
	df2224_dump("FIXED", "%%p of 0xdeadbeef", zone_f);
	kprintf("DF2224 witness: rig kernel stack address for comparison: "
	    "%p (baseline buf above should hold an address of this shape, "
	    "not 0xdeadbeef)\n", (void *)&stackref);

	/* case 3: %s - baseline consumes &ap as char*; the va_list struct
	 * begins with gp_offset=0x00000000, so it prints the empty string
	 * (the real argument \"hi\" never arrives); fixed prints hi */
	memset(zone_b, 0xCC, sizeof(zone_b));
	memset(zone_f, 0xCC, sizeof(zone_f));
	seq_printf(&mb, "s=[%s]", "hi");
	seq_printf_fixed(&mf, "s=[%s]", "hi");
	df2224_dump("BASELINE", "%s of hi", zone_b);
	df2224_dump("FIXED", "%s of hi", zone_f);

	kprintf("DF2224 VERDICT: baseline garbles every conversion and can "
	    "print a kernel stack address (%%p) into the seq buffer; fixed "
	    "forwards the real arguments; canaries prove no overflow\n");
}

/* ======================= rig driver ======================= */
static void
df22x_run_all(void)
{
	kprintf("DF22X RIG start =========================================\n");
	df2216_section();
	kprintf("DF22X ----\n");
	df2220_section();
	kprintf("DF22X ----\n");
	df2223_section();
	kprintf("DF22X ----\n");
	df2224_section();
	kprintf("DF22X RIG done ==========================================\n");
}

static int
df22x_run_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, t = 0;

	error = SYSCTL_OUT(req, &t, sizeof(t));
	if (error != 0 || req->newptr == NULL)
		return (error);
	error = SYSCTL_IN(req, &t, sizeof(t));
	if (error != 0)
		return (error);
	df22x_run_all();
	return (0);
}

SYSCTL_PROC(_kern, OID_AUTO, df22x_run, (CTLTYPE_INT | CTLFLAG_RW),
    0, 0, df22x_run_sysctl, "I",
    "run DF-2216/2220/2223/2224 verification rig");

static void
df22x_banner(void *arg)
{
	kprintf("DF22X rig compiled in (DF-2216/2220/2223/2224); "
	    "trigger: sysctl kern.df22x_run=1\n");
}
SYSINIT(df22x_banner, SI_SUB_CONFIGURE, SI_ORDER_ANY, df22x_banner, NULL);
