DF-3673 / verdict.json
{ "finding_id": "DF-3673", "status": "inconclusive", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "Source-provable CCB lifetime race, timing-dependent, not executable on this guest (no MPT hardware). mpt_req_untimeout() is plain callout_stop() (mpt.h:769-772) on a callout_init_mp() callout, which per kern_timeout.c:1073-1096 does not interlock against an in-progress callback. mpt_timeout() (mpt_cam.c:1234-1255) reads ccb->ccb_h.ccb_mpt_ptr before the lock and ccb->ccb_h.ccb_req_ptr after acquiring it; if the timeout callback is already executing and blocked on the mpt lock while the completion path (mpt_scsi_reply_handler:2662-2723, or the turbo target paths 5252/5315) runs callout_stop + xpt_done, the callback resumes with a CCB that CAM has recycled (pooled XPT_SCSI_IO CCBS) or kfreed (zone CTIO CCBS, armed 60s at mpt_cam.c:4849): garbage req pointer -> req->serno wild read -> panic, or stale req -> TAILQ move of an innocent request to the timeout list -> spurious abort / list confusion. Window is user-amplifiable: pass(4) callers set ccb_h.timeout per command (1ms). Expected impact on hardware: local DoS/panic from an operator-group user; write-primitive via TAILQ_REMOVE not demonstrated.", "exploit_chain": "operator-group user -> pass CAMIOCOMMAND XPT_SCSI_IO with ccb_h.timeout=1ms in a tight loop -> callout fires on CPU A, blocks on mpt lock -> completion on CPU B: callout_stop (no interlock), xpt_done recycles/frees CCB -> CPU A resumes, reads ccb_req_ptr from reused memory -> wild request_t* deref (panic) or wrong-request TAILQ_MOVE + REQ_STATE_TIMEDOUT -> recovery aborts unrelated command", "evidence": "VERDICT.md (full interleaving with path:line); mpt_timeout_race.c (load harness); env.txt/run.log (no LSI hardware on guest); fix.diff (pool-resident callout arg + under-lock re-validation)", "kernel_refs": [ "sys/dev/disk/mpt/mpt_cam.c:1234-1255", "sys/dev/disk/mpt/mpt_cam.c:2662", "sys/dev/disk/mpt/mpt_cam.c:2710", "sys/dev/disk/mpt/mpt_cam.c:5252", "sys/dev/disk/mpt/mpt_cam.c:5315", "sys/dev/disk/mpt/mpt_cam.c:4849", "sys/dev/disk/mpt/mpt_cam.c:1638-1641", "sys/dev/disk/mpt/mpt.h:769-775", "sys/kern/kern_timeout.c:1073-1096" ], "poc_changes": "n/a (authored from scratch)", "attempts": 1, "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": 38, "guest_dirty": 0, "build_cmd": "cc -O -o mpt_timeout_race mpt_timeout_race.c", "run_cmd": "./mpt_timeout_race /dev/passN (blocked: no mpt hardware on guest; presence probe run instead)", "code_hash": "2baa2361c245aba7a43968f71e94424316a3475a8baef14050c4cb964f4be24f", "notes": "Race exists in upstream FreeBSD too (its mpt_timeout uses MPT_LOCK_ASSERT), but DFlys self-locking variant adds the pre-lock ccb_mpt_ptr read. Cannot be triggered on this guest even on baseline (no mpt bus exists).", "recommended_fix": "Arm the callout with the pool-resident request_t instead of the CCB; take the mpt lock via a request back-pointer, re-validate REQ_STATE_QUEUED under the lock before touching the CCB", "fix_status": "not_testable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff authored against read-only sys/ (request_t gains to_arg + sc back-pointer; callout arg becomes the request; under-lock re-validation). Build-and-run validation impossible: bug not triggerable on the baseline guest (no MPT hardware).", "fix_evidence": "fix.diff; env.txt documents hardware absence" } |