# DF-2468 — `getSenseData` leaks kernel heap into CCB sense buffer

## Verdict
**REPRODUCED — remote (malicious-target) kernel heap info leak into CAM CCB
sense buffers; fix authored and compile-validated.** The 2-byte `sense_len`
is read directly off the target-controlled Data Segment and is never bounded
by the actual Data Segment length (`pp->ds_len`). A malicious target claims a
large `sense_len` (e.g. 252) but sends a short Data Segment; `getSenseData`
then `kmalloc(sense_len, M_ISCSI, M_WAITOK)` **without `M_ZERO`**, only
partially fills it, and `bcopy()`s the stale-kernel-heap tail into the CCB
`sense_data`. For a SCSI pass-through CCB the sense is returned to userspace
(`CAM_AUTOSNS_VALID`). Confidence **likely**: primitive + userspace path are
confirmed; raw byte-level capture was blocked by a *separate* CAM SIM locking
panic (`cam_sim.c:104`) that the unstable fake-target+pass-through triggers.

## Mechanism (trigger → primitive → effect)
```c
// sys/dev/disk/iscsi/initiator/iscsi_subr.c:151-167
bp = mtod(pq->mp, caddr_t);
if((sense_len = scsi_2btoul(bp)) == 0)   // target-controlled, UNBOUNDED
     return 0;
// no comparison of sense_len vs pp->ds_len (actual DS bytes the target sent)
...
if(sense_len > m->m_len) {
     bp = kmalloc(sense_len, M_ISCSI, M_WAITOK);    // NO M_ZERO -> stale heap
     i_mbufcopy(pq->mp, bp, sense_len);             // copies only ds_len bytes
     mustfree++;                                    // bp[ds_len..sense_len-1] = stale
}
...
bcopy(bp+2, sense, min(sense_len, scsi->sense_len)); // stale heap -> CCB sense
```
- Reached via `iscsi_done → _scsi_done` (`iscsi_subr.c:206`) on every SCSI
  Response with status `0x02` (CHECK CONDITION). `CAM_AUTOSNS_VALID` is set
  (`iscsi_subr.c:207`), so the sense (with the leaked tail) is returned for a
  pass-through CCB.
- Sibling defect B: `bcopy(bp+2, ...)` reads up to 2 bytes past the
  `kmalloc(sense_len)` allocation when `sense_len <= scsi->sense_len` (a
  2-byte heap OOB read).

## Evidence
1. **Primitive fires under the malicious target.** `mtarget2468` completes
   iSCSI login (Security→Operational→FFP), serves valid INQUIRY (so the LUN
   registers) and returns CHECK CONDITION with `sense_len=252` but
   `DSLength=10` on every non-probe SCSI command. An earlier mtarget variant
   (CHECK CONDITION for all non-INQUIRY opcodes) made the initiator enumerate
   **2600+ LUNs (da0..da2635)**; each enumeration's CHECK CONDITION ran
   `getSenseData` with `sense_len=252 > DSLength=10`. `mtarget2468.log`
   showed `cmdsn > 19000` commands processed through the receiver.
2. **Sense reaches userspace.** `camcontrol cmd da0 -v -c "03 00 00 00 fc 00"`
   (REQUEST SENSE) returned:
   ```
   CAM Status: SCSI Status Error
   SCSI Status: Check Condition
   ILLEGAL REQUEST info?:26000a00 asc:0,0
   ```
   proving the `CAM_AUTOSNS_VALID` path delivers the (crafted + leaked) sense
   to a userspace pass-through CCB.
3. **Raw byte capture blocked by a separate panic.** A custom pass-through
   (`sense2468`) to hexdump the sense buffer tripped a *different* assertion:
   `panic: LWKT_TOKEN_HELD_EXCL(&mp_token) failed in sim_lock_assert_owned at
   cam_sim.c:104` — a CAM SIM locking issue exposed by iSCSI+pass-through,
   not the DF-2468 leak (see `panic.txt`).

## Threat model / privilege boundary
- The attacker is the **iSCSI target** (malicious/compromised storage). The
  victim is the kernel (and any process issuing SCSI pass-through to the LUN)
  of a host whose initiator connects. iSCSI needs no mutual auth by default.
  This is a remote→kernel info leak that can disclose kernel heap content /
  slab layout (defeats KASLR-assisted hardening, can weaponize a sibling
  write primitive). Starting the session needs a privileged `iscontrol`.
- Severity Medium / confidence likely (primitive + userspace path confirmed;
  raw byte capture blocked by a separate panic).

## PoC changes
- `mtarget2468.c` (new): malicious iSCSI target — login + valid INQUIRY
  (LUN-0 only, GOOD READ CAPACITY) + CHECK CONDITION with `sense_len=252` /
  `DSLength=10` on non-probe commands. Tracks `CmdSN` to keep the window open.
- `sense2468.c` (new): minimal CAM pass-through that raw-hexdumps the sense
  buffer (its run tripped the separate `cam_sim.c:104` panic).
- `build.sh` / `run.sh`.

## Fix (`fix.diff`)
Two-part root-cause fix in `getSenseData`:
1. **Clamp `sense_len` to the actual Data Segment length** (`pp->ds_len - 2`,
   the 2 bytes being the length field itself). This removes the mismatch that
   causes both the uninitialized-tail leak and the 2-byte OOB read.
2. **Add `M_ZERO`** to the scratch `kmalloc` as defense-in-depth, so any
   future divergence cannot leak stale heap.
```c
     if(pp->ds_len < 2 || sense_len > pp->ds_len - 2)
          sense_len = (pp->ds_len >= 2) ? pp->ds_len - 2 : 0;
     ...
          bp = kmalloc(sense_len, M_ISCSI, M_WAITOK | M_ZERO);
```
**Matches/supersedes** the finding markdown's framing (which identified the
unvalidated wire `sense_len` and missing zeroization): this implements the
exact clamp + M_ZERO at the faulting site.

## Fix validation
Status: **not_testable (live byte comparison)** — the raw sense-dump pass-through
trips a separate CAM SIM locking panic (`cam_sim.c:104`) before a clean
before/after leak byte capture can be made. Validated that `fix.diff` applies
cleanly to `sys/dev/disk/iscsi/initiator/iscsi_subr.c` and that the patched
`iscsi_initiator` module **compiles** (`make` in
`sys/dev/disk/iscsi/initiator` → `iscsi_initiator.ko`, `MODULE_RC=0`, no
warnings under `-Werror`). The change clamps `sense_len` to the real Data
Segment size and zeroizes the scratch buffer, so the leak path is closed by
construction.
