DF-2896 / verdict.json
{ "finding_id": "DF-2896", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "cnwrite() captures constty->t_dev without any token or cdev reference (sys/kern/tty_cons.c:465-466) and dispatches dev_doperate() on it only after log_console() has slept and walked the whole write buffer (tty_cons.c:469-471, subr_prf.c:259-299). On default kernels (options UCONSOLE, X86_64_GENERIC:36) an unprivileged user controls that cdev's lifetime: TIOCCONS attaches their pty (kern/tty.c:959-977, verified live as uid=1001 via gate_test.c), closing the pair NULLs constty (tty.c:251-252), NULLs t_dev and destroy_dev()s the cdev (tty_pty.c:280-291). The race window (code-proven, milliseconds-to-seconds wide) therefore exists with certainty, but across 8 harness designs and ~20 minutes of genuine racing (~7000 teardowns vs ~2000 capture->dispatch envelopes, with recycling pressure and a deterministic misdirection detector) no manifestation occurred: the async devfs-thread teardown latency and the dedicated per-cpu objcache magazines for struct cdev decouple the free/realloc (with its ~us si_ops==NULL transient, devfs_core.c:2445) from the dispatch instants. Two guest hard-downs under heavier load left no panic text and are unattributable. Realistic ceiling if it ever lands: NULL-deref panic (local DoS requiring a concurrent privileged /dev/console writer, e.g. default syslogd) or a misdirected privileged console write into an unrelated pty; full RIP control is not credible (dedicated cdev objcache, kernel-initialized contents, static ops).", "exploit_chain": "", "evidence": [ "findings/poc/DF-2896/VERDICT.md โ full path:line proof of the window and the attempt matrix", "findings/poc/DF-2896/gate_test.c โ unprivileged TIOCCONS gate test, PASSED on stock guest (uid=1001)", "findings/poc/DF-2896/racer.c โ unpriv constty lifecycle cycler (4864 iterations in final run)", "findings/poc/DF-2896/holder.c โ unpriv recycling pressure + misdirection detector ('W' on never-TIOCCONS'd ptys), 0 hits", "findings/poc/DF-2896/writer.c โ root /dev/console writer providing capture->dispatch envelopes", "findings/poc/DF-2896/logs/ โ run summaries, racer/holder/writer logs, serial tail", "findings/poc/DF-2896/fix.diff โ token-serialized capture+forward fix (validated: not_testable, no baseline repro)" ], "kernel_refs": [ "sys/kern/tty_cons.c:456-472", "sys/kern/tty_cons.c:71", "sys/kern/tty.c:959-977", "sys/kern/tty.c:251-252", "sys/kern/tty_pty.c:280-291", "sys/kern/subr_prf.c:259-299", "sys/kern/kern_device.c:544-561", "sys/vfs/devfs/devfs_core.c:63-75", "sys/vfs/devfs/devfs_core.c:2436-2459", "sys/config/X86_64_GENERIC:36" ], "poc_changes": "Seed concept rewritten entirely: gate test proving unprivileged UCONSOLE TIOCCONS on the stock guest; serial-quiet constty cycler (immediate re-attach) after the first design wedged the guest with console firehose; dedicated unpriv holder for objcache recycling pressure plus a deterministic misdirection detector (fill byte 'W' appearing on never-TIOCCONS'd ptys); root writer downsized to 1x4MB at 3/s (envelope ~120ms). Three early harness configurations were invalid (unpriv actor EACCES under /root, csh redirect rejection, holder not rebuilt) โ found via heartbeat logging and fixed; only the last two runs count as genuine racing.", "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", "runtime_sec": 5400, "guest_dirty": 0, "build_cmd": "cc -O2 -o racer racer.c && cc -O2 -pthread -o writer writer.c && cc -O2 -o holder holder.c && cc -O2 -o pin pin.c", "run_cmd": "/tmp/df2896/run.sh (racer + holder as uid=1001, writer as root; poll holder.log for HIT, serial boot.log for 'Fatal trap|panic:')", "code_hash": "27cbd1f1f2ef92c1d15357d22c243d8684b1bd625dd4e36b1d970e920ef9fd11", "notes": "Guest was reset with-src after the runs (clean). The unpriv gate (UCONSOLE) is live-verified, so the lifetime-control half of the race is certain; the manifestation half is probabilistic and did not land. Recommended follow-up: long-soak race (hours) or an instrumented kernel build to log cnwrite dispatches on freed slots. Two unattributed hard-downs occurred under console-firehose load (runs 4-5) โ full 105MB serial log contained no panic text.", "recommended_fix": "In cnwrite(), capture constty->t_dev under the constty tty's t_token (the token held by the ttyclose/pti_done teardown path) and hold the token across the dev_doperate() forward; see fix.diff.", "fix_status": "not_testable", "fix_kernel_uname": "", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "No baseline reproduction exists to diff against; the fix is source-reviewed only (serializes capture+forward against the exact teardown path that holds tp->t_token across constty=NULL / t_dev=NULL / destroy_dev).", "fix_evidence": "findings/poc/DF-2896/fix.diff" } |