/*
 * DF-2859 PoC: sglist_split() trim bcopy moves `count` entries instead of
 * the `original->sg_nseg` surviving entries.
 *
 * sys/kern/subr_sglist.c:571-574:
 *
 *	original->sg_nseg -= count;
 *	bcopy(original->sg_segs + count, original->sg_segs, count *
 *	    sizeof(struct sglist_seg));
 *
 * The correct length is `original->sg_nseg` (the surviving remainder,
 * already decremented on the line above), not `count`.
 *
 * Demo (deterministic, no device needed):
 *   Case A  split==0, N=6 segs, split length covers exactly 1 seg
 *           (C=1, survivors=5).  bcopy moves 1 entry -> 4 stale entries.
 *           expected orig after = [P1..P5]
 *           actual   orig after = [P1,P1,P2,P3,P4]   (P1 dup, P5 lost)
 *
 *   Case B  split!=0, N=6 segs, split length covers 1.5 segs
 *           (C=2 -> C'=1, survivors=5).  bcopy moves 1 entry (the tail)
 *           -> 4 stale entries.
 *           expected orig after = [P1+0x800/0x800, P2, P3, P4, P5]
 *           actual   orig after = [tail(P1), FULL P1, P2, P3, P4]
 *
 *   Case C  split==0, maxseg=N=4, split length covers 3 segs
 *           (C=3, survivors=1).  bcopy moves 3 entries from segs+3,
 *           i.e. reads segs[3..5] -- segs[4], segs[5] are OUT OF BOUNDS
 *           of the 4-segment kmalloc allocation (heap OOB read of
 *           32 bytes; written only into unused slots).
 *
 * Case C's OOB read is by construction (cannot be "printed" safely); the
 * corrupted survivor lists of cases A and B are printed and diffed below.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sglist.h>

#define	P(i)	(0x100000 + (vm_paddr_t)(i) * 0x30000)	/* discontiguous */
#define	L	0x1000

static void
dump_sg(const char *tag, struct sglist *sg)
{
	int i;

	kprintf("DF2859 %s: nseg=%d\n", tag, sg->sg_nseg);
	for (i = 0; i < sg->sg_nseg; i++)
		kprintf("DF2859 %s   seg[%d] paddr=0x%016jx len=0x%zx\n",
		    tag, i, (uintmax_t)sg->sg_segs[i].ss_paddr,
		    sg->sg_segs[i].ss_len);
}

static void
build(struct sglist *sg, int n)
{
	int i;

	sglist_reset(sg);
	for (i = 0; i < n; i++)
		sglist_append_phys(sg, P(i), L);
}

static int
df2859_demo(void)
{
	struct sglist *orig, *head;
	int error, bad = 0;

	/* ---- Case A: split==0, survivors(5) > count(1): stale/dup segs */
	orig = sglist_alloc(6, M_INTWAIT);
	if (orig == NULL)
		return (ENOMEM);
	build(orig, 6);
	kprintf("DF2859 caseA: BEFORE split\n");
	dump_sg("A.orig.before", orig);

	head = NULL;
	error = sglist_split(orig, &head, L, M_INTWAIT);
	kprintf("DF2859 caseA: split(len=0x1000) error=%d\n", error);
	dump_sg("A.head", head);
	dump_sg("A.orig.after", orig);
	kprintf("DF2859 caseA: EXPECTED orig.after = P1..P5 (5 distinct segs)\n");
	if (orig->sg_nseg == 5 &&
	    orig->sg_segs[0].ss_paddr == P(1) &&
	    orig->sg_segs[1].ss_paddr == P(2) &&
	    orig->sg_segs[2].ss_paddr == P(3) &&
	    orig->sg_segs[3].ss_paddr == P(4) &&
	    orig->sg_segs[4].ss_paddr == P(5)) {
		kprintf("DF2859 caseA: PASS (kernel behaves correctly)\n");
	} else {
		kprintf("DF2859 caseA: BUG REPRODUCED (stale/duplicated segs)\n");
		bad = 1;
	}
	sglist_free(head);

	/* ---- Case B: split!=0, survivors(5) > count'(1): stale/dup segs */
	build(orig, 6);
	kprintf("DF2859 caseB: BEFORE split\n");
	dump_sg("B.orig.before", orig);

	head = NULL;
	error = sglist_split(orig, &head, L + L / 2, M_INTWAIT);
	kprintf("DF2859 caseB: split(len=0x1800) error=%d\n", error);
	dump_sg("B.head", head);
	dump_sg("B.orig.after", orig);
	kprintf("DF2859 caseB: EXPECTED orig.after = [0x%016jx/0x800, P2, P3, P4, P5]\n",
	    (uintmax_t)(P(1) + L / 2));
	if (orig->sg_nseg == 5 &&
	    orig->sg_segs[0].ss_paddr == P(1) + L / 2 &&
	    orig->sg_segs[0].ss_len == L / 2 &&
	    orig->sg_segs[1].ss_paddr == P(2) &&
	    orig->sg_segs[2].ss_paddr == P(3) &&
	    orig->sg_segs[3].ss_paddr == P(4) &&
	    orig->sg_segs[4].ss_paddr == P(5)) {
		kprintf("DF2859 caseB: PASS (kernel behaves correctly)\n");
	} else {
		kprintf("DF2859 caseB: BUG REPRODUCED (stale/duplicated segs)\n");
		bad = 1;
	}
	sglist_free(head);
	sglist_free(orig);

	/* ---- Case C: split==0, survivors(1) < count(3): heap OOB READ
	 *            bcopy(segs+3, segs, 3*16) reads segs[4], segs[5]
	 *            past the 4-seg allocation.  Survivor seg[0] happens
	 *            to get the right value; the OOB read is by
	 *            construction (see comment up top). */
	orig = sglist_alloc(4, M_INTWAIT);
	if (orig == NULL)
		return (ENOMEM);
	build(orig, 4);
	head = NULL;
	error = sglist_split(orig, &head, 3 * L, M_INTWAIT);
	kprintf("DF2859 caseC: split(len=0x3000) error=%d survivors=%d "
	    "(bcopy read segs[3..5], segs[4..5] OOB of 4-seg alloc)\n",
	    error, orig->sg_nseg);
	dump_sg("C.orig.after", orig);
	sglist_free(head);
	sglist_free(orig);

	kprintf("DF2859: verdict: %s\n", bad ? "BUG REPRODUCED" : "PASS");
	return (0);
}

static int
sgsplit_demo_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		return (df2859_demo());
	case MOD_UNLOAD:
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

static moduledata_t sgsplit_demo_mod = {
	"sgsplit_demo",
	sgsplit_demo_modevent,
	NULL
};

DECLARE_MODULE(sgsplit_demo, sgsplit_demo_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
