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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3482 Β· 6 files
FileTypeDescriptionSize
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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-3482

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.