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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3500 Β· 6 files
FileTypeDescriptionSize
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_lock in addport), 430/468 (ifnet_lock in delport), 453-455 (delport: bare if_ioctl on member), 521-533 (ng_fec_init serialize dance), 549-557 (ng_fec_stop unbalanced ifnet_deserialize_all), 1222 (ng_fec_stop called from rmnode)
  • Severity: Medium (deterministic INVARIANTS panics / lock-protocol violations; control plane is root-only on stock DFly)

Three reproduced facets (stock code paths)

  1. Constructor in netisr β€” every netgraph control message is processed in a TD_TYPE_NETISR thread; the constructor calls ether_ifattach β†’ if_attach β†’ ifnet_lock(), which KASSERTs td_type != TD_TYPE_NETISR (sys/net/if.c:3787-3788). Any ngctl mkpeer fec … on an INVARIANTS kernel panics (after the DF-3499 kfree(NULL) is fixed; at HEAD DF-3499 fires first).
  2. rmnode β†’ ng_fec_stop β€” ng_fec_stop() opens with ifnet_deserialize_all(ifp) (marked "XXX", ng_fec.c:549) but is called from ng_fec_rmnode (ng_fec.c:1222) without the serializer held β†’ lwkt_serialize_exit IS_SERIALIZED assertion panic on every node teardown.
  3. delport member ioctl β€” bifp->if_ioctl(bifp, SIOCSIFFLAGS, NULL, NULL) (ng_fec.c:454-455) without ifnet_serialize_all(bifp) β†’ the member driver's serializer assertion fires (reproduced with vtnet: vtnet_serialize_assert β†’ panic). Same pattern in ng_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.
VERDICT.md
↓ download raw

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_testable
per-fix-DF-3500

Confirmed 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.