ng_ppp_mp_strategy uses the file-scope global compareLatencies as kqsort comparator state and clears it to NULL on exit β the DF-0330 twin is LIVE in v1 wherever unfuneled transmit contexts exist (ng_iface output): NULL-index kernel panic
Summary
ng_ppp_mp_strategy sets compareLatencies = latency (the caller's STACK array), kqsort()s with ng_ppp_intcmp which derefs compareLatencies[index], then stores NULL - unlocked. ng7 funnels all items to cpu0 (DF-0330 'latent'); v1's ng_iface output path runs strategy in the sending process's thread (per-interface serializer only - no BGL, no netisr funnel), so two bundles driven from different CPUs (or one bundle from two senders) execute strategy concurrently: A's trailing NULL store turns B's in-flight comparator call into a NULL+index read -> panic; cross-bundle interleavings also sort against a dead stack array. Kernel panic on any MP-capable non-round-robin bundle with unequal links where two transmit contexts run concurrently - exactly the topologies PPP concentrators use; trigger is ordinary concurrent routed/local traffic. VERDICT: inconclusive (defect certain; staging BLOCKED - ng_iface/ng_eiface node creation panics deterministically first, see the side-discovery lead). Fix authored (local insertion sort over <=MAX_LINKS entries using only stack state, fix.diff in pack).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3482 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| pppstrat.c | β | 5.6 KB | view raw | |
| panic.iface_constructor.txt | β | 2.4 KB | view raw | |
| panic.eiface_constructor.txt | β | 1.1 KB | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| README.md | β | 2.1 KB | β raw | |
| verdict.json | β | 3.9 KB | view raw |
DF-3482 β v1 ng_ppp compareLatencies/kqsort race (DF-0330's LIVE v1 twin)
What this is
ng_ppp_mp_strategy (ng_ppp.c:1746-1749) uses the file-scope global
compareLatencies (ng_ppp.c:363) as qsort comparator state:
compareLatencies = latency; / caller's STACK array /
kqsort(sortByLatency, n, ..., ng_ppp_intcmp); / :1840 derefs /
compareLatencies = NULL;
In netgraph7 every item funnels to cpu0, so DF-0330 called this latent.
In v1 (the LIVE stack) the ng_iface output path runs
ng_ppp_output -> ng_ppp_mp_output -> ng_ppp_mp_strategy in the SENDING
PROCESS'S THREAD (per-interface serializer only, no BGL, no netisr funnel),
so two bundles (or one bundle driven from two CPUs) execute strategy
concurrently: thread A stores NULL on exit while thread B is inside the
comparator -> NULL[index] read -> kernel panic. Cross-bundle runs also make
B sort against A's DEAD STACK array (logic corruption / stale stack read).
Verification outcome
INCONCLUSIVE β blocked on this stock guest: the plan was two ng_iface-fed
bundles ping-flooded from different CPUs, but ng_iface (and ng_eiface) node
CREATION panics the stock kernel deterministically:
panic: trying to free NULL pointer
_kfree() ... ng_iface_constructor+0x3a2 / ng_eiface_constructor+0xf6
<- ng_mkpeer <- ng_generic_msg <- ng_send_msg
from a plain root ngctl mkpeer . iface i0 inet on a FRESH boot
(panic.iface_constructor.txt, panic.eiface_constructor.txt). The ng_ether
upper-hook alternative severs the guest's ssh path, so no unfuneled output
feeder could be staged. The defect itself is certain by inspection; the
reachability argument (v1's unfuneled output contexts) is documented in
DF-3481/VERDICT.md.
Files
- pppstrat.c β graph builder (2 bundles x 6 unequal links + ifaces)
- panic.iface_constructor.txt / panic.eiface_constructor.txt β blocker
- fix.diff β replace global+kqsort with a local insertion sort
- manifest.json / verdict.json
Build / run (guest, root)
cc -O2 -w -o pppstrat pppstrat.c kldload ng_socket ng_ppp ng_ksocket ng_iface # panics at mkpeer time ./pppstrat 2 && ping -f 10.240.0.9 & ping -f 10.241.0.9 &
Fix verification
not_testablefix.diff authored (local insertion sort); not kernel-validated because the baseline race could not be staged on this guest (iface creation panic).
findings/poc/DF-3482/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
['findings/poc/DF-3482/panic.iface_constructor.txt β deterministic blocker: iface node creation panics stock kernel', 'findings/poc/DF-3482/panic.eiface_constructor.txt β eiface variant of the same creation panic', 'findings/poc/DF-3482/pppstrat.c β intended two-bundle iface-fed strategy race graph builder', 'sys/netgraph/ppp/ng_ppp.c:1746-1749 β global set/kqsort/NULL-clear', 'sys/netgraph/ppp/ng_ppp.c:1840 β comparator deref of the global', 'sys/netgraph/iface/ng_iface.c ng_iface_output β process-thread unfuneled context']
PoC changes
Harness complete (pppstrat.c builds two 6-unequal-link bundles fed by ng_iface interfaces with routes); never executed past graph construction because mkpeer of the iface node itself panics the stock kernel.
Verified recommended fix
Replace the global+kqsort with a local insertion sort over the <=NG_PPP_MAX_LINKS entries using only stack state (see fix.diff), or pass the latency array via a qsort thunk if one is added.
Verdict
ng_ppp_mp_strategy (ng_ppp.c:1746-1749) sets the file-scope global compareLatencies to the caller's STACK array, kqsort()s with ng_ppp_intcmp dereferencing it (:1840), then stores NULL -- with no lock. netgraph7 funnels all items to cpu0, which is why DF-0330 was classified latent; v1 (the LIVE stack) has no such funnel for transmit: the ng_iface output path runs ng_ppp_output -> ng_ppp_mp_output -> ng_ppp_mp_strategy in the sending process's thread (per-interface serializer only, no BGL, no netisr), so two bundles driven from different CPUs (or one bundle driven by two senders) execute strategy concurrently and thread A's trailing 'compareLatencies = NULL' turns thread B's in-flight comparator call into a NULL+index read => kernel panic (cross-bundle interleavings additionally sort against a dead stack array). The defect is certain by inspection, but on this stock guest it could not be staged: ng_iface and ng_eiface node creation panics the kernel deterministically ('panic: trying to free NULL pointer', ng_iface_constructor+0x3a2 / ng_eiface_constructor+0xf6, from a plain root 'ngctl mkpeer . iface i0 inet' on a fresh boot -- logs in this pack), and the ng_ether upper-hook alternative severs the guest's ssh. Severity Medium (panic/DoS on MP-capable bundles with non-round-robin, unequal links); trigger in a real deployment is any concurrent routed/local traffic through two iface-fed MP bundles.
No comments yet.