# DF-5550 VERDICT — camq_resize shrink-below-entries heap OOB write

## Status: reproduced (deterministic, production primitives) + live xpt-path crash

## Root cause (path:line)

`sys/bus/cam/cam_queue.c:111-138` — `camq_resize()`:

```c
#ifdef DIAGNOSTIC
	if (new_size < queue->entries)
		panic("camq_resize: New queue size can't accommodate "
		      "queued entries.");
#endif
	new_array = kmalloc(new_size * sizeof(cam_pinfo *), M_CAMQ, M_INTWAIT|M_ZERO);
	...
	bcopy(queue->queue_array, new_array, queue->entries * sizeof(cam_pinfo *));
```

The precondition is enforced only under `#ifdef DIAGNOSTIC`.  `DIAGNOSTIC`
is absent from stock kernels — including INVARIANTS builds like the audit
guest's X86_64_GENERIC (which sets INVARIANTS at config line 56 but has no
DIAGNOSTIC option; verified on-guest).  On every such kernel a caller that
shrinks below `entries` gets:

1. an OOB heap write — `bcopy` copies `entries*8` bytes into a
   `new_size*8` allocation;
2. `queue->entries > queue->array_size` persisting afterward, so every
   later camq op (insert/remove/heap_up/heap_down/CAMQ_GET_HEAD) indexes
   past the array.

## In-tree trigger — traced, and crashed live

`camperiphfree()` (cam_periph.c:573-602) frees a periph **without
dequeueing it from `device->drvq`**; nothing in the invalidate path does
(the only drvq removals are xpt_run_dev_allocq cam_xpt.c:3740 and the
bus-dereg drain cam_xpt.c:4416).  `xpt_remove_periph()` (cam_xpt.c:1613)
then calls `camq_resize(&device->drvq, array_size - 1)` with **no
queued-check** — unlike `xpt_release_device()`, which panic-guards the
devq queues for exactly this class (cam_xpt.c:5210-5214).  A periph dying
(AC_LOST_DEVICE: surprise removal / bus deregister) while it (or all its
siblings) are scheduled on drvq — the normal state of a busy device whose
queue depth is exhausted — trips the shrink with `entries == array_size`.

Observed live on the audit guest (stock kernel, no DIAGNOSTIC):

* **Harness (deterministic, production in-kernel camq_* symbols):**
  * T1: `camq_resize(&q,3)` with entries=4 → `entries=4 array_size=3`,
    no panic, slot-4 OOB read returns the pointer that the bcopy wrote
    past the buffer.
  * T2: resize(6→4) → **canary chunk adjacent to the new M_CAMQ array
    overwritten** (`CORRUPTED at +0: e0 (was a5)`) — heap OOB write
    proven, silent.
  * T4: resize(1→0) → `kmalloc(0)` → NULL → bcopy faults: `Fatal trap 12
    ... memmove() at memmove+0x9d` (panic.t4.baseline.txt) — a stock
    kernel crash straight inside camq_resize's bcopy.
* **Live xpt path:** see panic.live.txt (if present) for the
  xpt_remove_periph-originated crash from the ptsim2 rig; the
  choreography (hold openings → removal storm → last-close free of a
  parked periph) is run-live.sh.

## Connection to the DF-5462 lead (cross-file)

The DF-5462 rig's mass-attach panic
`camq_remove+0x35: movl $0xffffffff,0x8(%r12)` (removed_entry == NULL,
bounds check at cam_queue.c:174 passed, `entries == index` so lines
177-181 skipped) is the predicted aftermath of this corruption: after an
unguarded shrink, `queue_array[entries]` lives past the array (or in the
zeroed tail of the fresh M_ZERO allocation) and reads as NULL/garbage.
Note the DF-5462 rig itself entered the scan path without the sim lock
(ptsim.c:264-285), which widened the race windows; the underlying
camq_resize defect is reachable through the locked path traced above.

## Exploitability assessment

Primitive: attacker-influenced 8-byte-granular OOB write of kernel heap
pointers past an M_CAMQ allocation, plus a durable queue-corruption state
that converts later queue operations into additional OOB reads/writes and
NULL/garbage derefs.  Trigger: unauthenticated-ish device removal churn
(umass unplug, iscsi target loss, bus deregister) against busy devices —
local DoS reliably; heap-grooming to uid=0 is theoretical (M_CAMQ-sized
neighbor control requires grooming the 24/32-byte class adjacent to a
victim object).  Not pushed to uid=0 in this run; classified as
memory-corruption (memcorrupt bucket) with reliable local DoS.

## Fix validation

* `fix.diff`: unconditional `if (new_size < queue->entries) return
  (CAM_RESRC_UNAVAIL);` in camq_resize (+ insert-side guard, DF-5551).
* Built as X86_64_GENERIC on the guest (fix_build.log), installed,
  rebooted; the exact baseline harness re-run shows the resize refused,
  no canary corruption, entries never exceeds array_size (fix_run.log).
* Baseline vs patched: baseline T2/T3 canary corruption → patched: all
  canaries intact; baseline T1 entries>array_size → patched: state
  untouched; baseline T4/panic path → not reachable post-fix (resize
  returns before any allocation/copy).

## Recommended paired fix (cam_xpt side, out of this file's scope)

`xpt_remove_periph()` should dequeue a still-queued periph (or refuse the
shrink) the way `xpt_release_device()` guards the devq queues
(cam_xpt.c:5210-5214); `camperiphfree()` should dequeue from drvq before
calling it.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
