v1 netgraph ng_eiface (and ng_iface): node constructors call if_attach()/ether_ifattach() from netisr control path - deterministic panic (self-deadlock on non-INVARIANTS)
Summary
Every ng_eiface node creation panics the stock kernel: 'panic: try holding ifnet lock in netisr' from ifnet_lock <- if_attach <- ether_ifattach_bpf <- ng_eiface_constructor+0xf6 (first-hand capture, DWARF-confirmed). Netgraph control messages execute on a TD_TYPE_NETISR thread, but if_attach() takes the ifnet lock whose helper asserts td_type != TD_TYPE_NETISR and calls netmsg_service_sync() which cannot run from netisr - production kernels get a hang/self-deadlock instead. ng_iface carries the same latent calls (if_attach at :605; if_detach at :801 in rmnode - the shutdown path could never have worked either), demonstrated once DF-3485 is fixed. This corrects the DF-3482 lead: the eiface panic is NOT 'free NULL pointer'. Root-only; these stock modules are crash-on-sight and cannot ever have worked on kernels carrying the ifnet-lock netisr assertion. Fix validated (both modules; ifnet attach/detach moved into short-lived kernel threads with a spinlock ownership-transfer state machine): both mkpeer triggers succeed, clean shutdown, 22+ create/destroy cycles, zero panics.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3486 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.sh | β | 494 B | view raw | |
| run.sh | β | 174 B | view raw | |
| build.sh | β | 223 B | view raw | |
| panic.txt | β | 1.9 KB | view raw | |
| panic.iface_path.txt | β | 1.2 KB | view raw | |
| run.log | β | 1.2 KB | view raw | |
| fix_build.log | β | 1.7 KB | view raw | |
| fix_run.log | β | 2.2 KB | view raw | |
| unpriv_check.log | β | 506 B | view raw | |
| env.txt | β | 620 B | view raw | |
| fix.diff | β | 8.6 KB | view raw | |
| VERDICT.md | β | 3.7 KB | β raw | |
| verdict.json | β | 3.9 KB | view raw |
DF-3486 VERDICT β REPRODUCED (panic, deterministic, root-only)
Bottom line
Every creation of an ng_eiface node panics the stock kernel:
panic: try holding ifnet lock in netisr from ifnet_lock() β
if_attach() β ether_ifattach_bpf() β ng_eiface_constructor+0xf6.
The constructors of the v1 netgraph interface nodes run in the netgraph
control-message path, which executes on a TD_TYPE_NETISR thread; if_attach()
is structurally forbidden there (ifnet lock KASSERT at sys/net/if.c:3787,
plus netmsg_service_sync() at if.c:741 which would self-deadlock).
ng_iface carries the same latent defect (ng_iface.c:605 if_attach,
ng_iface.c:801 if_detach in rmnode) β hidden on the stock kernel only
because DF-3485 panics earlier; demonstrated directly in
panic.iface_path.txt.
How it was proven
- Live reproduction, stock modules, fresh boot:
ngctl mkpeer . eiface e0 etherβ instant panic; full trace captured first-hand (panic.txt). Deterministic on every attempt. - The DF-3482 lead's recorded message for this panic ("trying to free NULL
pointer") was shown to be a truncation artifact: the actual console
message and the two
ifnet_lockframes were captured intact here. - Offsets resolved via module DWARF:
+0xf6= return address of thecall ether_ifattachat+0xf1(ng_eiface.c:356). - ng_iface variant reproduced with a DF-3485-only-fixed module (panic.
iface_path.txt) β proving both files are affected and that
ng_iface_rmnode's if_detach is a further instance. - Fix validated (module rebuilds only): with fix.diff, both mkpeer
triggers SUCCEED,
ng0/ngeth0appear in ifconfig,ngctl shutdownis clean, and 22+ rapid create/destroy cycles (exercising PENDING/RUNNING/DONE/DYING/DETACHING transitions) produced zero panics; the guest stayed up (fix_run.log).
Reachability / severity
Root-only (ng_socket.c:172 caps_priv_check(SYSCAP_RESTRICTEDROOT); EPERM
verified for uid 1001). Impact: deterministic kernel panic β on INVARIANTS
kernels; on production kernels a netisr self-deadlock (hang) instead.
No memory corruption. Severity Low. These v1 modules are effectively
unusable crash-on-sight as shipped (they cannot have worked on any kernel
containing the ifnet_lock netisr assertion), i.e. this is long-standing
rot in the legacy v1 netgraph interface nodes, not a regression.
Fix notes / residual
- The fix moves ifnet attach/detach into short-lived kernel threads with an ownership-transfer state machine (spinlock-protected). Validated for create / list / ifconfig / connect-hook / shutdown / rapid cycling.
- Not exercised by ngctl:
ng_eiface_rmnode's DONE-state path (ngctl's node-ID addressing did not resolve for the unnamed eiface node); that path is the unchanged original down-block, now merely gated on attach_state == DONE, and its state machine is identical in structure to ng_iface's validated one. - Data through hooks racing an in-flight attach (rcvdata before if_attach completes) is a pre-existing semantic gap in these modules; the fix does not widen it (the node is hook-connectable only after the constructor returns, and ifnet fields are initialized before the thread starts).
- Upstream may prefer outright retirement of the v1 iface/eiface modules (netgraph7 provides equivalents); the fix demonstrates the defect is module-local and repairable.
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
fixedBoth fixed modules loaded on the stock kernel: ng_iface and ng_eiface mkpeer triggers that panicked instantly now SUCCEED (ng0/ngeth0 created and visible in ifconfig/ngctl), ngctl shutdown is clean (deferred if_detach thread), and 22+ rapid create/destroy cycles exercising all attach-state transitions produced zero panics with the guest staying up.
fix_run.log; fix_build.log; panic.txt vs fix_run.log (baseline panic -> success)
Confirmed kernel references
Detail
Evidence (decisive lines)
panic.txt (full first-hand stock trace); panic.iface_path.txt (ng_iface variant proving both files affected); fix_run.log (fixed lifecycle: IFACE-CREATED/EIFACE-CREATED, ifconfig ng0/ngeth0, clean shutdown, race stress 0 panics); fix_build.log; unpriv_check.log (EPERM uid 1001)
PoC changes
none needed β plain ngctl reproduces on the stock kernel; trigger is 3 lines
Verified recommended fix
Defer the ifnet side of v1 iface/eiface node lifecycle to short-lived kernel threads (spinlock ownership-transfer state machine), never calling if_attach()/if_detach() from the netisr control path β see fix.diff.
Verdict
Deterministic kernel panic on every ng_eiface node creation: 'panic: try holding ifnet lock in netisr' from ifnet_lock() <- if_attach() <- ether_ifattach_bpf() <- ng_eiface_constructor+0xf6 (ng_eiface.c:356). Netgraph control messages run on a TD_TYPE_NETISR thread; if_attach() cannot run there (ifnet lock KASSERT sys/net/if.c:3787 and netmsg_service_sync() at if.c:741 which would self-deadlock β so on non-INVARIANTS kernels this is a hang rather than a panic). ng_iface carries the same latent calls (ng_iface.c:605 if_attach, ng_iface.c:801 if_detach in ng_iface_rmnode), demonstrated directly once DF-3485 is fixed. Root-only trigger (ng_socket.c:172). This also corrects the DF-3482 lead: the eiface panic is NOT 'trying to free NULL pointer' β that was inferred from a truncated console window. fix.diff moves the ifnet attach/detach into short-lived kernel threads with a spinlock ownership-transfer state machine; validated: both triggers succeed, ng0/ngeth0 appear in ifconfig, clean shutdown, 22+ rapid create/destroy cycles, zero panics.
No comments yet.