β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3507

v1 twin of DF-3498: no cross-bundle exclusivity (second bundle re-enslaves a member, clobbering ac_netgraph which ng_ether(4) also owns), plus addport accepts the bundle's own/another fec ifnet -> choose_port TX recursion

Summary

addport only checks membership in THIS bundle; a second fec node can enslave the same interface and clobbers ac_net_graph - which is also the field ng_ether uses for its own hooks (shared IFP2NG): cross-enslavement or fec+ng_ether coexistence silently steals/blinds RX. addport also accepts the bundle's own ifp or another fec's ifp: transmitting on such a bundle routes via choose_port to a fec ifp whose ether_output_frame re-enters ng_fec_start -> unbounded recursion/livelock. Misconfiguration-turned-DoS and silent RX theft; root-gated. Fix: refuse if IFP2NG already non-NULL + reject fec/ether-claimed members. [VERIFIED pass2: reproduced on harness - cross-enslave silently switches tap0 to fec1 while fec0 still lists it, DEL_IFACE leaves stale pointer; freed-node arm blocked on INVARIANTS serializer assert captured]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3507 Β· 10 files
FileTypeDescriptionSize
README.md β€” 535 B ↓ raw
VERDICT.md β€” 2.5 KB ↓ raw
env.txt β€” 309 B view raw
fec-sequence.log β€” 5.7 KB view raw
fechg.c β€” 37.0 KB view raw
fix.diff β€” 980 B view raw
manifest.json β€” 931 B view raw
panic.txt β€” 13.4 KB view raw
run.log β€” 690 B view raw
verdict.json β€” 3.1 KB view raw

DF-3507 - v1 ng_fec cross-bundle enslavement + stale ac_netgraph

Same harness module as DF-3506. Sequence: create fec0+fec1; ADD_IFACE tap0 to both; dump between steps (kern.fechg.dump). Observed: tap0 ac_netgraph switches to fec1's node while fec0 still lists it; DEL_IFACE from fec1 leaves tap0 pointing at fec1's node (stale). Teardown crash-arm blocked on INVARIANTS (ng_fec_stop serializer assert).

Kernel: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

VERDICT.md
↓ download raw

DF-3507 - VERDICT

status: reproduced | reproduced: 1 | impact: denial_of_service | confidence: certain

Reproduced on the minimally-repaired harness module (constructor UB fix only - the stock module cannot even create a node, see DF-3506 pack). Sequence (fechg dump evidence): (1) tap0+tap1 added to fec0 - FECDMP shows tap0 ac_netgraph=0xfffff801179b28a0 == fec0's node. (2) A SECOND bundle fec1 was created and ADD_IFACE tap0 issued against it - the duplicate check at ng_fec.c:362-369 only scans THIS bundle's ports, so the add SUCCEEDED (rc=0) and tap0's ac_netgraph silently switched to fec1's node (0xfffff8011752ea00) while fec0's portlist still contains tap0: cross-bundle enslavement + silent RX ownership theft confirmed. (3) DEL_IFACE tap0 from fec1 - ng_fec_delport (:417-471) never clears the member's ac_netgraph: FECDMP shows tap0 still pointing at fec1's node after fec1 no longer lists it (stale ownership). The freed-node dereference arm (rmnode sets node->private=NULL at :1239 then frees; ng_fec_input:752-758 would deref the stale IFP2NG) is code-certain but could not be run: ng_fec_rmnode/stop paths additionally hit the INVARIANTS-only lwkt_serializer assert ('panic: assertion IS_SERIALIZED in lwkt_serialize_exit' from ng_fec_stop, captured in boot.log) and crash before creating the dangling state - on production (non-INVARIANTS) kernels the teardown runs and the stale pointer survives. Also note v1 ng_fec shares ac_netgraph with ng_ether(4) (if_arp.h:124), so fec+ng_ether coexistence silently steals RX exactly as filed. Root-gated misconfiguration-turned-DoS; no escalation path (fixed-offset reads of freed slab; NULL private in the benign case).

Kernel references

Evidence

fec-sequence.log (three FECDMP snapshots: after adds / after cross-enslave / after DEL_IFACE showing tap0 ac_netgraph unchanged), fechg.c

in ng_fec_addport refuse interfaces whose ac_netgraph is already non-NULL (or whose if_input is already ng_fec_input); in ng_fec_delport clear ac_netgraph when it still points at this bundle's node.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
↓ fix.diffper-fix-DF-3507

Confirmed kernel references

Detail

Evidence (decisive lines)

fec-sequence.log (three FECDMP snapshots: after adds / after cross-enslave / after DEL_IFACE showing tap0 ac_netgraph unchanged), fechg.c

PoC changes

harness module (ctor-UB fix + sysctl driver + delport member-ioctl serialize-wrap for INVARIANTS context) - cross-enslave/delport logic verbatim

Verified recommended fix

in ng_fec_addport refuse interfaces whose ac_netgraph is already non-NULL (or whose if_input is already ng_fec_input); in ng_fec_delport clear ac_netgraph when it still points at this bundle's node.

Verdict

Reproduced on the minimally-repaired harness module (constructor UB fix only - the stock module cannot even create a node, see DF-3506 pack). Sequence (fechg dump evidence): (1) tap0+tap1 added to fec0 - FECDMP shows tap0 ac_netgraph=0xfffff801179b28a0 == fec0's node. (2) A SECOND bundle fec1 was created and ADD_IFACE tap0 issued against it - the duplicate check at ng_fec.c:362-369 only scans THIS bundle's ports, so the add SUCCEEDED (rc=0) and tap0's ac_netgraph silently switched to fec1's node (0xfffff8011752ea00) while fec0's portlist still contains tap0: cross-bundle enslavement + silent RX ownership theft confirmed. (3) DEL_IFACE tap0 from fec1 - ng_fec_delport (:417-471) never clears the member's ac_netgraph: FECDMP shows tap0 still pointing at fec1's node after fec1 no longer lists it (stale ownership). The freed-node dereference arm (rmnode sets node->private=NULL at :1239 then frees; ng_fec_input:752-758 would deref the stale IFP2NG) is code-certain but could not be run: ng_fec_rmnode/stop paths additionally hit the INVARIANTS-only lwkt_serializer assert ('panic: assertion IS_SERIALIZED in lwkt_serialize_exit' from ng_fec_stop, captured in boot.log) and crash before creating the dangling state - on production (non-INVARIANTS) kernels the teardown runs and the stale pointer survives. Also note v1 ng_fec shares ac_netgraph with ng_ether(4) (if_arp.h:124), so fec+ng_ether coexistence silently steals RX exactly as filed. Root-gated misconfiguration-turned-DoS; no escalation path (fixed-offset reads of freed slab; NULL private in the benign case).