# DF-5376 — targwrite() forwards the raw user abort_ccb pointer on
# targusermerge() ESRCH: fake-CCB arbitrary kernel write / RIP control

## What
`sys/bus/cam/scsi/scsi_target.c`

- `targusermerge()` (:720-745) translates a daemon XPT_ABORT's
  userland `abort_ccb` pointer to the matching kernel CCB on
  pending_ccb_queue; on no match it sets `status = CAM_PATH_INVALID`
  and returns **ESRCH** (:741-744).
- The inline send path checks only `!= EFAULT`
  (scsi_target.c:626): `if (targusermerge(...) != EFAULT)
  targsendccb(...)` — so the ESRCH result is *ignored* and the CCB is
  sent anyway with the **untranslated user pointer** still in
  cab.abort_ccb.
- `xpt_action(XPT_ABORT)` (cam_xpt.c:3075-3124) dereferences that
  pointer as a kernel union ccb:
  - reads func_code / pinfo.index,
  - `cam_ccbq_remove_ccb(&abort_ccb->ccb_h.path->device->ccbq, ...)` —
    heap + TAILQ surgery through attacker-controlled pointers,
  - writes `abort_ccb->ccb_h.status = CAM_REQ_ABORTED|CAM_DEV_QFRZN`,
  - `xpt_freeze_devq(abort_ccb->ccb_h.path, 1)` —
    `path->device->qfrozen_cnt += 1` at an attacker-chosen address,
  - (index>=0) `xpt_done(abort_ccb)` (cam_xpt.c:4891): links a fake
    `struct cam_sim` onto the kernel-global `cam_simq`, and the cambio
    SWI (`camisr` → `camisr_runqueue`, cam_xpt.c:7386+/7432-7440)
    drains it and calls the attacker-controlled
    `ccb_h.cbfcnp(periph, ccb)` — full kernel RIP control from a
    write(2) syscall.

The guest has no SMAP/KASLR, so the fake CCB tree can live in an
mmap'd user page and every kernel write is observable from userland.

## Reproduce (guest, root)
```
cd /root/targpwn
make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
cc -O0 -g -o poc5376 poc5376.c
kldload ./targsim.ko; sleep 2
./poc5376 a     # clean demo: kernel writes status + qfrozen_cnt into
                # the user-chosen addresses, write() returns success
./poc5376 b     # RIP control: cbfcnp = 0x414141414140 -> trap 12 with
                # that instruction pointer (guest dies)
```
Struct offsets for the fake tree are read from the targsim module's
`hw.targsim.off` sysctl dump (computed from the same kernel headers).

## Expected
- mode a: `fake ccb_h.status` changes to CAM_REQ_ABORTED|CAM_DEV_QFRZN
  and `fake dev->qfrozen_cnt` 0x11110000 → 0x11110001, printed by the
  PoC, exit 0.
- mode b: "Fatal trap 12" with `instruction pointer = 0x414141414140`,
  backtrace camisr_runqueue ← swi_cambio.

## Files
- poc5376.c — fake-CCB builder (modes a/b)
- build.sh / run.sh
- build.log / run.log (mode a) / run.b.log + panic.b.txt (mode b)
- fix.diff — `== 0` instead of `!= EFAULT` on the merge result
