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)
PoC verification
Evidence pack
findings/poc/DF-3517 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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_datadispatches the peer's rcvdata synchronously in the caller's context (ng_base.c:1678-1697), songs_rcvdataruns on whatever thread sent data into the socket node. Control messages are the same:ng_send_msgcallsCALL_MSG_HANDLERdirectly (ng_base.c:1186-1233) β no queue, no INVALID re-check β songs_rcvmsgruns 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_rmnodeNULLssockdata/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_rcvdataholdspcbpacrossssb_appendaddr(&so->so_rcv, ...)(:873-882) whileng_detach_commonkfrees the pcbp (:574) andsofrees 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_testableConfirmed kernel references
- sys/netgraph/socket/ng_socket.c:257
- sys/netgraph/socket/ng_socket.c:368
- sys/netgraph/socket/ng_socket.c:792
- sys/netgraph/socket/ng_socket.c:847
- sys/netgraph/socket/ng_socket.c:895
- sys/netgraph/socket/ng_socket.c:552
- sys/netgraph/socket/ng_socket.c:919
- sys/netgraph/netgraph/ng_base.c:1678
- sys/netgraph/netgraph/ng_base.c:1226
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).
No comments yet.