# DF-5550 — camq_resize() shrink-below-entries: heap OOB write + persistent queue corruption (guard is DIAGNOSTIC-only)

**File:** `sys/bus/cam/cam_queue.c:111-138`
**Severity:** High (kernel heap memory corruption)
**Verdict:** reproduced (deterministic KLD harness on the production kernel + live xpt call-path crash)

## What is broken

`camq_resize()` copies `queue->entries` pointers into a `new_size`-pointer
`kmalloc()` buffer and then sets `array_size = new_size`.  The only
protection against `new_size < queue->entries` is a panic guarded by
`#ifdef DIAGNOSTIC` (cam_queue.c:116-120) — **DIAGNOSTIC is not set in
stock kernels (not even in INVARIANTS builds)**, so on every production
kernel:

1. `bcopy(old, new, entries * 8)` writes `(entries - new_size) * 8` bytes
   **past the new M_CAMQ allocation** (heap OOB write of kernel heap
   pointers), and
2. the queue is left with `entries > array_size`, so **every subsequent
   camq_insert / camq_remove / heap_up / heap_down on that queue reads
   and writes past the array**, until it faults or silently corrupts
   adjacent heap.

## In-tree trigger (code trace)

`camperiphfree()` (cam_periph.c:573-602) frees a periph **without ever
dequeueing it from `device->drvq`** — nothing in the invalidate/free path
does (`xpt_schedule` inserts at cam_xpt.c:3636; the only drvq removals are
xpt_run_dev_allocq:3740 and the bus-deregister drain:4416).  It then calls
`xpt_remove_periph()` (cam_xpt.c:1602-1617), which does
`camq_resize(&device->drvq, array_size - 1)` with **no check that the dying
periph (or any sibling) is not still queued** — in contrast,
`xpt_release_device()` *does* panic-guard the devq queues
(cam_xpt.c:5210-5214) for exactly this class.  When all of the device's
periphs are scheduled (`entries == array_size`, the normal state of a busy
device whose queue depth is exhausted — periphs sit on drvq waiting for
alloc openings), the shrink runs with `new_size < entries`:

* single-periph geometry: resize to `new_size == 0` → `kmalloc(0)` returns
  NULL → the 8-byte bcopy faults in `memmove()` (observed live: baseline
  panic `Fatal trap 12 ... memmove() at memmove+0x9d`, stack
  `camqtest_sysctl → memmove`, i.e. the camq_resize bcopy);
* multi-entry geometry: silent pointer-sized heap overflow (canary-proven,
  harness T2) and a lingering `entries > array_size` state whose later
  consumption produces the **exact DF-5462 lead panic signature**
  (`camq_remove+0x35: movl $0xffffffff,0x8(%r12)` — removed_entry == NULL
  because the slot it indexes is past the shrunken array / zeroed fresh
  allocation).

Reachability: a periph must die (AC_LOST_DEVICE — device removal, bus
deregister, transport reset gone-wrong, iscsi/umass unplug) while it is
scheduled on its device's drvq.  That is a hot-unplug-of-busy-device
condition; the periph need not be open.

## Evidence in this pack

* `camqtest.c` — KLD harness driving the **production, in-kernel camq_***
  primitives (no recompiled copies): T1 state proof, T2 canary OOB-write
  proof, T3 (DF-5551 insert-full), T4 deliberate panic.
* `ptsim2.c` — contract-clean fake CAM SIM (all entry points hold the sim
  lock, unlike the DF-5462 rig) with delayed completions and a `hold`
  knob, used for the live xpt-path attempt.
* `ptchurn.c` — userland churner.
* `build.sh` / `run.sh` — exact commands.
* `run.log` — baseline T1/T2/T3 output on the stock kernel.
* `panic.t4.baseline.txt` — stock-kernel panic in the camq_resize bcopy
  (Fatal trap 12, memmove+0x9d).
* `panic.live.txt` — live xpt-path crash (xpt_remove_periph → camq_resize)
  if captured.
* `fix.diff` — unconditional `new_size < entries` refusal (+ insert-side
  guard, DF-5551).
* `fix_build.log`, `fix_run.log` — patched-kernel build + re-run showing
  the corruption is gone.

## Build

```
sh build.sh        # in guest: builds camqtest.ko, ptsim2.ko, ptchurn
```

## Run (baseline, stock kernel)

```
kldload ./camqtest/camqtest.ko
sysctl debug.camqtest.run=1     # T1: state proof (entries > array_size, no guard)
sysctl debug.camqtest.run=2     # T2: canary heap-OOB-write proof
sysctl debug.camqtest.run=3     # T3: insert-full OOB (DF-5551)
sysctl debug.camqtest.run=4     # T4: deliberate panic (camq_resize bcopy fault)
```

Success criterion (baseline): T2/T3 print
`N CANARY(IES) OVERWRITTEN -> heap OOB WRITE proven`; T1 shows
`entries=4 array_size=3`; T4 panics in `memmove` from the resize path.

## Run (live xpt path)

```
kldload ./ptsim2/ptsim2.ko          # da0..da7 appear on ptsim20
sysctl debug.ptsim2.delay=5
# start many parallel readers on /dev/da1../dev/da7 (see run.sh)
sysctl debug.ptsim2.hold=1          # openings never return; periphs park on drvq
sysctl debug.ptsim2.ntargets=0      # targets gone
sysctl debug.ptsim2.rescan=1        # AC_LOST_DEVICE storm
# wait for "removing device entry", then kill the readers:
pkill dd                            # last close frees INVALID da periphs
                                     # -> camperiphfree -> xpt_remove_periph
                                     # -> camq_resize shrink-below-entries
```

Expected on unfixed kernel: Fatal trap 12 in `memmove()` with stack
`camq_remove/xpt_remove_periph → camq_resize → memmove` (see
panic.live.txt), or silent drvq corruption.  On the fixed kernel: no
fault; `camq_resize` returns CAM_RESRC_UNAVAIL and the queue is untouched.
