DF-3502 / run.log
# DF-3502 run log (decisive run, 2026-09-13 23:19 UTC)
Guest: DragonFly 6.5-DEVELOPMENT #0 (INVARIANTS), vtnet0 (virtio NIC) + lo0.
Module: ng_fec.ko from HEAD source with verification enablers E1 (DF-3499 bcopy
guard), E2 (rmnode skips ng_fec_stop), E3 (delport serializes member around
if_ioctl). None of E1-E3 touch the DF-3502 defect (ac_netgraph lifetime /
ng_ether_input_p handling are stock). Driver: fecdrive.ko (thread context).
## Setup
FECDRIVE: cmd='create'
FECDRIVE: ng_make_node(fec) = 0 node=0xfffff80117ab2840 <- f0 = fec0
FECDRIVE: cmd='add vtnet0'
FECDRIVE: add 'vtnet0' = 0
-> ng_fec.c:379 ac_netgraph(vtnet0) = f0-node; ng_ether_input_p armed
## Detached sequence (vtnet0 goes down at teardown, ssh dies)
FECDRIVE: cmd='shutdown'
FECDRIVE: rmnode done
-> ng_fec_rmnode: delport frees port entry, kfree(priv), node->private
= NULL, ng_unref frees the node chunk. ng_fec.c:1233-1234 clears the
shared global ng_ether_input_p. delport NEVER clears the member's
ac_netgraph -> IFP2NG(vtnet0) = 0xfffff80117ab2840 (freed).
FECDRIVE: cmd='echo'
FECDRIVE: ng_make_node(echo) = 0 node=0xfffff80117ab2840
-> the freed f0-node chunk is immediately REUSED by an echo node
(same address, ->private == NULL).
FECDRIVE: cmd='create'
FECDRIVE: ng_make_node(fec) = 0 node=0xfffff80117ab28a0
-> second fec node re-arms ng_ether_input_p = ng_fec_input
(ng_fec.c:1133-1134).
# ifconfig vtnet0 up ; ping -c 3 10.0.2.2
## Detonation: first inbound frame on vtnet0
Fatal trap 12: page fault while in kernel mode
cpuid = 0; lapic id = 0
fault virtual address = 0x3f8
fault code = supervisor read data, page not present
instruction pointer = 0x8:0xffffffff8265e4d0
current process = Idle <- interrupt / RX context
current thread = pri 12
kernel: type 12 trap, code=0
Stopped at ng_fec_input+0x30: movq 0x3f8(%rbx),%rax
Chain: ether_input_frame(vtnet0) -> ng_ether_input_p != NULL ->
ng_fec_input(vtnet0, &m) (ng_fec.c:752) -> node = IFP2NG(vtnet0) = the freed,
now-echo node -> priv = node->private = NULL (rmnode wrote NULL at
ng_fec.c:1239; echo node has no private) -> b = &priv->fec_bundle
(offsetof(struct ng_fec_private, fec_bundle) = 0x3f8) -> TAILQ_FOREACH reads
address 0x3f8 -> page fault -> kernel panic.
## Notes
- The panic fires in RX context and needs no further privilege: once the
dangling state exists (any fec teardown that ever enslaved an ethernet
member), the NEXT fec node creation re-arms the global and the next inbound
frame on that member detonates. With no chunk reuse, node->private reads
NULL out of the freed chunk (same fault); with reuse by a node carrying a
live private pointer, ng_fec_input instead walks a foreign bundle list
(wild deref / silent blackhole).
- Secondary facet in the same code: rmnode clears the SHARED global
ng_ether_input_p unconditionally (ng_fec.c:1233-1234), so with two live fec
bundles the teardown of either blinds both (and ng_ether(4), which uses the
same global) - permanent RX blackhole with everything else "up".