DF-4148 / verdict.json
{ "finding_id": "DF-4148", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "Code-level race proven by trace but not triggered on the guest in 420 teardown-vs-open rounds. cuse_client_open (cuse.c:1509-1521) reads dev->si_drv1 unlocked and does pcs->refs++ under pcs->mtx; the final cuse_server_unref (cuse.c:692) decrements refs to 0 with no re-check and frees pcsd/pcs after an ASYNCHRONOUS destroy_dev (devfs_core.c:755-763 - the synchronously comment at cuse.c:679 is wrong on DFly). A racy open lands refs 0->1 on the dying server, inserts its client into freed pcs, and its is_closing error path runs a SECOND full teardown: double TAILQ_REMOVE from cuse_server_head, double kfree(pcsd)/kfree(pcs). The hammer (6 cycler processes doing open/close at full speed vs 1-second pumper + 1 Hz teardown poll) completed 120 + 300 rounds with the guest alive and /dev/cuse still serviceable. The window is a few microseconds wide; the trace is sound but the practical hit-rate on this 6-vCPU guest was too low to demonstrate. Supporting observations: a pumper parked >60s in cuse_server_frees wait loop mid-run (refs oscillation), and harness-induced wedges showing teardown with blocked clients never completes.", "exploit_chain": "", "evidence": "VERDICT.md (full interleaving trace with path:line); run.log / run.2.log (120 + 300 clean rounds, guest alive); cuse_race.c", "kernel_refs": [ "sys/dev/misc/cuse/cuse.c:1497", "sys/dev/misc/cuse/cuse.c:1509", "sys/dev/misc/cuse/cuse.c:1521", "sys/dev/misc/cuse/cuse.c:1564", "sys/dev/misc/cuse/cuse.c:657", "sys/dev/misc/cuse/cuse.c:678", "sys/dev/misc/cuse/cuse.c:692", "sys/dev/misc/cuse/cuse.c:731", "sys/vfs/devfs/devfs_core.c:755", "sys/kern/kern_conf.c:346" ], "poc_changes": "Harness iterated twice: v1s pump loop never exited (self-deadlock) and children inherited /dev/cuse keeping dead servers alive; v2 closes the inherited fd in children, bounds the pump with SIGALRM + wall-clock deadline, uses unique per-round devnames, and treats EINVAL/ENOENT/ENXIO as end-of-round.", "attempts": 420, "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": 900, "guest_dirty": 0, "fix_status": "not_testable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff authored (dying flag set under cuse_global_mtx before teardown; open-side si_drv1 load + dying check + refs++ under the same lock; si_drv1 NULLed under cuse_global_mtx in cuse_server_free_dev) but not built/validated on the guest because the race itself did not reproduce - there is no behavioral baseline to diff against.", "fix_evidence": "fix.diff in this directory; logic-level review only", "build_cmd": "cc -O1 -Wall -o /root/cuse_race /root/cuse_race.c", "run_cmd": "kldload cuse; /root/cuse_race 300", "notes": "Unprivileged users can drive the open() side against any world-writable cuse node; the exit side needs the daemon to die/restart at that moment.", "recommended_fix": "Serialize the final refs decrement and the open-side increment under cuse_global_mtx with a dying flag; NULL si_drv1 under the same lock before freeing pcsd (see fix.diff).", "code_hash": "b977bd9e5ba5d79b451c483b1a8f185726fec73f8b268a13c85ac23775353b91" } |