# DF-3812 — isp_add_wwn_entry / isp_del_wwn_entry index FCPARAM(chan) without any channel bounds check; chan is a raw firmware byte

## Files
- `sys/dev/disk/isp/isp_library.c:2391-2398` (`isp_add_wwn_entry`)
- `sys/dev/disk/isp/isp_library.c:2517-2529` (`isp_del_wwn_entry`)
- `sys/dev/disk/isp/isp_library.c:2560-2584` (`isp_del_all_wwn_entries` — adjacent off-by-one `chan > isp->isp_nchan` at :2580 admits `chan == nchan`)

## Build / Run / Expected
Two parts:

1. **Offset-arithmetic demonstrator (runs anywhere, executed on the audit
   guest as an unprivileged user):**
   ```
   cc -Wall -o vpidx vpidx_oob_arith.c && ./vpidx
   ```
   Expected (see `build.log`, executed on DragonFly 6.5-DEVELOPMENT):
   `sizeof(fcparam)` replica lower bound = 69,696 B; `isp_param` is
   `nchan ∈ {1,2}` fcparams (isp_pci.c:824-832), so an `in_vpidx` of e.g. 52
   indexes 3.55 MB past the block and 255 indexes 17.77 MB past it.
2. **In-vivo trigger (NOT executed — the guest has no QLogic HBA):**
   target-mode ISP FC adapter with MULTI-ID firmware receives an ELS
   (PLOGI/PRLI) whose firmware-generated `IN24XX_ELS_RCVD` notify IOCB carries
   `in_vpidx >= isp_nchan`; the driver then reads/writes kernel heap far
   outside `isp_param` (list under "Impact").

## Root cause chain
```c
/* isp_freebsd.c:2626-2627 */  int chan = ISP_GET_VPIDX(isp, inot->in_vpidx);
/* ispmbox.h:861-862 */        #define ISP_GET_VPIDX(isp, tag) \
                                  (ISP_CAP_MULTI_ID(isp) ? tag : 0)   /* raw 8-bit fw field */
/* isp_freebsd.c:2677 */       isp_add_wwn_entry(isp, chan, wwn, nphdl, portid);
/* isp_library.c:2398 */       fcp = FCPARAM(isp, chan);
/* ispvar.h:644 */             #define FCPARAM(isp, chan) (&((fcparam *)(isp)->isp_param)[(chan)])
```
Every sibling in the same library validates first — `isp_find_pdb_by_wwn`
(isp_library.c:2304 `if (chan < isp->isp_nchan)`), `isp_find_pdb_by_loopid`
(:2327), `isp_find_pdb_by_sid` (:2350), `isp_fc_change_role` (:512) — but
`isp_add_wwn_entry`/`isp_del_wwn_entry` dereference `FCPARAM(isp, chan)`
unvalidated. `isp_del_all_wwn_entries` checks `chan > isp->isp_nchan` (:2580)
instead of `>=`, admitting `chan == nchan` (one-past-end fcparam).

Through the OOB `fcp` the function then performs, on attacker/firmware-
influenced data:
- reads `fcp->isp_tgt_map[nphdl]` (isp_library.c:2407; `nphdl` is the firmware
  16-bit N-port handle, bounded only against MAX_NPORT_HANDLE=2048 *relative
  to the already-OOB pointer*);
- writes `fcp->isp_tgt_map[nphdl] = i + 1` (:2497);
- writes `lp->target_mode = 1`, `lp->handle = nphdl`, `lp->portid = s_id`,
  `lp->port_wwn = ini` (:2491-2496) into `fcp->portdb[...]` — i.e. an 8-byte
  remote-controlled WWN plus 32-bit port ID written at
  `chan * sizeof(fcparam) + <portdb offset> + entry*fcportdb`;
- dispatches `isp_async(NT_ARRIVED)` with the OOB-derived fields (:2510).

## Threat model
Same trust boundary as previously accepted DF-2471 (card-reported channel in
isp.c async handler) and DF-3582/3583 (card-controlled fields): the field is
produced by the adapter firmware/DMA, i.e. a compromised or buggy HBA (or PCIe
DMA attacker) fully controls it; a remote FC peer controls the ELS traffic that
generates the notification but normally only sees its own valid VP index.
Legit firmware reports only configured VPs, which is why this is Medium, not
High: it is a hardening hole in the kernel's trust of the card, with a
multi-field OOB write primitive behind it.

## Impact
Kernel heap OOB read + OOB write of remote-controlled data (WWN/portid), offset
controlled in units of ~68 KB up to ~17.8 MB past a 68–139 KB `kmalloc` block —
arbitrary heap corruption on target-mode FC with MULTI-ID firmware. Guest has
no QLogic hardware, so the driver path could not be exercised: classification
`inconclusive` (source-certain, untriggerable here).
