downstream_hook.dlci never initialized to -1 (v1 twin of DF-3545): downstream traffic misrouted into the DLCI branch and reflected back out the same downstream hook; decode/demux branch is dead code
Summary
ng_xxx_constructor M_ZEROes struct XXX and initializes only the 16 DLCI channels; the newhook downstream branch stores the hook but never sets the documented -1 sentinel, so downstream_hook.dlci stays 0 and ng_xxx_rcvdataq's 'dlci != -1' branch forwards arriving frames straight back out downstream_hook.hook. VERIFIED live on the stock v1 stack (module built -Werror RC=0, does not ship; root-gated node creation): 'PING' written to sample:downstream reflected back verbatim, GET_STATUS packets_in=0/packets_out=1. Dormant template; no memory-safety impact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3550 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.9 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| v1demo.c | β | 8.2 KB | view raw | |
| build.sh | β | 133 B | view raw | |
| run.sh | β | 153 B | view raw | |
| build.log | β | 3.1 KB | view raw | |
| run.log | β | 1.8 KB | view raw | |
| env.txt | β | 314 B | view raw | |
| fix.diff | β | 522 B | view raw | |
| verdict.json | β | 3.2 KB | view raw |
DF-3550 evidence pack β v1 ng_sample downstream reflection (DF-3545 twin)
sys/netgraph/ng_sample.c (v1 netgraph template, node type "sample"):
downstream_hook.dlci is never initialized to the -1 sentinel β the
constructor M_ZEROs the whole private struct (ng_sample.c:155) and only
the 16 DLCI channels get explicit values (:158-161), while
ng_xxx_newhook()'s downstream branch (:233-237) stores the hook pointer
but never sets dlci = -1. Data arriving on sample:downstream therefore
has dlci == 0, takes the dlci != -1 "from a DLCI channel" branch in
ng_xxx_rcvdataq() (:355) and is forwarded straight back out
downstream_hook.hook (:362-364) β i.e. reflected to the sender β
while the intended decode/demux branch (:365-375) is dead code.
This is the v1 twin of DF-3545 (ng7 ng_sample), verified live here for the
first time against the stock v1 netgraph stack.
Build
See build.sh β builds ng_sample.klo from /usr/src/sys/netgraph and
compiles v1demo. Module build log: build.log (RC=0, -Werror).
Run
See run.sh β loads ng_socket.ko + ng_sample.ko, then v1demo reflect.
Expected
RECEIVED 4 bytes back: 'PING' RESULT: REFLECTED GET_STATUS: packets_in=0 packets_out=1
packets_in=0 / packets_out=1 proves the frame was misclassified as DLCI
egress traffic (dead decode branch) instead of counted as demux input.
Contents
v1demo.cv1-ABI driver (raw AF_NETGRAPH sockets; NG_VERSION 2, 52-byte ng_mesg header, libnetgraph-style data-socket association via NGM_NODEINFO +connect("[id]:"))build.logmodule build (-Werror, RC=0)run.logdecisive run (reflection + leak + hooktest sections)env.txtguest uname / compiler / module statefix.diffone-line sentinel fix
DF-3550 β VERDICT: REPRODUCED (v1 twin of DF-3545)
Impact: none (misroute/reflection in a dormant template node) Confidence: certain
What was run
v1demo reflect on the stock v1 netgraph stack (guest boots netgraph.ko
v1; ng_socket.ko + freshly built ng_sample.ko loaded as root). The demo
speaks the v1 ABI natively (NG_VERSION 2, 52-byte struct ng_mesg header,
NGM_NODEINFO + connect("[id]:") data-socket association exactly like
libnetgraph's NgMkSockNode), builds the graph
socket(sd) <-> sample(downstream) via NGM_MKPEER, writes "PING" out
sd, and polls the data socket for a reply.
Observed (run.log)
RECEIVED 4 bytes back: 'PING' RESULT: REFLECTED GET_STATUS: packets_in=0 packets_out=1
The frame written into sample:downstream came straight back out the same
hook, and NGM_XXX_GET_STATUS reports packets_out=1 / packets_in=0 β the
demux branch never ran.
Why (kernel path, line-by-line)
ng_xxx_constructor()M_ZEROs the wholestruct XXX(sys/netgraph/ng_sample.c:155) and then initializes only the 16 DLCI channels (:158-161).downstream_hook.dlcistays 0.ng_xxx_newhook()'s downstream branch (:233-237) stores the hook pointer and private pointer but never sets the-1sentinel documented at:118(int dlci; /* -1 == downstream */).- Data arriving on the downstream hook:
ng_xxx_rcvdata()readsdlci = hookinfo->dlci == 0(:333),ng_xxx_rcvdataq()takes thedlci != -1"received on a DLCI hook" branch (:355) and forwards the frame back outxxxp->downstream_hook.hook(:362-364) β the very hook it arrived on β reflection to the sender,packets_out++. - The intended decode branch (
:365-375) is unreachable dead code.
Module state: ng_sample.ko builds from /usr/src (-Werror, RC=0,
build.log) but does not ship (no SUBDIR entry in sys/netgraph/Makefile,
no sys/conf/files line, absent from /boot/kernel). Instantiating a
sample node requires root (v1 control sockets are caps_priv_check
gated, sys/netgraph/socket/ng_socket.c:172) plus an explicit kldload.
Exploit chain
None β dormant template, misroute/reflection only, no memory-safety impact.
(An administrator who wires sample:downstream to a real interface hook
would create a packet-reflection loop, but that already requires root.)
Fix validation
fix.diff adds the one-line sentinel (xxxp->downstream_hook.dlci = -1; in
the newhook downstream branch). Kernel-rebuild validation skipped per
contract for Info-severity findings (mirrors DF-3545's treatment of the
ng7 twin); the patch is a trivially auditable initialization fix.
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
not_testableOne-line sentinel fix (downstream_hook.dlci = -1 in the newhook downstream branch, fix.diff, git-apply --check clean); kernel rebuild validation skipped per contract for Info-severity findings (mirrors DF-3545).
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (RECEIVED 4 bytes back: 'PING' / RESULT: REFLECTED / GET_STATUS: packets_in=0 packets_out=1), v1demo.c (v1-ABI AF_NETGRAPH driver), build.log (module -Werror RC=0), fix.diff (one-line sentinel init)
PoC changes
Demo written from scratch for the v1 ABI: NG_VERSION 2, 52-byte ng_mesg header (u16 arglen at offset 4), NGM_GENERIC_COOKIE 851672668, data socket associated to the control socket's node via NGM_NODEINFO + connect("[id]:") exactly like libnetgraph NgMkSockNode; generic cmds (MKPEER/CONNECT) return errors synchronously from sendto(2) and send no response.
Verified recommended fix
Set xxxp->downstream_hook.dlci = -1 when accepting the downstream hook in ng_xxx_newhook (ng_sample.c:234).
Verdict
Reproduced live on the stock v1 netgraph stack: a frame written into sample:downstream is reflected straight back to the sender (readable again on the sending data socket) and GET_STATUS reports packets_in=0/packets_out=1, because downstream_hook.dlci stays 0 (constructor M_ZERO at sys/netgraph/ng_sample.c:155 initializes only the 16 DLCI channels at :158-161; the newhook downstream branch at :233-237 never sets the documented -1 sentinel from :118) so rcvdataq's 'dlci != -1' branch at :355 forwards the frame back out downstream_hook.hook at :362-364; the intended decode/demux branch (:365-375) is unreachable. Module builds from /usr/src (-Werror, RC=0) but does not ship (no sys/netgraph/Makefile SUBDIR entry, no sys/conf/files line, absent from /boot/kernel); node creation is root-gated (caps_priv_check at sys/netgraph/socket/ng_socket.c:172). v1 twin of DF-3545 (ng7), demonstrated live for the first time. DF-0690's v1 counterpart and the newhook defects are filed separately (DF-3551, DF-3552).
No comments yet.