# DF-2466 — Heap overflow in `i_send` via ahs_len/ds_len padding mismatch

## Verdict
**REPRODUCED — root-triggerable kernel heap overflow, deterministically
panics the default GENERIC kernel; fix validated on a rebuilt module.**
The ISCSISEND ioctl (`/dev/iscsiN`, `0600 root:wheel`) lets a root process
supply an attacker-chosen `ahs_len`/`ds_len`. `i_prepPDU()` sizes `pq->buf`
rounding `(48+ahs+ds)` to 4, but `i_send()`'s data-segment pad loop rounds
only `ds_len` to 4. When `ahs_len` is not a multiple of 4 the two diverge by
1–3 bytes and `i_send()` writes NUL pad bytes past the allocation. This is a
**root→kernel** corruption (the iSCSI device is root-only), so it is a panic
/ DoS / hardening gap, **not** an unprivileged escalation.

## Mechanism (trigger → primitive → effect)
```
i_prepPDU (isc_sm.c:266-291):  len = 48 + ahs_len + ds_len;  if(len&3) len += 4-(len&3);
                               pq->len = len;                 // total rounded
i_send (iscsi.c:461):          bp = kmalloc(pq->len - 48, ...) // = ahs+ds+prep_pad
i_send (iscsi.c:467-490):      copyin ahs (ahs_len); bp += ahs_len;
                               copyin ds  (ds_len);  bp += ds_len;
                               while(n & 03){ n++; *bp++ = 0; }  // n == ds_len ONLY
```
- `prep_pad = (4 - ((ahs_len+ds_len)&3)) & 3`;  `send_pad = (4 - (ds_len&3)) & 3`.
- Overflow = `send_pad - prep_pad` bytes when positive, i.e. whenever
  `(ahs_len+ds_len)%4 == 0` but `ds_len%4 != 0` (possible only when
  `ahs_len%4 != 0`).
- Concrete (PoC): `ahs_len=7, ds_len=1` → `prep_pad=0`, `send_pad=3`,
  alloc = 8 (exact kmalloc-8 chunk) → 3 NUL bytes written at offsets 8,9,10,
  **3 bytes into the adjacent kmalloc-8 slab chunk**. (Same root cause also
  overflows in `isc_sendPDU` at `isc_soc.c:235`, where `iv->iov_len` is rounded
  on `ds_len` alone and `sosend` reads past `pp->ds`.)

## Evidence (panic, unpatched `#0` + stock module)
`panic: overflowed mbuf 0xfffff80117665a00` — `m_free → m_freem → sbdrop →
tcp_input → Debugger("panic")`. The 3-byte overflow corrupts an adjacent
slab object (the slab zone is shared across malloc types of the same size, so
an M_ISCSI kmalloc-8 chunk sits next to an mbuf's metadata); when TCP later
frees that mbuf, `m_free()`'s INVARIANTS check fires. Reproduced
deterministically on **3 independent fresh `vm.sh reset with-src`** runs
(panic addresses `0x...665a00`, `0x...e0a000`, `0x...d02800`). See `panic.txt`.

## Threat model / privilege boundary
- The trigger is the ISCSISEND ioctl on `/dev/iscsiN` (`0600 root:wheel`,
  confirmed in-guest). So the attacker is a **root** process (or a flawed root
  daemon). Root→kernel corruption is game-over by definition; the realistic
  demonstrated impact is therefore **kernel heap corruption / panic DoS**, not
  unpriv→root. Severity Low is appropriate (root-only, requires the non-default
  iSCSI initiator module, DoS-class).
- The `ahs_len` value is fully attacker-controlled from the ioctl argument; the
  overflow content is NUL bytes (limited control), the offset/size is
  controlled. On a non-INVARIANTS kernel the corruption is silent.

## PoC changes
- `df2466.c` (new): opens `/dev/iscsi`, ISCSISETSES → opens `/dev/iscsiN`,
  connects a socket to a local dummy listener, ISCSISETSOC, then loops
  ISCSISEND with `ahs_len=7, ds_len=1` (3-byte overflow past kmalloc-8).
- `build.sh` / `run.sh`.

## Fix (`fix.diff`)
Root-cause fix: reject a non-multiple-of-4 `ahs_len` at the `i_send` entry
(the iSCSI BHS `AHSLength` field is counted in 4-byte words, so `ahs_len`
must be a multiple of 4). A non-multiple-of-4 `ahs_len` is what makes the
`i_send`/`isc_sendPDU` per-field padding diverge from `i_prepPDU`'s
total-length rounding; rejecting it removes the divergence at the source and
also closes the sibling `isc_sendPDU` read overflow (`isc_soc.c:235`).
```c
     if(pp->ahs_len & 0x3) {
          xdebug("i_send: ahs_len=%d not a multiple of 4", pp->ahs_len);
          error = EINVAL;
          goto out;
     }
```
**Supersedes** a pad-only fix: I first tried making `i_send`'s pad loop round
`(ahs_len+ds_len)` — that closes the write overflow but leaves the
`isc_sendPDU` read overflow (and the malformed BHS `AHSLength`) reachable.
The validation fix is one logical change at the root cause and closes both.

## Fix validation (rebuilt module)
`iscsi_initiator` is a **loadable module**, so the validation rebuilds and
installs the module (`iscsi_initiator.ko`), not the kernel proper:
- **Baseline** (stock `iscsi_initiator.ko`, sha256 `99e1710b...`, kernel `#0`):
  `./df2466 7 1 8000` → `panic: overflowed mbuf` (3/3 fresh-reset runs).
- **Fixed** (`iscsi_initiator.ko` rebuilt with `fix.diff`, sha256
  `977b62dc...`, installed at `/boot/kernel/iscsi_initiator.ko`, rebooted):
  `./df2466 7 1 8000` → every ISCSISEND returns `EINVAL` (`ok=0 fail=8000`),
  **no panic**, guest stays up (`uptime` healthy). `fix_status = fixed`.
