# DF-5462 — lockless cam_extend array race: ptopen/ptstrategy/ptioctl UAF read vs ptctor growth

## What this demonstrates

`scsi_pt.c` looks its periph up through the shared, completely
unsynchronized `cam_extend` array:

- readers: `ptopen` sys/bus/cam/scsi/scsi_pt.c:153, `ptclose` :189,
  `ptstrategy` :219, `ptioctl` :632 — `cam_extend_get(ptperiphs, unit)`
  with **no lock held**;
- writer: `ptctor` :311-312 — calls `cam_extend_set()` with the sim
  lock **dropped**;
- root cause: `cam_extend_set()` (sys/bus/cam/cam_extend.c:54-78) grows
  the array with kmalloc/bcopy/**kfree(ea->ps)** whenever
  `index >= nelem` (i.e. at every 8th attached unit), while
  `cam_extend_get()` (:81-88) does an unlocked check-then-load of
  `ea->ps[index]`.

A reader that loads `ea->ps` before the swap but dereferences
`ps[index]` after the kfree reads **freed kernel memory** and uses it
as a `struct cam_periph *` (`cam_periph_acquire()` → `periph->refcount++`,
then `periph->softc`, `cam_periph_lock()`...).

All pt entry points are root-gated (`ptopen` :149
`caps_priv_check_self(SYSCAP_RESTRICTEDROOT)`; node 0600 root:operator
`ptctor` :320-322), so this is a privileged-trigger race — but the same
`cam_extend` defect backs `da`/`sa`/`cd`/`ch`/`ses`/`pass` lookups,
where attach churn is ordinary hot-plug.

## Rig

`ptsim.ko` — fake CAM SIM ("ptsim") whose 512 LUN-0 targets answer
INQUIRY as **T_PROCESSOR** so the `pt` driver attaches to them.
Knobs:

- `debug.ptsim.ntargets`  targets answering INQUIRY (rest are absent)
- `debug.ptsim.maxtarget` scan bound reported in XPT_PATH_INQ
- `debug.ptsim.rescan=1`  run XPT_SCAN_BUS
- `debug.ptsim.stamp=1/0` start/stop kernel stamper threads that
  kmalloc/memset(0x41..)/kfree chunks of every size class
  `cam_extend_set` frees (64..4096 B), so a raced reader that lands in
  the freed chunk sees 0x4141414141414141 instead of the old (still
  valid) pointers and faults visibly in `cam_periph_acquire()`.

## Prereq

Guest kernel must contain `device pt` (NOT in X86_64_GENERIC):

    cd /usr/src/sys/config
    cp X86_64_GENERIC EVILPT && printf "device\t\tpt\n" >> EVILPT
    cd /usr/src && make -j6 nativekernel KERNCONF=EVILPT && make installkernel KERNCONF=EVILPT
    reboot

## Reproduce (guest, root)

    ./build.sh          # builds ptsim.ko + race5462
    ./run.sh            # attach pt0..pt7, start 8 racers + stampers,
                        # then ntargets=512 + rescan -> 63 growth windows

## Expected

- Clean miss: `RACER-DONE opens=... other=0`, guest stays up (window
  not hit this boot; the freed chunk usually still holds the old valid
  pointers, so even most *hits* are silent).
- Hit: reader gets a stamped/garbage periph pointer → kernel page fault
  in `cam_periph_acquire` (`periph->refcount++` at 0x41414141...) →
  panic on the serial console (`dfbsd-qemu/boot.log`).
