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)
PoC verification
Evidence pack
findings/poc/DF-3538 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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)
sys/netgraph7/vjc/ng_vjc.c:449-454β pullup onlymin(MAX_VJHEADER, m_pkthdr.len)bytes;sys/net/ppp_layer/slcompress.c:524β mandatory checksum read with no length validation;sys/net/ppp_layer/slcompress.c:115-140β DECODE* macros read 1β3 bytes each unchecked;sys/net/ppp_layer/slcompress.c:571-575β only post-hoc length check.
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
fixedfix.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)
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.
No comments yet.