# DF-2458 — Target-controlled buffer offset in `scsi_decap` → arbitrary-offset kernel heap write

## Verdict
**REPRODUCED** — a malicious iSCSI target obtains an attacker-controlled
**write-what-where** primitive against the initiator kernel: the target's
Data-In PDU supplies a 32-bit **Buffer Offset** (`rcmd->bo`) that `scsi_decap()`
uses directly as `dp = csio->data_ptr + ntohl(bo)` and then `memcpy()`s the
target's data segment to that address, with **no bounds check**. Demonstrated as
a remote kernel panic (write to an unmapped page); the realistic ceiling is
remote kernel memory corruption / RCE on a host that connects to a malicious
target. Fix **validated** on a rebuilt module.

## Mechanism (trigger → primitive → effect)
1. A privileged user runs `iscontrol` against the malicious target. The kernel
   receiver starts at `ISCSISETSOC` (`iscsi.c:430`), the login exchange
   completes (Security→Operational→FullFeature), CAM probes LUN 0, and the
   initiator issues a SCSI **INQUIRY** (a read, `R=1`, `edtlen` = alloc length).
   The outstanding command is placed on the held list (`isc_sm.c:527-528`).
2. The malicious target replies with a SCSI **Data-In** (`opcode=0x25`) whose
   `itt` matches the INQUIRY and whose **Buffer Offset** `rcmd->bo` is
   attacker-chosen (here `0x80000000`).
3. `ism_recv` → `_read_data` (`isc_sm.c:144`) → `i_search_hld` finds the opq →
   `scsi_decap` (`iscsi_subr.c:531`). The only size guard is
   `ntohl(cmd->edtlen) >= pq->pdu.ds_len` (`iscsi_subr.c:566`) — it never checks
   **placement**:
   ```c
   offset = ntohl(rcmd->bo);          /* iscsi_subr.c:572 -- target-controlled */
   dp = csio->data_ptr + offset;      /* iscsi_subr.c:573 -- NO bounds check   */
   i_mbufcopy(pq->mp, dp, len);       /* iscsi_subr.c:574 -- memcpy to dp      */
   ```
4. `i_mbufcopy` memcpy()s the target's data segment (attacker content, length
   `ds_len`) to `csio->data_ptr + bo`. With `bo = 0x80000000` the destination is
   unmapped → write page-fault → panic. The offset is `int`, so values with the
   sign bit set (≥ `0x80000000`) also yield underflow writes *before* the buffer.

## Evidence (panic signature, unpatched `#0`)
With `bo = 0x80000000` (and identically with `bo = 0x40000000`):
```
panic: assertion "obj != NULL" failed in vm_object_hold_shared at vm_object.c:330
vm_object_hold_shared()
vm_object_hold_shared()
vm_fault()
trap_pfault()
trap()
--- trap 000000000000000c, rip=ffffffff80bcabf6, rbp=... ---
memcpy() at memcpy+0x66
```
and (earlier run, `bo = 0x40000000`):
```
Fatal trap 12: page fault while in kernel mode
fault virtual address  = 0xfffff80157b32860
fault code             = supervisor write data, page not present     <-- WRITE fault
Stopped at      memcpy+0x66:    movq    %rdx,(%rdi)                  <-- i_mbufcopy
```
The `supervisor write data` fault inside `memcpy` (called by `i_mbufcopy` from
`scsi_decap`) at an address = `data_ptr + bo` is the proof that the target's
Buffer Offset is honored with no bounds check and that this is a **write**.
Reproduced by `mtarget2458` (login + malicious Data-In) + `iscontrol` (root,
drives login→FFP→CAM INQUIRY). See `panic.txt`, `mt2458.log`.

## Threat model / privilege boundary
- The attacker is the **iSCSI target** (malicious/compromised storage server).
  The victim is the kernel of any host whose `iscsi_initiator` connects to it.
  iSCSI needs no mutual auth by default (RFC 3720). The bug fires in normal
  full-feature operation (no special setup beyond connecting + a SCSI read
  probe, which CAM issues automatically on LUN discovery). The network→kernel
  boundary is crossed.
- Starting the session needs a privileged `iscontrol` (`/dev/iscsi` is `0600
  root:wheel`), which is the normal operational model.

## Phase 6 — escalation analysis
This **is** a write-capable primitive (write-what-where relative to
`csio->data_ptr`), so Phase 6 applies. What the attacker controls:
- destination offset `bo` (full 32-bit, signed → underflow possible),
- written content (the Data-In data segment bytes),
- length `ds_len` (bounded by the negotiated `MaxRecvDataSegmentLength`).

What limits a clean `uid=0` / RCE chain on this guest:
- The destination is `csio->data_ptr + bo` — a **relative** write. To overwrite
  a specific kernel object (function pointer / `struct ucred *`) the attacker
  must know `data_ptr`'s absolute heap address. KASLR is off, so **kernel
  symbol** addresses are fixed, but `data_ptr` is a dynamic heap allocation
  whose address is not known to a *remote* target without an info leak or
  successful heap grooming (the target can influence slab churn only indirectly,
  via the I/O it serves). The attacker is a **network** peer and has no local
  process on the victim, so the usual "spray/fork/pipe to fix the layout then
  pivot to userspace shellcode" local-escalation recipe is not directly
  applicable; RCE would require (a) a heap info-leak to pin `data_ptr`, then
  (b) a groomed adjacent victim object, then (c) redirecting a function pointer
  to shellcode staged in a kernel mbuf the attacker controls.
- This is therefore reported at its **demonstrated** impact: **remote kernel
  panic / DoS**, with remote-kernel-RCE as the realistic (but not demonstrated
  in this PoC) ceiling. It is correctly rated **Critical** as a remote
  attacker-controlled kernel write primitive; the unpriv→`uid=0` framing does
  not fit (there is no local unprivileged actor — the attacker is the network
  peer, the victim is the kernel).

## PoC changes
- `mtarget2458.c` (new): malicious iSCSI target implementing the Login exchange
  (Security→Operational→FFP, permissive agreements) and replying to the first
  SCSI command with a Data-In carrying an attacker-chosen `bo`.
- `fix.diff` (new): bounds-check `rcmd->bo` in `scsi_decap`.

## Fix (`fix.diff`)
In `scsi_decap` (`iscsi_subr.c:572`), before computing `dp`, require the
placement to lie entirely inside the expected transfer buffer:
`offset >= 0 && offset + len <= edtlen`; otherwise `xdebug` + drop the PDU
(the command stays outstanding for a valid completion). **Supersedes** the
finding markdown's proposal (which identified the unchecked `rcmd->bo`); this
implements the exact bounds check at the faulting site.

A sibling instance of the same unchecked-`bo` pattern exists in `so_recv`'s
`douio` path (`isc_soc.c:452`, `iov->iov_base = csio->data_ptr + ntohl(rcmd->bo)`)
which is reached only when `sp->douio` is enabled (default off); the same
bounds check should be applied there. This fix targets the cited/reproduced
`scsi_decap` path.

## Fix validation (rebuilt single module)
- Baseline (unpatched `#0`): malicious Data-In `bo=0x80000000` →
  `panic: assertion obj!=NULL in vm_object_hold_shared` via `memcpy+0x66`
  (write fault) (panic.txt). Same with `bo=0x40000000`.
- Patched module (sha256 `aadc9875...`): same malicious Data-In → bounds check
  fires, guest stays up, dmesg:
  *"`>>> scsi_decap: 0] bad data-in offset=-2147483648 len=16 edtlen=36 - dropping"`*.
  No panic, no da device (the malicious target never served valid data, as
  expected).
