DF-3661 / trig_devq.c
/* * DF-3661 - shared cam_devq over-release on multi-channel isp(4) adapters. * * Refcount walk (DragonFly CAM): * * cam_devq_alloc() cam_queue.c:228 refcount = 1 (caller's) * cam_sim_alloc(..., devq) cam_sim.c:185 ++refcount (per sim) * cam_sim_free(sim) cam_sim.c:246-250 --refcount when the * last sim ref dies * cam_devq_release() cam_queue.c:248 --refcount; ==0 -> kfree * * isp_attach() allocates ONE devq (isp_freebsd.c:213) and passes the same * pointer to isp_attach_chan() for EVERY channel (isp_freebsd.c:219-223). * Each isp_attach_chan() calls cam_simq_release(devq) (isp_freebsd.c:95) - * dropping "the caller's" reference once PER CHANNEL while the caller only * ever held ONE. * * For isp->isp_nchan == N the live refcount after attach is 1 while N sims * each believe they hold a reference (off by N-1). The moment the FIRST sim * is released (isp_detach's cam_sim_free at :308, or isp_attach's unwind at * :250) the refcount hits zero and cam_devq_release() kfrees the devq * (cam_queue.c:253-255) - while N-1 sims are still registered on CAM buses * and dereference sim->devq on every CCB (xpt_run_devq/alloc paths). * The remaining sims' cam_sim_free() then executes * `--freed_devq->refcount` - a write-after-free - and any I/O between the * two frees is a use-after-free. * * Multi-channel isp(4) instances in this tree: * - IS_DUALBUS SCSI: PCI_QLOGIC_ISP1240 / ISP12160 -> isp_nchan = 2 * (isp_pci.c:732, 749) * - NPIV FC: ISP2422/2432/2532 with the nvports hint -> * isp_nchan += isp_nvports (isp_pci.c:798, 805), up to 126 channels * -> off by up to 125. * * Upstream FreeBSD never releases inside isp_attach_chan; it drops the * single caller reference in isp_attach-unwind / isp_detach after all sims * are gone (verified against FreeBSD HEAD 2026-09-15). * * Trigger on hardware: dual-channel or NPIV card + module unload / devctl * detach / failed attach unwind. Single-channel cards are unaffected * (N=1: alloc+release balance). * * This guest has no QLogic device, so the sequence cannot run here; the * arithmetic is source-certain. Documentation trigger below. */ int main(void) { return 0; } |