DF-4369 — annotated static proof ================================ [All citations hpt27xx_osm_bsd.c unless noted] ARM PATH (read/write CCB to hardware) hpt_action :770 XPT_SCSI_IO hpt_scsi_io :658 pCmd = ldm_alloc_cmds(...) :708-715 ext = cmdext_get(vbus_ext); pCmd->priv = ext; pCmd->done = os_cmddone; pCmd->psg = ext->psg; SG path :730-731 callout_reset(ccb->ccb_h.timeout_ch, HPT_OSM_TIMEOUT, hpt_timeout, pCmd); ldm_queue_cmd(pCmd); -- or -- busdma :737-742 bus_dmamap_load(..., hpt_io_dmamap_callback, pCmd, ...) callback :530-531 callout_reset(..., hpt_timeout, pCmd); ldm_queue_cmd; HPT_OSM_TIMEOUT = 20*hz [os_bsd.h:190] PCOMMAND.priv is a plain field (not freelist link) [him.h:417] OS_CMDEXT.vbus_ext only ever written at init [:1061] COMPLETION PATH (interrupt / task, vbus lock held via hpt_pci_intr :836 / hpt_do_tasks :386) os_cmddone :421 :428 callout_stop(ccb->ccb_h.timeout_ch) :461 bus_dmamap_unload(...) :463 cmdext_put(ext) <- ext back on cmdext_list :464 ldm_free_cmds(pCmd) <- pCmd back on LDM freelist :465 xpt_done(ccb) callout_stop() contract: "Stops a callout if it is pending or queued, does not block. This function does not interlock against a callout that is in-progress." [sys/kern/kern_timeout.c:1087-1088] TIMEOUT PATH (softclock, NO lock) hpt_timeout :411 :414 ext = (POS_CMDEXT)pCmd->priv <- UAF read after :464 :418 ldm_reset_vbus(ext->vbus_ext->vbus) RECYCLERS that change priv semantics hpt_flush_vdev :303 pCmd->priv = &done (kernel STACK, hpt_stop_tasks/ hpt_shutdown_vbus callers) ldm internal commands (rebuild/verify, binary blob) — priv per blob => interleaving CPU0: os_cmddone enters CPU1: deadline expires, CPU0: callout_stop (no-op vs CPU1: hpt_timeout starts in-progress callout) CPU0: ldm_free_cmds(pCmd) CPU0: alloc thread recycles pCmd CPU1: reads pCmd->priv -> &done (stack) / NULL / new ext CPU1: ext->vbus_ext -> stack garbage / NULL-page / valid CPU1: ldm_reset_vbus(wild) -> fatal trap Outcome distribution: mostly benign spurious reset (freelist memory intact); panic when the command block is recycled within the window. DoS only — no write primitive. ======================================================================== APPENDIX — candidates examined and killed in this pass (negative results) ======================================================================== 1. hpt_get_logical_devices count shuffle OOB (hpt27xx_osm_bsd.c:916-928, caller hpt_stop_tasks :971-980, DEVICEID ids[32]) Feared: ldm writes TOTAL device count into pIds[0]; shuffle loop `for (i=0;i31 logical devices exist (osm_max_targets = 0xff, osm.h/config). DISASSEMBLY of the blob (x86_64-elf.hpt27xx_lib.o.uu, ioctl table at .data+0x1b40, entry 0xff04 -> handler .text 0x7f1c0, worker 0x77330): the worker writes at most `maxcount` ids and RETURNS min(total, maxcount) (loop at 0x773d3: `cmp %esi,%r10d; je ret`), and the size validator at 0x7f190 requires nOutBufferSize >= count*4+4. pIds[0] <= 31 => shuffle and the `for (i=0;iccb = ccb would NULL-deref at :710) Cannot happen through CAM: devq openings (cam_simq_alloc(os_max_queue_comm) :1071) == cmdext pool size (loop :1055-1069) == os_max_queue_comm, and os_cmddone returns the ext (:463) BEFORE xpt_done (:465) releases the opening, so in-flight CCBs (and hence ext demand) never exceed the pool. 3. Flush/ioctl lost-wakeup infinite loops (:307-311, :878-883) Safe: `done`/`ioctl_cmnd` are checked while the caller holds the vbus lock; completions run under the same lock (intr :836); hpt_sleep = lksleep drops the lock atomically at sleep-enqueue (os_bsd.h:198-201). No window between check and sleep. 4. KdPrint OOB read of short CDBs (:548-552 reads cdb[0..11] regardless of cdb_len) — KdPrint(x) is `do{}while(0)` in release (osm.h:233): args never evaluated. Debug-build-only cosmetic. 5. hpt_ioctl unbounded kmalloc(nInBufferSize/nOutBufferSize) (:1281,:1290) — device node is root-only 0600 (make_dev UID_ROOT/S_IRUSR|S_IWUSR :1143-1144); root self-DoS, not a finding. 6. hpt_ioctl dereferences the user HPT_IOCTL_PARAM directly (piop->Magic :1265) instead of copyin — unmapped a_data => kernel page fault; again root-only (0600 node), not a finding. 7. SG path treats non-PHYS (virtual) segment lists as bus addresses (:717-728 applies for CAM_SCATTER_VALID without CAM_SG_LIST_PHYS too) => DMA to user-chosen numerical "physical" addresses; reachable only through crafted CAM CCBs (root via /dev/xpt), and DF-1563 already covers this SG path's lack of validation. Not re-reported. 8. Shutdown worker-task UAF (TASK_ENQUEUE'd worker racing kfree(vbus_ext) :336-364, no taskqueue drain) — single-shot at poweroff, ta_context=NULL mitigation covers the common case; noted, not actionable. 9. dmapool underflow at hpt_free_mem (:211-215, HPT_ASSERT(p) no-op => free_pages(NULL)) — requires hpt_alloc_mem partial failure which returns before shutdown_eh registration, making hpt_shutdown_vbus unreachable for that vbus. Dead path.