v1 ng_socket: ngc_send/ng_connect_cntl deref pcbp->sockdata with no NULL check after node shutdown β deterministic kernel panic via connect() on a still-open control socket
Summary
ngc_send:257 and ng_connect_cntl:694/701 lack the sockdata==NULL check their sibling paths have (ngd_send:357, ng_bind:714, ng_setsockaddr:444). After the node is shut down - deterministically by sendto(NGM_SHUTDOWN) addressed to the socket's own node (ngs_rmnode NULLs pcbp->sockdata, kfrees the ngsock and calls soisconnected while leaving the socket open with so_pcb=pcbp) - connect() on that socket reaches ngc_connect because soconnect has no SS_ISDISCONNECTED gate. REPRODUCED on guest: Fatal trap 12, fault vaddr 0x0, 'Stopped at ngc_connect+0x52: movq (%r13),%rdi' - 4 syscalls, 100% reliable (root-held control socket; NGM_SHUTDOWN from any control socket, NOLINGER disconnect, or cross-node rmnode; realistic: a root netgraph daemon reconnecting after its graph was removed). DoS only. Fix validated (two ENOTCONN guards, module rebuild): panic gone, clean ENOTCONN, good path unaffected.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3516 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df3516.c | β | 3.4 KB | view raw | |
| build.sh | β | 138 B | view raw | |
| run.sh | β | 102 B | view raw | |
| run.log | β | 1.4 KB | view raw | |
| run.fix.log | β | 584 B | view raw | |
| panic.txt | β | 1.2 KB | view raw | |
| env.txt | β | 301 B | view raw | |
| fix.diff | β | 645 B | view raw | |
| VERDICT.md | β | 4.5 KB | β raw | |
| verdict.json | β | 3.1 KB | view raw |
DF-3516 VERDICT β REPRODUCED (panic), fix validated
Reproduced? YES β deterministic kernel panic, 4 syscalls, no race.
Guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, stock INVARIANTS kernel), netgraph.ko + ng_socket.ko from /boot/kernel. PoC run as root.
The chain, line by line
socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL=2)βngc_attach(sys/netgraph/socket/ng_socket.c:165-182). Root-gated at :172 (caps_priv_check(SYSCAP_RESTRICTEDROOT|__SYSCAP_NULLCRED)) βng_attach_cntl(:476-503): creates pcb + ngsock,pcbp->sockdata = privdata,sockdata->refs = 1.bind(s, {"shutme"})βngc_bindβng_bind(:709-723): names the node.sendto(s, NGM_SHUTDOWN, ..., "shutme:")βngc_send(:201-273):pcbp->sockdata->node(:257) is still valid here;ng_send_msg(sys/netgraph/netgraph/ng_base.c:1208-1233) resolves the path and runsng_generic_msgβcase NGM_SHUTDOWN: ng_rmnode(here)(ng_base.c:1256-1258).ng_rmnode(:425-449) setsNG_INVALIDand calls the type shutdownngs_rmnode(ng_socket.c:910-935): -dpcbp/pcbpnon-NULL βsoisdisconnected(pcbp->ng_socket),pcbp->sockdata = NULL,sockdata->refs---node->private = NULL,ng_unref(node),kfree(sockdata)β the control socket stays open withso_pcb = pcbp(nothing clears it; onlyngc_detachdoes). sendto returns 52 (bytes sent).connect(s, {"shutme:link0"})βsoconnect(sys/kern/uipc_socket.c:685-713): noSS_ISCONNECTED/SS_ISDISCONNECTEDrejection for PR_ATOMIC|PR_ADDR sockets βso_pru_connectβngc_connect(:291-303) βng_connect_cntl(:680-703):sockdata = pcbp->sockdata== NULL;ng_path_parse("shutme:link0")returns node+hook (both non-NULL, so the :690 guard passes); :694ng_findname(sockdata->node, node)evaluatessockdata->nodeβstruct ngsock.nodeis the first member (offset 0) β read of vaddr 0x0.
Observed (dfbsd-qemu/boot.log, captured in panic.txt / run.log):
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x0 fault code = supervisor read data, page not present instruction pointer = 0x8:0xffffffff8263e9d2 Stopped at ngc_connect+0x52: movq (%r13),%rdi
movq (%r13),%rdi is exactly the load of sockdata->node (r13 = NULL sockdata)
whose result feeds ng_findname's first argument. Guest died; ssh hung; vm status
"down" right after. Classification: reproduced / impact=panic.
Why not uid0?
NULL-deref read at a fixed offset 0 β a denial-of-service primitive, not a write. No escalation path.
PoC corrections vs the seed I wrote myself
- NG_CONTROL is 2 on DragonFly (guessed 1) β use
<netgraph/socket/ng_socket.h>. - Step 2 originally a second
sendtoβ blocked:soisdisconnected()makessosendfail EPIPE/SIGPIPE beforengc_sendruns (observed EXIT=141).connect()is the route:soconnecthas no disconnected-state gate. - Connect address must be
node:hook(both parts) orng_path_parsereturns hook==NULL β early EINVAL (:690) before the deref.shutme:link0works.
Fix validation β FIXED (module-level)
fix.diff adds the missing NULL checks (mirroring ngd_send:357/ng_bind:714):
- ngc_send: if (pcbp->sockdata == NULL) { error = ENOTCONN; goto release; }
- ng_connect_cntl: if (sockdata == NULL) { TRAP_ERROR; return (ENOTCONN); }
Applied in guest /usr/src (repo sys/ untouched), module rebuilt
(sys/netgraph/socket make), loaded over stock. Same PoC:
connect returned -1 errno=57 (ENOTCONN), no panic, guest alive, and the
NGM_SHUTDOWN good-path send still works (returns 52) β no regression.
Kernel refs
- sys/netgraph/socket/ng_socket.c:257 (ngc_send bare deref)
- sys/netgraph/socket/ng_socket.c:694,701 (ng_connect_cntl bare deref)
- sys/netgraph/socket/ng_socket.c:910-935 (ngs_rmnode NULLs sockdata, frees ngsock, leaves so_pcb set; soisdisconnected)
- sys/netgraph/socket/ng_socket.c:357,714,444 (the three sibling paths that DO check)
- sys/netgraph/netgraph/ng_base.c:1256-1258 (NGM_SHUTDOWN β ng_rmnode)
- sys/kern/uipc_socket.c:685-713 (soconnect β no SS_ISDISCONNECTED gate)
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
fixedConfirmed kernel references
Detail
Exploit chain
socket(AF_NETGRAPH,SOCK_DGRAM,NG_CONTROL) + bind name + sendto(NGM_SHUTDOWN to own node) [ngs_rmnode: sockdata=NULL, socket stays open] + connect(node:hook path) -> ng_connect_cntl -> NULL->node load at offset 0 -> page fault -> kernel panic
Evidence (decisive lines)
panic.txt + run.log (Fatal trap 12, vaddr 0x0, ngc_connect+0x52); run.fix.log (patched module: connect=-1 errno=57 ENOTCONN, guest alive); df3516.c (final PoC); fix.diff (validated)
PoC changes
Fixed NG_CONTROL (2, not 1) via system header; second sendto replaced with connect() after observing sosend's EPIPE/SIGPIPE gate (EXIT=141) blocks that route; connect address must be 'node:hook' (ng_path_parse rejects empty hook with EINVAL before the deref); added SIGPIPE ignore.
Verified recommended fix
Add pcbp->sockdata/sockdata NULL checks returning ENOTCONN in ngc_send and ng_connect_cntl (see fix.diff).
Verdict
Deterministic kernel NULL-deref panic reproduced on the stock guest: after NGM_SHUTDOWN shuts the socket node down (ngs_rmnode NULLs pcbp->sockdata and frees the ngsock while the control socket stays open), connect() on that socket passes soconnect (no SS_ISDISCONNECTED gate) into ng_connect_cntl which dereferences sockdata->node -> Fatal trap 12, fault vaddr 0x0, 'Stopped at ngc_connect+0x52: movq (%r13),%rdi'. Root-gated (control sockets require caps_priv_check SYSCAP_RESTRICTEDROOT), no write primitive, so impact is privileged local DoS, not privesc. Fix validated at module level: with the two NULL checks applied, the identical PoC returns ENOTCONN (errno 57) with no panic and the NGM_SHUTDOWN good path still works.
No comments yet.