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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3550 Β· 10 files
FileTypeDescriptionSize
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.c v1-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.log module build (-Werror, RC=0)
  • run.log decisive run (reflection + leak + hooktest sections)
  • env.txt guest uname / compiler / module state
  • fix.diff one-line sentinel fix
VERDICT.md
↓ download raw

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)

  1. ng_xxx_constructor() M_ZEROs the whole struct XXX (sys/netgraph/ng_sample.c:155) and then initializes only the 16 DLCI channels (:158-161). downstream_hook.dlci stays 0.
  2. ng_xxx_newhook()'s downstream branch (:233-237) stores the hook pointer and private pointer but never sets the -1 sentinel documented at :118 (int dlci; /* -1 == downstream */).
  3. Data arriving on the downstream hook: ng_xxx_rcvdata() reads dlci = hookinfo->dlci == 0 (:333), ng_xxx_rcvdataq() takes the dlci != -1 "received on a DLCI hook" branch (:355) and forwards the frame back out xxxp->downstream_hook.hook (:362-364) β€” the very hook it arrived on β†’ reflection to the sender, packets_out++.
  4. 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_testable
baseline reproduced→ patch + rebuild →patched clean

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

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).