⬢ DragonFlyBSD Kernel Audit
DF-3502 / run.log
← back to finding ↓ download raw
# 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".