# DF-4557 — atapi-cam unbounded CAM target_id → `softc->atadev[tid]` OOB read → wild-pointer deref (kernel panic)

## File
`sys/dev/disk/nata/atapi-cam.c` — `atapi_action()` XPT_SCSI_IO (:490, :615, :618) and
XPT_PATH_INQ (:385, :391).

## Root cause
`struct atapi_xpt_softc.atadev` is a 2-element array (`atadev[2]`, atapi-cam.c:73).
CAM target ids routed to the SIM are **not** bounded by the SIM's advertised
`max_target = 1` (atapi-cam.c:367): `xpt_compile_path()` (sys/bus/cam/cam_xpt.c:3993)
allocates a `cam_et` for *any* target id a CCB path names — including 2..15.
`atapi_action()` then uses the raw id as an array index:

* XPT_PATH_INQ (atapi-cam.c:385,391): `softc->atadev[tid]` — OOB read, NULL-checked
  but not bounds-checked; a non-NULL OOB qword is dereferenced (`->mode`).
* XPT_SCSI_IO (atapi-cam.c:490,615,618): `softc->atadev[tid]` — same OOB read; if
  the OOB qword is non-NULL, `->mode` is read and `->dev` is loaded and handed to
  `ata_queue_request()` → wild-pointer deref.

`state_lock` (a `struct lock`, sys/sys/lock.h:67-73) immediately follows `atadev[2]`
in `struct atapi_xpt_softc` (atapi-cam.c:72-74). `struct lock` field offsets:
`lk_flags|lk_timo`@0, `lk_count`@8, `lk_wmesg`@16, **`lk_lockholder`@24**.
Hence:

* tid=2 → lk_flags|lk_timo (0 → benign DEV_NOT_THERE)
* tid=3 → lk_count (0 when uncontended → benign)
* tid=4 → lk_wmesg ("atapicamattach" rodata ptr → non-NULL → garbage `->dev`
  = ASCII "atapicam" → wild deref → same crash class)
* tid=5 → **`lk_lockholder`** — and in the XPT_SCSI_IO path `state_lock` is HELD
  (acquired at :482) by the time :490 reads the slot, so `lk_lockholder` ==
  curthread — **non-NULL by construction** → passes the `== NULL` check →
  `atadev[5]->dev` = first qword of `struct thread` = run-queue link, NULL for a
  running thread → `request->dev = NULL` → `ata_queue_request()` →
  `device_get_parent(NULL)` → supervisor read fault at VA 0x28. This is exactly
  the signature observed in panic.txt.

## Reachability
`/dev/xpt0` is `crw------- root operator` on the guest → **root-only** trigger
(same privilege gate as the known DF-1695/DF-2501 family, which cover only
XPT_RESET_DEV). The XPT core's own traffic is bounded by `max_target`, so this
needs a deliberately crafted path — e.g. the stock base-system command:

    camcontrol rescan 1:5:0

`camcontrol rescan <b:t:l>` issues `XPT_SCAN_LUN` (whitelisted in `xptioctl`,
sys/bus/cam/cam_xpt.c:1052) on the crafted path; `xpt_scan_lun()` first sends
XPT_PATH_INQ (OOB read #1) and then the `probe` periph sends XPT_SCSI_IO INQUIRY
(OOB read #2 → wild deref → panic).

The guest (QEMU PIIX3 IDE, ATAPI DVD on ata1 → scbus1) runs this exact driver
live: `cd0`/`pass0`/`sg0` are the atapicam SIM's devices.

## Build
No build: uses stock `/sbin/camcontrol` + `/dev/xpt0`.

## Run
    ./run.sh          # as root on the DragonFly guest

## Expected result (baseline, unpatched)
Guest panics ("Fatal trap ... page fault in supervisor mode") with a backtrace
ending in the ata layer (ata_queue_request path) — ssh connection dies,
`vm.sh status` → down. Serial console shows the panic text.

With the fix applied (tid bounds-checked): `camcontrol rescan 1:5:0` returns
cleanly, no panic, guest stays up (probe just finds no device).
