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)
PoC verification
Evidence pack
findings/poc/DF-3507 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
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
- sys/netgraph/fec/ng_fec.c:362
- sys/netgraph/fec/ng_fec.c:379
- sys/netgraph/fec/ng_fec.c:417
- sys/netgraph/fec/ng_fec.c:752
- sys/netgraph/fec/ng_fec.c:1239
Evidence
fec-sequence.log (three FECDMP snapshots: after adds / after cross-enslave / after DEL_IFACE showing tap0 ac_netgraph unchanged), fechg.c
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.
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_testableConfirmed 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).
No comments yet.