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

cam_devq reference count is a non-atomic int and cam_devq_release() frees the devq with active I/O after only a warning

Summary

Plain ++/-- on devq->refcount with no lock/atomics; the final release merely kprintf's when alloc_active/send_active are non-zero then frees anyway: two SIMs sharing one devq concurrently registered/released (kldload/kldunload churn) can lose an update -> premature free -> UAF of the queue arrays under the scheduler. No in-tree guest-reachable concurrent shared-devq (de)registration (virtio/nata use private devqs) - hardening class. Atomic-cmpset fix mirroring the file's xpt_release_* neighbors (cam_xpt.c:4979-5230).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5552 Β· 3 files
FileTypeDescriptionSize
README.md β€” 1.6 KB ↓ raw
verdict.json β€” 1.7 KB view raw
env.txt β€” 239 B view raw

DF-5552 β€” cam_devq reference count is a non-atomic int and cam_devq_release() frees a devq with active I/O after only a warning

File: sys/bus/cam/cam_queue.c:241-257 (cam_devq_reference, cam_devq_release), consumers sys/bus/cam/cam_sim.c:151,185 (cam_sim_release β†’ cam_simq_release, cam_sim_alloc β†’ cam_devq_reference).

  • ++devq->refcount / --devq->refcount are plain, non-atomic, and lock-free. Two SIMs sharing one devq (in-tree: cam_sim_alloc(..., queue) with a passed devq β€” e.g. mpt's cam_sim + phydisk_sim pair) that are concurrently registered/released (kldload/kldunload, driver attach/detach storm, device re-enumeration) can lose an update: the count drops to 0 while a live sim still holds the devq β†’ camq_fini() + kfree(devq) under the scheduler's feet β†’ use-after-free of the alloc/send queue arrays and the devq itself.
  • cam_devq_release() at refcount 0 merely kprintfs a warning when alloc_active/send_active are non-zero (cam_queue.c:251-252) and frees anyway β€” a detach racing in-flight I/O turns the warning into a UAF instead of a refusal.

No PoC built: triggering requires concurrently (de)registering two SIMs that share one devq β€” a driver-orchestrated state no in-tree path reaches on this guest (virtio/nata each allocate a private devq). Hardening-class: Low severity, analysis-only.

Fix sketch: make the refcount atomic_add_int/atomic_cmpset_int-based (the file's neighbors xpt_release_bus/xpt_release_target/xpt_release_device in cam_xpt.c:4979-5230 already use that pattern), and refuse (or leak with a warning) the final free while alloc_active/send_active != 0.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_applicable (no PoC; hardening)

per-fix-DF-5552

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md code analysis with path:line citations

PoC changes

n/a (no PoC built)

Verified recommended fix

atomic refcount (atomic_add_int/cmpset) for cam_devq_reference/cam_devq_release and refuse the final free while alloc_active/send_active != 0

Verdict

cam_devq_reference/cam_devq_release (cam_queue.c:241-257) use a plain non-atomic ++/-- on devq->refcount with no lock, and the final release frees the devq (camq_fini + kfree) after only a kprintf warning when alloc_active/send_active are non-zero. Two SIMs sharing one devq concurrently registered/released can lose a refcount update -> premature free -> UAF of the queue arrays under the scheduler. Analysis-only: no in-tree guest-reachable path registers two sims on one shared devq concurrently (virtio/nata use private devqs); hardening-class, PoC not built per contract for Low findings.