β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5594

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5594 Β· 4 files
FileTypeDescriptionSize
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_reference is called solely from cam_sim_alloc, cam_sim.c:185) plus the initial 1 from cam_devq_alloc. Devices (struct cam_ed) are inserted into devq->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) calls cam_simq_release(sim->devq) β†’ cam_devq_release() (cam_queue.c:247-257) β†’ refcount 0 β†’ kfree(devq) β€” while cam_ed allocation slots may still be queued in its heap arrays. The release-side warning checks only alloc_active/send_active counters, never alloc_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 queued cam_ed_qinfo entries behind; the subsequent detach then frees the devq under them.
  • Downstream: the queued devices' alloc_ccb_entry.pinfo.index / send_ccb_entry.pinfo.index remain 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 live cam_ed objects β€” latent UAF surface, currently fenced off only by the bus->sim == &cam_dead_sim retarget (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_testable
baseline no→ patch + rebuild →patched clean

not validated (no dynamic repro in this run)

per-fix-DF-5594

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).