⬢ DragonFlyBSD Kernel Audit
DF-3537 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3537",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "leak",
  "confidence": "certain",
  "verdict": "Reproduced on DragonFly 6.5-DEVELOPMENT #0 (stock INVARIANTS kernel): ng_vjcs vjcomp path guarantees only min(19, m_pkthdr.len) bytes but sl_uncompress_tcp_core reads up to 19 bytes unvalidated; a 2-byte frame makes the mandatory th_sum read (slcompress.c:524) cross the packet boundary and 30/30 probes wrote stale mbuf bytes (0x1111 = prior sync frames window field) into kernel cstate, read back via NGM_VJC_GET_STATE; diagnostic runs additionally showed DECODE-walk corruption of urp/win/ack/id with earlier-packet residue delivered out the ip hook by a clean emit frame. Leak-only (all writes stay inside struct cstate). fix.diff bounds-checks every engine read; rebuilt ng_vjc.ko: 30/30 -> 0/30 with legal frames intact.",
  "exploit_chain": "remote/local frame source -> short vjcomp frame -> sl_uncompress_tcp_core reads 1-17 bytes past m_len (checksum read + DECODE walk, no buflen checks) -> stale mbuf bytes persist in rstate[cid].cs_ip -> next clean compressed frame emits them inside the reconstructed TCP/IP header to the ip hook -> attacker reads reconstructed stream (up to ~10 network-visible bytes per probe pair, unlimited repetitions)",
  "evidence": "run.log / run.2.log: [oracle] 30/30 rounds leaked non-zero stale bytes via th_sum; leak_sample.txt (cross-packet traces incl. 0x6572, 0x7995, urp=0x33/win+=0x34 from a prior syncs seq field); fix_run.log: 0/30 after fix.diff with [sanity] th_sum = 0xC75D OK",
  "kernel_refs": [
    "sys/netgraph/vjc/ng_vjc.c:456",
    "sys/netgraph/vjc/ng_vjc.c:461",
    "sys/netgraph/vjc/ng_vjc.c:468",
    "sys/net/ppp_layer/slcompress.c:115",
    "sys/net/ppp_layer/slcompress.c:524",
    "sys/net/ppp_layer/slcompress.c:571"
  ],
  "poc_changes": "Seed sketch was fully rewritten: real graph is built with libnetgraph (NGM_MKPEER/CONNECT/NAME + NGM_VJC_SET_CONFIG with binary struct ngm_vjc_config); deterministic oracle is the 2-byte-probe checksum read observed through NGM_VJC_GET_STATE (a struct slcompress mirror verified by a legal 19-byte frame); v1 path addressing requires trailing colons (vinj:); EINVAL from NgSendData is the nodes synchronous frame-drop, not an error.",
  "attempts": 14,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "runtime_sec": 3600,
  "guest_dirty": 0,
  "build_cmd": "cc -O -o /root/vjc_oob_leak /root/vjc_oob_leak.c -lnetgraph",
  "run_cmd": "kldload netgraph ng_socket ng_vjc && /root/vjc_oob_leak",
  "code_hash": "855b59d625de86b3d45bb091ed74209769674b230fbaae3f390dcf53597fc217",
  "notes": "DECODE-field exfil is mbuf-recycling dependent (often self-masks when stale bytes equal current state); the 2-byte checksum oracle is deterministic. Locally building the graph requires root; threat model is the remote PPP peer (same surface as DF-3272). Guest reset with-src performed after verification.",
  "recommended_fix": "Bounds-check every read in sl_uncompress_tcp_core against buf+buflen (DECODE macros, changes/CID/checksum, struct ip minimum) and fail the frame via the existing bad path; see fix.diff.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied to /usr/src/sys/net/ppp_layer/slcompress.c in the guest; ng_vjc.ko (which compiles slcompress.c) rebuilt and reloaded; identical PoC run: 2-byte-probe oracle went from 30/30 leaked stale bytes to 0/30 while the legal-frame sanity check (th_sum=0xC75D) still passes. Same result for the ng7 twin module.",
  "fix_evidence": "fix_run.log (0/30 + sanity OK), fix_build.log (module relink), compare run.log (30/30)"
}