DF-5015 / verdict.json
{ "finding_id": "DF-5015", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "Reproduced: an unprivileged console user (uid 65534 after fd open) shrinks a syscons history ring to 25 rows via ioctl(CONS_HISTORY,25) on an 80x25 vty; a mode change to 80x60 updates scp->xsize/ysize BEFORE sc_alloc_history_buffer() runs, and when that realloc fails (M_NOWAIT under kernel memory pressure, reproduced via unpriv churn) its return is ignored (scvidctl.c:163/397/947) and the failure path re-installs the stale 2000-cell ring against a 4800-cell screen. The mismatch was reproduced three times with a user-visible artifact (scroll-lock+End view shows the marker line 2-3x: ring wrap) and confirmed in-kernel with a KLD probe (sysctl hw.df5015: rows=25 size=2000 ... ysize=60). Scroll-lock exit runs sc_hist_restore() whose sc_vtb_pos(tail,-xsize*ysize) is negative for every tail in this configuration; the subsequent sc_hist_save() append executed on the armed ring (observed via ring-content views). Impact is recorded as dos because the directly-observed manifestation is history/scrollback corruption; the write primitive itself (up to 4000 bytes of attacker-typed screen text BEFORE the ring, plus an OOB read past it when columns misalign) executed per the code path but no independent crash/leak artifact was pinned to it in isolation - a kernel write fault in sc_vtb_bcopy during the same campaign is attributed to DF-5016s UAF (no keyboard was active in that run). A generic DragonFly ioctl-loop token assertion (reproducible with TIOCGWINSZ alone) was identified and excluded as a confound.", "exploit_chain": "console-seat fd -> ioctl(CONS_HISTORY,25) -> ioctl(CONS_SET, 80x60) during kmem pressure (realloc fails, geometry already updated) -> scroll-lock in/out (sc_hist_restore stores negative vtb_tail) -> any scrolled line/mode change appends up to 4000 bytes of controlled screen text before the 4000-byte M_SYSCONS ring (kmem-obj heap underflow write; repeatable); column-mismatched variants OOB-read heap to the visible screen. uid=0 not achieved (needs deterministic alloc-failure window + heap grooming; documented in VERDICT.md).", "evidence": "README.md/VERDICT.md; df5015.c (trigger tool); round2.py-round6.py transcripts in run logs; marker wrap artifacts [3,34,59]/[28,59] (hunt detections); offmod2.c in-kernel probe output rows=25 size=2000 ... ysize=60; panic-scvtb_bcopy-unpriv-churn.log (campaign write fault); fix.diff (validated build)", "kernel_refs": [ "sys/dev/misc/syscons/schistory.c:118-160", "sys/dev/misc/syscons/schistory.c:211-215", "sys/dev/misc/syscons/schistory.c:219-234", "sys/dev/misc/syscons/schistory.c:244-259", "sys/dev/misc/syscons/scvtb.c:183-186", "sys/dev/misc/syscons/scvtb.c:221-235", "sys/dev/misc/syscons/scvtb.c:239-242", "sys/dev/misc/syscons/scvtb.c:198-211", "sys/dev/misc/syscons/scvidctl.c:146-163", "sys/dev/misc/syscons/scvidctl.c:382-397", "sys/dev/misc/syscons/syscons.c:3843-3933" ], "poc_changes": "rewrote trigger from scratch: CONS_HISTORY/CONS_SET/CONS_SCRSHOT/TIOCGWINSZ/KDGKBSTATE/VT_ACTIVATE plumbing (by-value ioctl args), QEMU-monitor key injection with hardware SLK-state sync, in-kernel KLD probe instead of /dev/kmem (pread rejects kernel-canonical addresses and blind kmem scans fault), pressure redesigned several times (pipe writes block; tmpfs/anon bursts work)", "attempts": 9, "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": 9600, "guest_dirty": 0, "build_cmd": "cc -O1 -o /tmp/df5015 /tmp/df5015.c (guest)", "run_cmd": "./df5015 arm; pressure churn; ./df5015 flip 100000 3000 60 &; monitor sendkey scroll_lock/end; ./df5015 shot 0 (mismatch detect); scroll_lock in/out cycles (arm+fire)", "code_hash": "0e529d3f69c203058bbcd187072ab0747ee877543e785672b55dd5aee1f3cff1", "notes": "generic DragonFly issue found while verifying: ANY tight unpriv ioctl loop on a tty (even TIOCGWINSZ) panics with assertion count & TOK_COUNTMASK failed in _lwkt_reltokref within ~60-90s (stock kernel, 4/4) - NOT a syscons bug; route to a kern/lwkt audit. offmod/scan-tool panics in the pack are my own tooling faults, documented.", "recommended_fix": "On sc_alloc_history_buffer failure free the stale-geometry ring and leave scp->history NULL (callers/CONS_CLRHIST already NULL-guard or must be taught to); see fix.diff", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied to guest /usr/src; patched X86_64_GENERIC compiled, installed and booted (KBUILD_DONE, kernel #1 Sat Sep 26 01:40). The stale-geometry state is eliminated by construction (failure path frees the ring and installs NULL; browse/CLRHIST guard). Full re-trigger of the alloc-failure precondition on the patched kernel was not re-run to completion (precondition is a stochastic NOWAIT failure window); patched kernel survived CONS_HISTORY/mode-change churn and normal syscons function.", "fix_evidence": "/tmp/kbuild.log KBUILD_DONE + installkernel completion in transcript; fix.diff in pack" } |