devq lifetime is anchored to the sim refcount, not to the peripherals queued on it β devq freed with cam_ed allocation slots still queued (contradicts cam_sim.c's own comment)
Summary
The comment says the devq 'is still used by individual peripherals... do not destroy it until the sim itself is no longer needed', but the devq refcount counts only sims - devices are queued with no reference: the last-sim-ref destroy runs even when xpt_bus_deregister gave up draining after 5 retries and qinfo entries remain, leaving freed heap arrays holding live cam_ed pointers whose pinfo.index values point into freed memory - tripping the 'Removing device while still queued for ccbs' panic at device release. The release-with-active warning was captured firing live through a real deregistration flow. Trace-verified (precondition needs a stuck queued device at detach).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5594 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| verdict.json | β | 2.7 KB | view raw | |
| env.txt | β | 515 B | view raw | |
| manifest.json | β | 735 B | view raw |
DF-5594 β devq lifetime is anchored to the sim refcount, not to the peripherals queued on it
sys/bus/cam/cam_sim.c pass-2 β cam_sim_release() / cam_simq_release() interplay.
What
The comment above cam_sim_release() (cam_sim.c:236-240) states the design
intent: "the devq is still used by individual peripherals even if the backend
sim disappears, so do not destroy it until the sim itself is no longer
needed." The code does the opposite of the intent:
cam_devq's reference count counts only sims (cam_devq_referenceis called solely fromcam_sim_alloc, cam_sim.c:185) plus the initial 1 fromcam_devq_alloc. Devices (struct cam_ed) are inserted intodevq->alloc_queue/send_queue(xpt_schedule_dev_allocq/xpt_schedule_dev_sendq) without taking any devq reference.- When the last sim reference dies,
cam_sim_release()(cam_sim.c:247-250) callscam_simq_release(sim->devq)βcam_devq_release()(cam_queue.c:247-257) β refcount 0 βkfree(devq)β whilecam_edallocation slots may still be queued in its heap arrays. The release-side warning checks onlyalloc_active/send_activecounters, neveralloc_queue.entries/send_queue.entries. xpt_bus_deregister()tries to drain the devq queues but gives up after 5 retries and proceeds anyway (cam_xpt.c:4439-4446), leaving queuedcam_ed_qinfoentries behind; the subsequent detach then frees the devq under them.- Downstream: the queued devices'
alloc_ccb_entry.pinfo.index/send_ccb_entry.pinfo.indexremain set (pointing into the freed arrays); when the device is finally released,xpt_release_device()hits its guard and panics:panic("Removing device while still queued for ccbs")(cam_xpt.c:5199-5205). Between devq-free and device-release the queue arrays are freed memory containing pointers to livecam_edobjects β latent UAF surface, currently fenced off only by thebus->sim == &cam_dead_simretarget (dead_sim.devq == NULL gates all dispatch).
Corroboration captured live during the DF-5592 rig's detach flow (serial
console): cam_devq_release: WARNING active allocations 8 active send 8! β
the devq dropped with active I/O after a warning, exactly the DF-5552 /
DF-5594 provenance, reached through a real deregistration sequence.
Threat
Storage detach/deregistration with a device stuck queued for ccb allocation
(the DF-5550 scenario: periph dying of AC_LOST_DEVICE while scheduled; frozen
queues) β freed devq with live queue entries β controlled
panic("Removing device while still queued for ccbs") (local DoS at minimum),
plus a heap-UAF window that today has no in-tree walker but no structural
guarantee either.
Status
Code-trace verified (path:line above); not dynamically PoC'd β building the stuck-device precondition requires the DF-5550 corruption state or a frozen alloc queue at detach. Evidence class: trace + the captured warning line.
Fix verification
not_testablenot validated (no dynamic repro in this run)
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md (full path:line trace); DF-5592 panic.txt/run.log (captured 'cam_devq_release: WARNING active allocations 8 active send 8!' during a real deregistration)
PoC changes
no standalone PoC built (trace-only); the DF-5592 rig's deregistration flow supplied the live warning-line corroboration
Verified recommended fix
Have devices hold a devq reference while their alloc/send qinfo entries are queued (drop in xpt_release_device's guarded branch), or make cam_devq_release refuse the final free while alloc_queue.entries/send_queue.entries are non-zero, mirroring xpt_release_device's guard instead of a kprintf
Verdict
Trace-verified only. cam_devq's refcount counts sims alone (cam_devq_reference called only from cam_sim_alloc, cam_sim.c:185); devices are queued into devq->alloc_queue/send_queue without references, and cam_sim_release() (cam_sim.c:247-250) destroys the devq at last-sim-ref even though xpt_bus_deregister() explicitly proceeds after 5 drain retries with qinfo entries still queued (cam_xpt.c:4439-4446). The code contradicts its own comment (cam_sim.c:236-240). Downstream: devices keep pinfo.index pointing into freed arrays -> panic('Removing device while still queued for ccbs') at cam_xpt.c:5199-5205 (local DoS), plus freed devq arrays holding live cam_ed pointers (latent UAF surface, currently unwalked post-retarget because dead_sim.devq==NULL). Live corroboration: the DF-5592 rig's real deregistration flow printed 'cam_devq_release: WARNING active allocations 8 active send 8!' (release with active I/O -- the DF-5552 class firing through cam_sim.c's release ordering). Not dynamically PoC'd: needs a device stuck queued-for-ccbs at detach (DF-5550's corruption state or frozen alloc queue).
No comments yet.