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

v1 ng_socket: ngsock/ngpcb lifetime entirely unsynchronized across socket threads, netisr and synchronous-dispatch contexts β€” NULL-deref/UAF race (DF-3510 ng7 twins, worse: no locks at all, refs-blind kfree)

Summary

struct ngsock fields and struct ngpcb are mutated on socket protocol threads (detach_common: sockdata=NULL, unlocked --refs, kfree(pcbp); connect_data: unlocked refs++) and by ngs_rmnode which kfrees sockdata UNCONDITIONALLY (not refs-gated), while five hot paths read them bare from other threads: ngc_send:257, ngd_send:368/376 (NULL-check TOCTOU), ngs_rcvmsg:792-793 (bare node->private - reachable synchronously from ANY sender thread since v1 ng_send_msg calls CALL_MSG_HANDLER directly), ngs_rcvdata:847-859 (datasock/pcbp->ng_socket held across ssb_appendaddr while detach can kfree the pcbp - pcb UAF), ngs_disconnect:895-899. v1 node methods also run on the netisr and v1 ng_send_data dispatches synchronously in the sender's context - genuinely different CPUs; the only 'locking' is crit_enter() which does not stop other CPUs. Unpriv data-socket holder (ngd_attach has NO privilege check) racing teardown of a root-configured graph: NULL-deref panic (easy) / pcb UAF (potential corruption) / refs lost update. Not executed (narrow window, panic-only signal; the deterministic sibling DF-3516 was verified instead). Fix: ngsock mutex + counted node ref + refs-gated rmnode kfree.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3517 Β· 2 files
FileTypeDescriptionSize
README.md β€” 3.6 KB ↓ raw
verdict.json β€” 2.3 KB view raw

DF-3517 β€” v1 ng_socket: no synchronization at all on ngsock/ngpcb lifetime (DF-3510 v1 twins)

What

The v1 AF_NETGRAPH socket code (sys/netgraph/socket/ng_socket.c) shares struct ngsock (node/datasock/ctlsock/flags/refs) and struct ngpcb across three genuinely concurrent execution contexts with zero locking:

  • socket protocol threads: ngc_send/ngd_send/ngc_connect/ngc_detach/ ngd_detach (pru handlers dispatched on each socket's lwkt port);
  • the netgraph netisr thread: queued control messages / ngintr (ng_base.c:2045-2108) β†’ ngs_rcvmsg/ngs_disconnect;
  • any data-sender's thread: v1 ng_send_data dispatches the peer's rcvdata synchronously in the caller's context (ng_base.c:1678-1697), so ngs_rcvdata runs on whatever thread sent data into the socket node. Control messages are the same: ng_send_msg calls CALL_MSG_HANDLER directly (ng_base.c:1186-1233) β€” no queue, no INVALID re-check β€” so ngs_rcvmsg runs on any sender's thread.

Mutations happen on the socket threads (ng_detach_common:552-574 NULLs sockdata, does unlocked --sockdata->refs, kfrees the pcbp; ng_connect_data:669-671 does unlocked refs++) and in ngs_rmnode:919-933 which kfrees sockdata unconditionally (not refs-gated).

Bare cross-thread reads / TOCTOU windows: - ngc_send:257 pcbp->sockdata->node - ngd_send:357 (NULL check) then :368/:376 pcbp->sockdata->node use - ngs_rcvmsg:792-793 node->private β†’ sockdata->ctlsock - ngs_rcvdata:847-859 hook->node->private β†’ sockdata->datasock β†’ pcbp->ng_socket (pcbp freed concurrently by ng_detach_common:574) - ngs_disconnect:895-899 hook->node->private, numhooks

This is the v1 twin of ng7 DF-3510 (which identified the ng7 socket's five bare-read sites racing mtx-protected teardown). v1 is strictly worse: there is no mtx anywhere, ngs_rmnode's kfree isn't reference-gated, and v1's only "protection" is crit_enter() in ng_unref (ng_base.c:474-484), which does not stop another CPU. DF-0537 covered only ng7's ng_connect_data TOCTOU.

Impact

  • Window A (NULL-deref panic): ngs_rmnode NULLs sockdata/datasock/frees between a reader's check and use β†’ read of freed/NULL memory β†’ panic (same sink class as DF-3516, but requiring the race).
  • Window B (pcb UAF): ngs_rcvdata holds pcbp across ssb_appendaddr(&so->so_rcv, ...) (:873-882) while ng_detach_common kfrees the pcbp (:574) and sofrees the socket β†’ use-after-free on the socket buffer / socket.
  • Window C (refs lost-update): unlocked refs++/-- on different CPUs β†’ premature or missed free.

Unprivileged reachability: data sockets have no privilege check (ngd_attach:310-321), so an unprivileged holder of a data socket can race teardown of a root-configured graph (same precondition DF-3510 documented). The control-message paths need a root-held control socket.

Why untested

Same judgment as DF-3510: the window is a few instructions wide, teardown must land inside it, and race-hammering costs seconds per attempt with a panic as the only positive signal; the deterministic sibling DF-3516 was verified instead. Filed on the lock-usage asymmetry (all mutating paths vs all reading paths listed above).

Fix direction

Give the ngsock a lock (or route all ngsock/ngpcb access through one context): mirror the ng7 discipline β€” lock around the loads, take a counted reference on sockdata->node before deref, re-check after; make ngs_rmnode's free refs-gated. The minimal hardening is to reuse the DF-3516 NULL checks plus an ngsock mutex around sockdata/ctlsock/datasock read-modify pairs.

Fix verification

not_testable
per-fix-DF-3517

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md enumerates mutating vs reading sites with path:line; no runtime artifacts (untested by design).

PoC changes

no PoC executed; analysis-only filing

Verified recommended fix

Serialize ngsock/ngpcb access (mutex + counted node reference) and make ngs_rmnode's sockdata free reference-gated.

Verdict

Not executed: narrow race window with panic as the only positive signal, seconds per attempt; the deterministic sibling DF-3516 (same sink) was verified instead and its fix removes the worst v1 lifetime hazard. The unsynchronized-access analysis is certain from source: v1 ng_socket.c shares ngsock/ngpcb across socket protocol threads, the netgraph netisr, and synchronous dispatch contexts (ng_send_data ng_base.c:1678-1697, ng_send_msg ng_base.c:1226) with no locks at all; ngs_rmnode:919-933 kfrees sockdata unconditionally; ng_detach_common:552-574 kfrees pcbp while ngs_rcvdata:847-882 can hold it across ssb_appendaddr. Expected worst outcomes: NULL-deref panic (Window A) or pcb/socket UAF (Window B). Unprivileged side needs a root-configured graph + concurrent teardown (same as ng7 DF-3510).