ng_fec control plane violates the ifnet locking contract everywhere: constructor runs if_attach/ifnet_lock in netisr; rmnode->ng_fec_stop does an unbalanced ifnet_deserialize_all; delport calls member if_ioctl unserialized β three distinct INVARIANTS panics
Summary
All ng_fec control operations arrive as netgraph messages processed in TD_TYPE_NETISR threads, but (1) the constructor calls ether_ifattach->if_attach->ifnet_lock() which KASSERTs against netisr - every ngctl mkpeer fec on an INVARIANTS kernel panics; (2) ng_fec_rmnode calls ng_fec_stop which opens with ifnet_deserialize_all(ifp) ('XXX', :549) without the serializer held - IS_SERIALIZED assertion panic on every teardown; (3) ng_fec_delport invokes the member's if_ioctl(SIOCSIFFLAGS) without ifnet_serialize_all - vtnet_serialize_assert panic on every delport/rmnode with members. On non-INVARIANTS kernels these remain sleep-in-netisr / lock-order hazards. All three facets REPRODUCED on the guest (facet 1: 'panic: try holding ifnet lock in netisr'; facets 2/3 driven from thread context via a documented lab enabler: 'lwkt_serialize_exit' and 'vtnet_serialize_assert' panics). Deterministic INVARIANTS panics on every create/teardown. Structural fix (defer ifnet ops to thread context + serialize member ioctls) - not one-line; not_testable.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3500 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| panic_ctor_netisr.txt | β | 602 B | view raw | |
| panic_rmnode_delport.txt | β | 2.1 KB | view raw | |
| verdict.json | β | 2.9 KB | view raw | |
| manifest.json | β | 919 B | view raw |
DF-3500 β ng_fec control plane violates the ifnet locking contract everywhere: INVARIANTS panics on create/teardown via any netgraph control path
- File:
sys/netgraph/fec/ng_fec.c(v1 netgraph, LIVE module) - Lines: 1132-1140 (constructor:
ng_ether_input_p,ether_ifattach), 342/411 (ifnet_lockin addport), 430/468 (ifnet_lockin delport), 453-455 (delport: bareif_ioctlon member), 521-533 (ng_fec_initserialize dance), 549-557 (ng_fec_stopunbalancedifnet_deserialize_all), 1222 (ng_fec_stopcalled from rmnode) - Severity: Medium (deterministic INVARIANTS panics / lock-protocol violations; control plane is root-only on stock DFly)
Three reproduced facets (stock code paths)
- Constructor in netisr β every netgraph control message is processed
in a
TD_TYPE_NETISRthread; the constructor callsether_ifattachβif_attachβifnet_lock(), which KASSERTstd_type != TD_TYPE_NETISR(sys/net/if.c:3787-3788). Anyngctl mkpeer fec β¦on an INVARIANTS kernel panics (after the DF-3499 kfree(NULL) is fixed; at HEAD DF-3499 fires first). - rmnode β ng_fec_stop β
ng_fec_stop()opens withifnet_deserialize_all(ifp)(marked "XXX", ng_fec.c:549) but is called fromng_fec_rmnode(ng_fec.c:1222) without the serializer held βlwkt_serialize_exitIS_SERIALIZED assertion panic on every node teardown. - delport member ioctl β
bifp->if_ioctl(bifp, SIOCSIFFLAGS, NULL, NULL)(ng_fec.c:454-455) withoutifnet_serialize_all(bifp)β the member driver's serializer assertion fires (reproduced with vtnet:vtnet_serialize_assertβ panic). Same pattern inng_fec_init(524-532) is at least serialize-balanced, but still runs from netisr.
Run (facet 1)
# module with only the DF-3499 bcopy guard applied (see DF-3499/fix.diff) cd /usr/src/sys/netgraph/fec && make kldload ng_socket; kldload ./ng_fec.ko ngctl mkpeer fec c0 c1 -> panic: try holding ifnet lock in netisr ifnet_lock() <- if_attach() <- ether_ifattach_bpf() <- ng_fec_constructor()
Facets 2 and 3 are demonstrated with the fecdrive.ko thread-context driver
(needed because the stock csock path cannot get past facet 1 / DF-3499):
sysctl -n debug.fecdrive=shutdown after create+add vtnet0 panics in
facet 2 with the stock ng_fec_stop line present; with that one call
lab-disabled, the same command panics in facet 3 at vtnet_serialize_assert
through ng_fec_delport. See panic_ctor_netisr.txt and
panic_rmnode_delport.txt.
Notes
- On non-INVARIANTS production kernels these paths do not trip KASSERTs but
still take
ifnet_mtx(a sleep-capable mutex) and member serializers from netisr context β a lock-order / sleep-in-netisr hazard, not just debug noise. - This finding is why the DF-3501/DF-3502 verifications use fecdrive.ko (kldload/sysctl thread context) plus three documented lab enablers.
DF-3500 VERDICT β reproduced (panic; three facets)
Context
Guest: DragonFly 6.5-DEVELOPMENT #0 (INVARIANTS). The stock installed /boot/kernel/ng_fec.ko and the HEAD-rebuilt module both panic during creation for the independent DF-3499 reason first; the facet-1 panic here is exhibited with ONLY the DF-3499 bcopy guard applied (one-line enabler, does not touch any code in this finding's scope).
Facet 1 β constructor runs in netisr and takes ifnet_lock
ngctl mkpeer fec c0 c1 (message delivered via the ng csock, processed in
a TD_TYPE_NETISR thread) β ng_fec_constructor β ether_ifattach β if_attach β
ifnet_lock β KASSERT (sys/net/if.c:3787-3788):
panic: try holding ifnet lock in netisr ifnet_lock() at ifnet_lock+0x62 if_attach() at if_attach+0x4db ether_ifattach_bpf() at ether_ifattach_bpf+0x45 ng_fec_constructor() at ng_fec_constructor+0x1f9 ng_mkpeer() at ng_mkpeer+0x4c
(panic_ctor_netisr.txt; reproduced twice.)
Facet 2 β rmnode β ng_fec_stop without serializer
Driven from a normal thread (fecdrive.ko "shutdown"; module had only the DF-3499 guard β the ng_fec_stop/rmnode code was stock):
panic: assertion "IS_SERIALIZED((s))" failed in lwkt_serialize_exit (lwkt_serializer.c:138) lwkt_serialize_exit() <- ng_fec_stop() <- ng_fec_rmnode() <- ng_rmnode()
ng_fec_stop (ng_fec.c:549) does ifnet_deserialize_all(ifp) on entry but ng_fec_rmnode (ng_fec.c:1222) calls it without the interface serialized. (panic_rmnode_delport.txt, facet 2 section.)
Facet 3 β delport calls member if_ioctl unserialized
With facet 2's single call lab-disabled (documented enabler), the same teardown panics inside the member driver:
panic: assertion "IS_SERIALIZED((_arr[_i]))" failed in ifnet_serialize_array_assert (if_var.h:864)
vtnet_serialize_assert() <- vtnet_stop() <- vtnet_ioctl()
<- ng_fec_delport.part.3() <- ng_fec_rmnode()
ng_fec_delport (ng_fec.c:454-455) invokes bifp->if_ioctl(SIOCSIFFLAGS) without ifnet_serialize_all(bifp). (panic_rmnode_delport.txt, facet 3.)
Impact
On INVARIANTS kernels every create/teardown operation through the netgraph control plane panics (facet 1 on create; facets 2/3 on any delport/rmnode with members). On production kernels the same paths take ifnet_mtx and member serializers from netisr context β sleep-in-netisr / lock-order hazards. Trigger requires the netgraph control socket (root on stock DFly), so this is a hard functional break + robustness defect, not an unprivileged primitive.
Fix
Architectural: interface attach/detach/ioctl work must be deferred out of netisr (e.g., queued to a thread) and member ioctls must be wrapped in ifnet_serialize_all/deserialize_all. No single-line fix validated here (the verification used three documented enablers precisely because the correct fix is structural); fix_status: not_testable.
Attempts: 2 (facet 1), 1 (facet 2), 1 (facet 3).
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
panic_ctor_netisr.txt (facet 1: ifnet_lock <- if_attach <- ether_ifattach_bpf <- ng_fec_constructor <- ng_mkpeer); panic_rmnode_delport.txt (facets 2 and 3 verbatim serial-console panics); VERDICT.md
PoC changes
facet-1 run needs the one-line DF-3499 guard applied first (DF-3499 panics earlier otherwise); facets 2/3 driven from thread context with fecdrive.ko (sources in ../DF-3501/) because the stock csock path cannot reach them
Verified recommended fix
Defer interface attach/detach and member ioctls out of netisr context and hold each member's serializer around if_ioctl (structural fix)
Verdict
ng_fec's entire control plane violates the ifnet locking contract and runs in netisr: (1) the constructor calls ether_ifattach -> if_attach -> ifnet_lock() from a TD_TYPE_NETISR thread (KASSERT panic 'try holding ifnet lock in netisr', sys/net/if.c:3787); (2) ng_fec_rmnode calls ng_fec_stop which does an unbalanced ifnet_deserialize_all (IS_SERIALIZED assertion panic, ng_fec.c:549/1222); (3) ng_fec_delport calls the member's if_ioctl(SIOCSIFFLAGS) without the member serializer (vtnet_serialize_assert panic, ng_fec.c:454-455). All three reproduced on the INVARIANTS guest (facets 2/3 from thread context via fecdrive.ko because facet 1 blocks the stock csock path); on production kernels these remain sleep-in-netisr / lock-order hazards. Root-triggered (netgraph control socket), so impact = deterministic panic + non-INVARIANTS locking hazard, not an unprivileged primitive.
No comments yet.