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

ng7 twin of DF-3537: VJ decompression OOB read on the vjcomp hook leaks stale mbuf bytes via persistent rstate[] into reconstructed TCP/IP headers

Summary

ng7 ng_vjc pulls up only min(MAX_VJHEADER=19, m_pkthdr.len) bytes on the vjcomp hook while the shared engine (sys/net/ppp_layer/slcompress.c - compiled directly into the ng7 module per its Makefile .PATH) walks up to 19 bytes with no buflen checks. Short frames make the kernel read past m_len into stale mbuf contents that persist in rstate[cid].cs_ip and are re-emitted inside reconstructed headers on the ip hook. VERIFIED identically to the v1 twin: 30/30 oracle rounds on a freshly built netgraph7 stack (2-byte probe [0x40,0x01] -> stale 0x1111 in rstate[1].cs_ip.th_sum, mirror verified by legal 19-byte frame th_sum=0xC75D). Single engine patch fixes both front-ends; validated 30/30 -> 0/30 on the rebuilt ng7 module with legal frames intact. No callouts in the node (no DF-3531/3536 twin); lifecycle and engine write paths verified sound.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3538 Β· 12 files
FileTypeDescriptionSize
vjc_oob_leak.c β€” 11.0 KB view raw
build.sh β€” 257 B view raw
run.sh β€” 137 B view raw
build.log β€” 256 B view raw
run.log β€” 3.2 KB view raw
fix_run.log β€” 1.0 KB view raw
fix_build.log β€” 172 B view raw
leak_sample.txt β€” 1.1 KB view raw
env.txt β€” 495 B view raw
fix.diff β€” 1.7 KB view raw
VERDICT.md β€” 2.3 KB ↓ raw
verdict.json β€” 3.0 KB view raw
VERDICT.md
↓ download raw

DF-3538 VERDICT β€” reproduced (kernel heap info leak, ng7 twin of DF-3537)

Bottom line

Reproduced on the same guest (DragonFly 6.5-DEVELOPMENT #0, stock INVARIANTS kernel) with the netgraph7 stack built from /usr/src/sys/netgraph7 (core + socket + vjc modules) and loaded on a fresh boot (ng7 and v1 sockets both claim AF_NETGRAPH). The ng7 vjcomp path (sys/netgraph7/vjc/ng_vjc.c:449-463) is line-identical in logic to the v1 path and shares the engine (sys/net/ppp_layer/slcompress.c β€” the ng7 module's Makefile .PATHs into net/ppp_layer and compiles it), so the 2-byte-probe oracle behaves identically: 30/30 rounds wrote stale mbuf bytes (0x1111, residue of the preceding sync frame's TCP window field, which was never part of the 2-byte probe) into rstate[1].cs_ip.th_sum, read back with NGM_VJC_GET_STATE (mirror verified by a legal 19-byte frame, th_sum=0xC75D). Impact: kernel memory info leak (stale mbuf data / cross-packet disclosure); no write crosses an object boundary.

Root cause (path:line)

Fix validation

fix.diff (bounds-checked reads in the shared engine) applied to the guest's /usr/src; ng7 ng_vjc.ko rebuilt (it embeds slcompress.c) and reloaded; identical PoC re-run: oracle 0/30 leaks, [sanity] th_sum = 0xC75D OK (no functional regression). Baseline vs patched logs: run.log (30/30) and fix_run.log (0/30).

Threat model

Same as DF-3537: the remote peer of a VJ-compressed link whose ng7 vjc node decompresses peer frames (e.g. ng7-based PPP). Local graph construction needs root; the PoC is a demonstration vehicle. Leak-only β†’ Medium.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applied to /usr/src/sys/net/ppp_layer/slcompress.c; ng7 ng_vjc.ko rebuilt (module embeds slcompress.c) and reloaded; identical PoC: 30/30 -> 0/30 oracle leaks with the legal-frame sanity check still passing.

fix_run.log (0/30 + sanity OK), fix_build.log (module relink), compare run.log (30/30)
↓ fix.diffper-fix-DF-3538

Confirmed kernel references

Detail

Exploit chain

remote/local frame source -> short vjcomp frame -> sl_uncompress_tcp_core reads past m_len (checksum read + DECODE walk, no buflen checks) -> stale bytes persist in rstate[cid].cs_ip -> next clean compressed frame emits them inside the reconstructed TCP/IP header to the ip hook

Evidence (decisive lines)

run.log: '[oracle] 30/30 rounds leaked non-zero stale bytes via th_sum' + '[sanity] th_sum = 0xC75D OK'; fix_run.log: 0/30 after fix.diff with sanity still OK

PoC changes

Built libnetgraph7 from /usr/src/lib/libnetgraph7 sources directly into the PoC binary (no installed ng7 userland exists); ng7 modules built from /usr/src/sys/netgraph7 and loaded on a fresh boot (AF_NETGRAPH conflict with v1).

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

Verdict

Reproduced with the netgraph7 stack (core+socket+vjc modules built from /usr/src/sys/netgraph7, loaded on a fresh boot since ng7 and v1 sockets both claim AF_NETGRAPH): the ng7 vjcomp path (ng7 ng_vjc.c:449-463) shares the v1 logic and the shared slcompress engine, and the 2-byte-probe oracle leaked stale mbuf bytes into rstate[1].cs_ip.th_sum in 30/30 rounds (0x1111 = prior sync frame's window field), with the struct mirror verified by a legal 19-byte frame (th_sum=0xC75D). Leak-only. fix.diff bounds-checks the engine reads; ng7 ng_vjc.ko rebuilt with it: 30/30 -> 0/30, sanity intact.