DF-5550 / verdict.json
{ "finding_id": "DF-5550", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "camq_resize()s new_size>=entries precondition is guarded only by #ifdef DIAGNOSTIC (cam_queue.c:116-120), which is compiled out of every stock kernel (verified: audit guest X86_64_GENERIC sets INVARIANTS but not DIAGNOSTIC). A KLD harness driving the production in-kernel camq_* primitives proved deterministically on the unmodified guest kernel that (a) a shrink below entries silently leaves entries>array_size (T1), (b) the resize bcopy writes kernel heap pointers past the new M_CAMQ allocation -- adjacent canary chunk overwritten (T2), and (c) the 1->0 geometry faults straight inside the bcopy because kmalloc(0) returns NULL: baseline Fatal trap 12 at memmove+0x9d from the camq_resize copy (panic.t4.baseline.txt). The in-tree trigger is traced: camperiphfree (cam_periph.c:602) frees a periph without dequeueing it from device->drvq and xpt_remove_periph (cam_xpt.c:1613) then shrinks the drvq with no queued-check (unlike xpt_release_devices panic-guard at cam_xpt.c:5210-5214), so a periph dying of AC_LOST_DEVICE while scheduled (busy surprise-removed device) trips the overflow; the lingering entries>array_size state predicts exactly the DF-5462 lead panic signature (camq_remove+0x35 NULL-slot write). Live xpt-path end-to-end was attempted 4x with a contract-clean rig (ptsim2: delayed completions, hold knob, removal churn) and did not close the narrow free-while-parked window within budget -- the deterministic harness plus code trace plus baseline panic carry the finding. Fix validated on a rebuilt kernel: resize refused, canaries intact, entries never exceeds array_size.", "exploit_chain": "deterministic: kldload camqtest.ko; sysctl debug.camqtest.run=2 -> camq_resize(new_size=4, entries=6) bcopy overflows the 32-byte M_CAMQ chunk into the adjacent canary chunk (kernel-pointer-sized OOB write); run=4 -> camq_resize(1->0) panics in memmove (stock-kernel crash). In-tree: surprise removal / bus deregister of a busy device -> AC_LOST_DEVICE -> camperiphfree frees a periph still parked on device->drvq -> xpt_remove_periph shrinks drvq below entries -> OOB write + entries>array_size -> subsequent camq ops read/write past the array -> NULL/garbage deref (DF-5462 signature). uid=0 route: theoretical -- 8-byte-granular heap-pointer OOB write past an M_CAMQ (24/32-byte class) allocation plus a persistent corrupted-queue state; M_CAMQ-adjacent victim grooming not attempted this run; reliable local DoS demonstrated.", "evidence": "run.log lines T2: canary 63 ... CORRUPTED at +0 and T1 after resize: entries=4 array_size=3 (stock kernel); panic.t4.baseline.txt: Fatal trap 12 ... memmove() at memmove+0x9d with stack camqtest_sysctl->memmove (the camq_resize bcopy); fix_run.log: same harness on patched kernel -> all 64 canaries intact, entries==array_size; VERDICT.md full narrative", "kernel_refs": [ "sys/bus/cam/cam_queue.c:111-138", "sys/bus/cam/cam_queue.c:116-120", "sys/bus/cam/cam_queue.c:121-133", "sys/bus/cam/cam_periph.c:573-602", "sys/bus/cam/cam_xpt.c:1602-1617", "sys/bus/cam/cam_xpt.c:5210-5216" ], "poc_changes": "harness + rig authored fresh for this finding (camqtest.c T1-T4; ptsim2.c contract-clean SIM with delayed completions + hold knob; ptchurn.c); the DF-5462 rigs unlocked-sysctl flaw was fixed (all entry points take pts_lock)", "attempts": 7, "guest_uname": "DragonFly dfbsd 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) / #1: Wed Oct 7 12:03:51 UTC 2026 (fix-validation build)", "runtime_sec": 2760, "guest_dirty": 1, "build_cmd": "sh build.sh (guest /root/df5550: camqtest.ko + ptsim2.ko via kmod.mk, ptchurn via cc)", "run_cmd": "kldload ./camqtest/camqtest.ko; sysctl debug.camqtest.run=1; sysctl debug.camqtest.run=2; sysctl debug.camqtest.run=4", "code_hash": "6a5ffd59641523361395cdc99059d226b3b77080cd5aee05699e33629e792358", "notes": "DIAGNOSTIC is off even in INVARIANTS kernels -- CAMs own capacity invariants have zero enforcement in production. Live xpt-path attempts: 4 choreographies (delay jam, hold knob, removal storm, kill-9 close) -- removal completed but the free-while-parked window did not close observably; two dd readers wedged uninterruptibly on held-back bios (guest reset performed afterwards). Cross-file: recommend xpt_remove_periph dequeue-or-refuse + camperiphfree dequeue (the cam_xpt-side owner).", "recommended_fix": "camq_resize: unconditionally refuse new_size < queue->entries (return CAM_RESRC_UNAVAIL); pair with an xpt_remove_periph dequeue/guard so drvq capacity tracks real periph counts", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied to guest /usr/src, X86_64_GENERIC rebuilt (fix_build.log: Kernel build completed), installed, rebooted (kernel #1 12:03:51). Exact PoC re-run on the patched kernel: T2/T3 all 64 canaries intact (baseline: canary corrupted), T1 entries==array_size==4 (baseline: 4>3), and the T4 1->0 panic path is structurally unreachable (resize returns before kmalloc/bcopy). Baseline corruption GONE.", "fix_evidence": "fix_run.log (patched T1-T3 output) vs run.log (baseline); fix_build.log; VERDICT.md Fix validation section" } |