# DF-4053 — virtio-scsi sense memcpy dst/src swapped (DFly-only port bug)

File: `sys/dev/virtual/virtio/scsi/virtio_scsi.c:1392-1394` — pass 2 (GLM 5.3).
Device-driven threat model (QEMU host process / malicious virtio-scsi backend).

## THE BUG

DragonFly (imported from FreeBSD r311305 but hand-adjusted):

```c
bzero(&csio->sense_data, sizeof(csio->sense_data));
memcpy(cmd_resp->sense, &csio->sense_data,        /* dst/src SWAPPED */
    csio->sense_len - csio->sense_resid);
```

FreeBSD (all versions since import, verified against release/11.4.0):

```c
memcpy(&csio->sense_data, cmd_resp->sense,
    csio->sense_len - csio->sense_resid);
```

The copy direction is inverted in the DragonFly port: the driver copies
FROM the just-bzero()ed CCB sense buffer INTO the device response buffer
instead of delivering the device's sense data to CAM.

Two consequences:

1. **Functional (fires on every CHECK CONDITION, any device):** autosense
   is never delivered. CAM gets `CAM_AUTOSNS_VALID` over an all-zero
   sense buffer (`Sense Error Code 0x0`), breaking all sense-driven error
   recovery in da/cd/sa/pass (media-change detection, retry policy,
   error reporting). Proven live on the audit guest — see below.

2. **Memory-safety (device + pass CCB with sense_len > 96):** the copy
   length is `csio->sense_len - csio->sense_resid`:
   * device sense_len >= ccb sense_len  -> length = csio->sense_len
   * device sense_len <  ccb sense_len  -> length = device sense_len
   i.e. `min(device_sense_len, ccb_sense_len)`, fully device-influenced.
   The *destination* `cmd_resp->sense` is `uint8_t sense[96]` inside
   `req->vsr_uresp.cmd` (`struct vtscsi_request`, virtio_scsi.h:59),
   ~12 bytes into the union. Kernel-originated CCBs always use
   `sense_len = SSD_FULL_SIZE = 32` (sys/bus/cam/scsi/scsi_all.h:953;
   struct scsi_sense_data is 32 bytes on DragonFly), so the normal path
   stays in bounds. But pass(4) `CAMIOCOMMAND` merges the user-supplied
   union ccb wholesale (`xpt_merge_ccb`, scsi_pass.c:546 -> bcopy of
   everything after ccb_h) with **no validation of `sense_len`**
   (u_int8_t, up to 255). A CCB with sense_len=255 from a pass(4) user
   plus a device returning sense_len >= 97 yields a copy of up to 255
   bytes into the 96-byte field: ~159 bytes past it, smashing
   `req->vsr_callout` (callout func/arg pointers) and `req->vsr_link`
   (TAILQ next/prev), and ~80 bytes past the end of the
   `contigmalloc`d `struct vtscsi_request` (~268 bytes). Write content =
   32 zero bytes then CCB fields (including the 16 fully user-controlled
   `cdb_io.cdb_bytes`). A corrupted `vsr_link` gives a controlled
   `*tqe_prev = tqe_next` write when the request is next dequeued
   (TAILQ_REMOVE in vtscsi_dequeue_request). The memcpy also OOB-READS
   the CCB (up to 255 bytes read from the 32-byte sense_data) — kernel
   heap bytes land inside `vsr_uresp`, though that region is bzero'd on
   re-enqueue (virtio_scsi.c:2213).

   Stock QEMU clamps device sense_len to the negotiated sense_size=96,
   so reproducing the overflow needs a modified/malicious backend —
   exactly the threat model (QEMU host compromise or malicious
   passthrough device) but not possible with an unmodified QEMU 7.2.

## MAKING THE GUEST ATTACKABLE

The audit guest normally boots with virtio-blk only. vm.sh was NOT
modified; a temporary launcher (`dfbsd-qemu/vm-vtscsi.sh`, deleted after
the run) booted the with-src snapshot with:

    -device virtio-scsi-pci,id=scsi0 \
    -drive file=vtscsi-disk.img,if=none,format=raw,id=vtscsi0 \
    -device scsi-hd,drive=vtscsi0

(The root disk needs an explicit `bootindex=0` on a
`-device virtio-blk-pci`, otherwise SeaBIOS hangs on the empty scsi disk.)

virtio_scsi is compiled into X86_64_GENERIC, so vtscsi0+da0+pass0 appear
with no kldload.

## BUILD (on the guest)

    cc -O2 -Wall -o /tmp/sense_test /tmp/sense_test.c -lcam

## RUN (root; /dev/pass0 is root-only)

    /tmp/sense_test 255     # unvalidated pass CCB sense_len=255
    /tmp/sense_test 32      # what every kernel periph uses (SSD_FULL_SIZE)
    camcontrol cmd da0 -v -i 512 - -c "28 00 ff ff ff f0 00 00 01 00"

The command is an out-of-range READ(10) (LBA 0xfffffff0 on a 64 MB disk)
so QEMU's scsi-hd returns CHECK CONDITION with a known sense
(ILLEGAL REQUEST, ASC 0x21 "LOGICAL BLOCK ADDRESS OUT OF RANGE").

## EXPECTED

* **Baseline (buggy) kernel** — `CAM_AUTOSNS_VALID` +
  `scsi_status=0x02` + `sense_resid` proves the device returned exactly
  18 sense bytes (255-237 and 32-14), **but sense_data is all 0x00**
  (`Sense Error Code 0x0` from camcontrol). Reproduced 100% of runs.
* **Patched kernel (fix.diff: copy direction restored + MIN() clamp):**
  sense_data becomes `70 00 05 ... 21 00 ...` — real error code 0x70,
  sense key 0x5 ILLEGAL REQUEST, ASC 0x21. Fix validated by rebuild +
  reboot on this guest (fix_build.log / run.patched.log).
