DF-4542 / verdict.json
{ "finding_id": "DF-4542", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced 3/3 on the stock guest kernel: concurrent CONS_SET text-mode cycling and write() traffic on the same /dev/ttyvN panics the kernel within 60s (lwkt_reltokref TOK_COUNTMASK assertion, lwkt_token.c:458). Isolation matrix: ioctl-churn-only (2.9M ioctls) and writers-only (1.3M writes) are clean; the minimal fatal pair is CONS_SET + writers. Root cause: sc_set_text_mode (scvidctl.c:54-184) mutates scp and frees/reallocates scp->vtb (kmalloc M_WAITOK inside sc_alloc_scr_buffer) and scp->history (lock-free sc_alloc_history_buffer) holding only vga_token, which LWKT drops at every sleep, while the tty output path scstart/sc_puts writes the same buffers under only the syscons_mtx spinlock - no common lock. fix v1 (swap-only serialization) turned the panic into a raw panic: BADFREE2 double-free at sc_vtb_destroy<-sc_alloc_scr_buffer<-sc_set_text_mode, directly confirming the heap-double-free class. fix v2 (M_NOWAIT alloc-first, commit under reentrant syscons_mtx, frees outside, history detach/install under syscons_mtx) was built and booted on the guest: the identical stress that panicked 3/3 now survives 3 consecutive 60s rounds (~1M CONS_SETs, ~3M writes) with the guest alive.", "exploit_chain": "unprivileged console session (writable /dev/ttyvN fd; sc_vid_ioctl has no privilege check) -> thread A cycles CONS_SET text modes (what vidcontrol/X do), thread B prints (getty/init/logins qualify) -> emulator writes glyphs into vtb/history slabs freed concurrently by the mode change -> kernel-heap UAF write / double-free -> observed: deterministic kernel panic (DoS). Weaponization to uid=0 would require deterministic race timing plus M_SYSCONS slab grooming (victim object reuse between free and emulator write); not completed in this run - the reliable panic plus the BADFREE2 double-free proof is the delivered impact.", "evidence": "run.log (all rounds); run.panic{1,2,3}_bootlog.txt (token assertion, cpuid 2/5/5); fixv1.BADFREE2_bootlog.txt (double-free backtrace sc_vtb_destroy<-sc_alloc_scr_buffer<-sc_set_text_mode); VERDICT.md (full analysis); fix.diff (validated v2 patch)", "kernel_refs": [ "sys/dev/misc/syscons/scvidctl.c:54", "sys/dev/misc/syscons/scvidctl.c:64", "sys/dev/misc/syscons/scvidctl.c:129", "sys/dev/misc/syscons/syscons.c:244", "sys/dev/misc/syscons/syscons.c:3560", "sys/dev/misc/syscons/syscons.c:983", "sys/dev/misc/syscons/schistory.c:72", "sys/kern/lwkt_token.c:458" ], "poc_changes": "authored scstress.c from scratch (4 vt x [CONS_SET ioctl thread + writer thread] + optional KD_TEXT1/KDRASTER churn and VT_ACTIVATE cycler; mode bitmask for the isolation matrix). No seed existed.", "attempts": 8, "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 (baseline); patched #1: Mon Sep 21 10:17:20 UTC 2026", "runtime_sec": 540, "guest_dirty": 1, "build_cmd": "cc -O2 -pthread -o scstress scstress.c ; make -j6 nativekernel KERNCONF=X86_64_GENERIC (fix validation)", "run_cmd": "/tmp/scstress 60 10 (and modes 1/2/3/7 for isolation)", "code_hash": "3089b9c902301a3b21534f8ae31d5ce5bde3a865c698797daf8d70c843fc045d", "notes": "KDRASTER/pixel paths unreachable on this guest (no VESA: hw.vesa unknown, KDRASTER->ENODEV); same missing-lock defect exists in sc_set_pixel_mode and is fixed identically in fix.diff. /dev/ttyv* are 0600 root on this ssh-only guest, so the runs executed as root; on stock console logins the session owner holds a writable ttyv fd. Residual theoretical race: async renderer draw phase (runs without syscons_mtx) - noted in VERDICT.md for upstream follow-up.", "recommended_fix": "allocate new buffers M_NOWAIT first, commit scp meta + vtb/history swaps + emulator reinit under the reentrant syscons_mtx, free old buffers outside the spinlock (fix.diff)", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fixed - v2-patched kernel survived 3x60s of the exact stress (modes 10,7,10) that panicked the stock kernel 3/3 and BADFREE2d the v1 patch; behavior change confirmed (no panic, clean exits, guest alive)", "fix_evidence": "run.log (baseline panics + v1 BADFREE2 + 3 clean v2 rounds); fix.diff (v2); VERDICT.md" } |