⬢ DragonFlyBSD Kernel Audit
DF-5195 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5195",
  "status": "reproduced",
  "reproduced": true,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "Deterministically reproduced on a fresh-boot QEMU guest (vidconsole kernel console + snake_saver.ko loaded, blank timer default): a CONS_SET mode-change ioctl issued while the LKM screen saver has the console blanked never returns -- the calling thread sleeps on &scrn_blanked (scrsav) holding vga_token (scioctl syscons.c:983 / sc_set_text_mode scvidctl.c:68 -> sc_clean_up -> wait_scrn_saver_stop non-unlock branch), while the only wake path (scrn_timer -> stop_scrn_saver -> scsplash_saver(FALSE) -> restore_scrn_saver_mode(TRUE) -> set_mode) must lwkt_gettoken(&vga_token) at syscons.c:4171 -- a token inversion deadlock. Killing the hung process does not restore consistency: scrn_blanked stays 1 and SC_SCRN_BLANKED stays set forever (verified via /dev/kmem), the saver module becomes permanently unloadable (kldunload hangs, RC=124), and every later saver-stop waiter sleeps forever while holding vga_token, wedging the console subsystem and the scrn_timer callout until reboot. Not memory corruption: a privileged-trigger local kernel DoS with permanent state corruption (realistic sequence: console idles 5 min -> blank -> X/vidcontrol starts).",
  "exploit_chain": "root loads any LKM saver module once (documented loader.conf configuration) -> console blanks after idle (scrn_blank_time defaults to 300s) -> any process with a /dev/ttyvN fd issues CONS_SET/KDSETMODE/VT_ACTIVATE while blanked -> kernel deadlock (ioctl never returns; kill leaves scrn_blanked permanently stuck, module unload permanently hangs, subsequent console ioctls that wait on the saver sleep forever holding vga_token and block the scrn_timer stop retry) -> console subsystem wedged until reboot. Ceiling: kernel-console DoS; no privilege escalation path (no memory corruption).",
  "evidence": "run.log (fresh vm.sh reset with-src boot: child blocked 10s in CONS_SET -> DEADLOCK REPRODUCED -> SIGKILL -> 12s later kread shows scrn_blanked=1 stuck -> kldunload timeout RC=124); discovery_ps.txt (wchan scrsav for the hung ioctl and for a later vidcontrol -p waiter; /dev/kmem dumps showing scrn_blanked=1, SC_SCRN_BLANKED SET, saver_mode=CONS_LKM_SAVER); deadlock_probe.c (deterministic reproducer); kread.c (live statics reader).",
  "kernel_refs": [
    "sys/dev/misc/syscons/syscons.c:2720",
    "sys/dev/misc/syscons/syscons.c:2734",
    "sys/dev/misc/syscons/syscons.c:983",
    "sys/dev/misc/syscons/syscons.c:2691",
    "sys/dev/misc/syscons/syscons.c:2204",
    "sys/dev/misc/syscons/syscons.c:4171",
    "sys/dev/misc/syscons/syscons.c:3536",
    "sys/dev/misc/syscons/scvidctl.c:105",
    "sys/dev/misc/syscons/scvidctl.c:68",
    "sys/kern/lwkt_token.c:832"
  ],
  "poc_changes": "n/a (finding and PoC originated in this run; no seeded draft to fix)",
  "attempts": 3,
  "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": 42,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -o deadlock_probe deadlock_probe.c && cc -O2 -o kread kread.c",
  "run_cmd": "kldload /boot/kernel/snake_saver.ko && ./kread && ./deadlock_probe",
  "code_hash": "6d716db0da450e5b0a6290f3899542e098154a9a2211c29958fc259b0c79af54",
  "notes": "Discovery path: found while verifying DF-5194 (snake_saver.c) -- the mode change while blanked is guarded positive control hung instead. First hang on the polluted first boot, then a clean deterministic reproduction on a fresh vm.sh reset with-src boot with console=vidconsole. With the stock serial console (console=comconsole) the LKM saver subsystem is entirely inert (splash_init never runs; splash_adp stays NULL; kldload only queues the decoder) -- an environmental gate worth knowing for future saver-family PoCs. /dev/ttyv* is root-only (0600) on this guest, so the direct trigger is privileged; an unprivileged trigger would require permissive ttyv* devfs rules. Guest was left clean: loader.conf restored to comconsole and rebooted after evidence collection.",
  "recommended_fix": "sc_clean_up(): drop sc_clean_ups own vga_token reference around wait_scrn_saver_stop() and re-acquire it, so the saver-stop wake path (stop_scrn_saver -> restore_scrn_saver_mode(TRUE) -> set_mode) can take the token; LIFO-safe because the dropped reference is the innermost one on the threads token stack.",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "fix.diff authored (sc_clean_up vga_token drop/reacquire around the saver-stop wait) but not built or run: validating it requires a guest kernel rebuild, and per the audit contract fix validation is mandatory only for memory-corruption findings. The proposed fix is LIFO-safe for all callers because sc_clean_up acquires its own token reference at entry; residual risk is a widened (non-deadlocking) race window while the token is dropped, strictly better than the deterministic deadlock.",
  "fix_evidence": "fix.diff; deadlock baseline in run.log"
}