{
  "finding_id": "DF-3558",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "leak",
  "confidence": "certain",
  "verdict": "REPRODUCED (deterministic). ngh_rcvmsg (sys/netgraph7/hole/ng_hole.c:163-168) validates only header.arglen==NG_HOOKSIZ(32) and passes (char*)msg->data to ng_findhooks strcmp (ng_base.c:1095-1107); nothing on the path checks that the arglen claim fits what was actually sent (ngc_send kmallocs len+1: ng_socket.c:261-262; ng_package_msg validates nothing: ng_base.c:3007-3027), so an under-sent control message makes msg->data stale heap and the strcmp reads out of bounds of the kmallocd buffer. Demonstrated as a heap-residue match oracle: a getstats that supplies ZERO hook-name bytes returns the real hooks stats (frames=1 octets=5) in 256/256 rounds per run across 3 runs, because the allocator hands back the groomed 64-byte slab chunk (zoneindex() 8-byte granularity, kern_slaballoc.c). The honest-length unterminated case is provably safe (hook names are NUL-terminated within 31 chars, so strcmp stops by data[31]) -- carried as the PoCs negative control (ENOENT). Root-gated (uid 1001: EPERM at socket(), caps_priv_check); module buildable/loadable but not shipped in the default kernel or netgraph7 SUBDIR. Same consumer-side pattern as DF-0610/DF-2600/DF-3496 on the DF-0534 enabler. Fix validated: socket-side arglen-vs-len check + hole-side NUL check -> 0/256, all EINVAL, well-formed stats unregressed.",
  "exploit_chain": "",
  "evidence": "run.log: RESULT oob_matches=256/256 ... stats returned for a request that supplied ZERO hook-name bytes; negative control ENOENT on the same run; run.2.log/run.3.log determinism; unpriv EPERM in run.log; fix_run.log: oob_matches=0/256 rejected=256 (EINVAL), sanity stats still frames=1 octets=5; echo_neg.log: live negative proof that a closed echo<->echo ring is inert (separate ng_echo pass-2 lead).",
  "kernel_refs": [
    "sys/netgraph7/hole/ng_hole.c:163",
    "sys/netgraph7/hole/ng_hole.c:168",
    "sys/netgraph7/netgraph/ng_base.c:1095",
    "sys/netgraph7/netgraph/ng_base.c:3007",
    "sys/netgraph7/socket/ng_socket.c:261"
  ],
  "poc_changes": "Seed harness was written fresh for this batch (DF-3557s ui_cycle.c pattern). Iterations: (1) initial version treated sendto failure as fatal -- ng7 returns the node handlers error synchronously via sendto errno (ENOENT = no hook match), so error accounting was reworked around that; (2) response wait switched from SO_RCVTIMEO to poll(); (3) fix-run accounting added (rejected/EINVAL).",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (stock INVARIANTS)",
  "runtime_sec": 96,
  "guest_dirty": 0,
  "build_cmd": "sh build.sh (in-guest root: make in netgraph7/{netgraph,socket,hole,echo}; cc -O2 triggers)",
  "run_cmd": "sh run.sh (kldload stack; /root/poc58/echo_neg; /root/poc58/hole_oob)",
  "code_hash": "cee8c37ac3553c142f64849d55572cde74cd501f1d017a1ae85a490da93b4b3d",
  "notes": "Impact classified leak as the closest enum: an OOB read of kernel heap residue whose observable is a match/no-match oracle (control-flow, not direct byte disclosure); root-gated, non-default module -> severity Low. The ng_echo negative evidence (echo_neg) and the ng_ip_input/ng_split pass-2 negative results are documented in VERDICT.md.",
  "recommended_fix": "ng_socket.c ngc_send: reject messages whose mbuf chain is shorter than sizeof(struct ng_mesg)+header.arglen (EINVAL); ng_hole.c: also require msg->data[NG_HOOKSIZ-1]==\\0.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fixed",
  "fix_evidence": "fix_run.log: after vm.sh reset with-src + patch(1) of fix.diff into /usr/src + -Werror-clean rebuild of ng_socket.ko/ng_hole.ko, the exact PoC shows oob_matches=0/256 with rejected=256 (EINVAL at the socket), sanity well-formed getstats still returns frames=1/octets=5 (no regression), and echo_neg still passes."
}