DF-3272 / verdict.json
{ "finding_id": "DF-3272", "status": "reproduced", "reproduced": 1, "impact": "memcorrupt", "confidence": "certain", "verdict": "Unauthenticated PPP peer heap out-of-bounds WRITE, reproduced with byte-exact contents: an IPCP CONF-REQ VJ-compression option with Max-Slot-Id=255 reaches sl_compress_init(sp->pp_comp, 255) (if_spppsubr.c:2976, also :3167), whose tstate[i] loop (tstate is cstate[16], stride 144) writes up to ~32KB past the 4656-byte pp_comp allocation. Kernel-memory dumps prove a heap pointer + index byte written into the ADJACENT 4656-byte chunk (tstate[32] at comp+4672, past the end), exactly as predicted from source. No immediate panic in a clean single shot (write is sparse: 9 bytes per 144); two deferred panics in an earlier session are consistent with later use of the corrupted objects. uid0/remote-RCE chain not completed: per-offset values are fixed by the offset (pointer=&tstate[i-1], byte=i), so escalation needs a 4097..5120-byte size-class victim with an exploitable field at a matching offset plus grooming on DFlys size-segregated slab allocator -- documented as the escalation path. fix.diff (clamp Max-Slot-Id to MAX_STATES-1, NAK out-of-range) validated in-guest: corruption gone, negotiation proceeds.", "exploit_chain": "peer frames only: (1) ACK kernel LCP CONF-REQ (id off the wire), (2) send magic-only CONF-REQ -> LCP OPENED -> PHASE_NETWORK (no auth by default, CONF_ENABLE_VJ default on), (3) IPCP CONF-REQ with option 02 06 00 2d ff 01 -> sl_compress_init(comp, 255) -> OOB write comp+2352..comp+36768, 144-byte stride, ptr at +0 / index byte at +10 -> [proven] adjacent same-size-class heap chunk overwritten -> [not completed] victim-object grooming for control flow / uid0", "evidence": [ "findings/poc/DF-3272/run.log: AFTER dump at comp+3932 shows 0xfffff801185bb4d0 (= &tstate[26]) + byte 0x1b(27); dump at comp+4672 (16 bytes PAST the 4656-byte allocation) shows 0xfffff801185bb7a0 (= &tstate[31]) + byte 0x20(32) -- cross-object corruption, contents exactly as predicted", "findings/poc/DF-3272/run.fixed.log: same driver on fix.diff-patched sppp.ko -- AFTER == BEFORE (all zeros)", "findings/poc/DF-3272/VERDICT.md: full narrative incl. exploitability ceiling" ], "kernel_refs": [ "sys/net/sppp/if_spppsubr.c:2976", "sys/net/sppp/if_spppsubr.c:3167", "sys/net/sppp/if_spppsubr.c:964", "sys/net/ppp_layer/slcompress.c:20", "sys/net/slcompress.h:153" ], "poc_changes": "Original sketch was a standalone C file with wrong assumptions (no driver feeds sppp on this guest; ng_sppp does not compile). Rewrote as a harness KLD modeling the sync-PPP driver (own struct sppp softc + sppp_attach + if_attach + /dev/spppinj0 write->sppp_input, read->TX capture, SIOCSIFFLAGS passthrough) plus a userland driver (drive3.c) that handshakes LCP with ident re-sync (the 3s retransmit timer bumps confid), sprays the slcompress size class, dumps comp memory before/after via a harness ioctl, then churns. Syscall numbers/struct layouts were derived from the guests own /usr/src.", "attempts": 6, "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": 2100, "guest_dirty": 0, "build_cmd": "cd /root/poc && make && cc -O2 -o drive3 drive3.c (harness KLD + driver; fix build: cd /usr/src/sys/net/sppp && make)", "run_cmd": "kldload /root/poc/sppp_harness.ko && /root/poc/drive3", "code_hash": "4ebc33c04c73cfeca304586c3f5f0e5c9d0af96d87d650972b6a7ec74fdf401a", "notes": "Legacy (PPP) bucket. Delayed panics observed in an earlier session (ip=0x0 jump; vm_fault: fault on stack guard in the sppp_cp_input ICF clone) are consistent with this corruption but not attributably captured; the verdict rests on the proven write. The harness memory-dump ioctl is PoC introspection only -- the trigger is pure peer frames.", "recommended_fix": "Clamp the IPCP VJ Max-Slot-Id to MAX_STATES-1 (NAK out-of-range values) before calling sl_compress_init(), at if_spppsubr.c:2976 and :3167; defense-in-depth: clamp inside sl_compress_init() too.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (sppp.ko rebuilt from patched /usr/src/sys/net/sppp, cc 8.3 -Werror clean)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "With fix.diff applied (p[4] >= MAX_STATES -> NAK with our max_state in RCR; skip re-init in RCN_nak), the identical weapon frame leaves the heap identical to baseline (AFTER dumps all zeros, run.fixed.log) and IPCP continues negotiating. Baseline vs patched differ exactly in the corruption.", "fix_evidence": [ "findings/poc/DF-3272/run.log (baseline corruption)", "findings/poc/DF-3272/run.fixed.log (patched: no corruption)", "findings/poc/DF-3272/fix.diff" ] } |