# DF-1161 — mpt_fc_els_reply_handler: unbounded els_cmd_ptrs[] index (source-only verification)

## Verdict: REPRODUCED at source level (latent in compiled-in mpt(4); not triggerable without LSI-Logic MPT FC HBA)

## Mechanism

`mpt(4)` is statically compiled into `X86_64_GENERIC`
(`sys/config/X86_64_GENERIC:93`). The Fiber Channel ELS reply handler runs only
when `mpt->is_fc` is true (set by the FC HBA attachment in `mpt_pci.c`).

When an FC LINK_SERVICE_BUFFER_POST_REPLY reply frame arrives with `MsgLength >
5`, the handler executes:

```c
/* sys/dev/disk/mpt/mpt_cam.c:2951-2952 */
ioindex = le32toh(rp->TransactionContext);     /* U32 from IOC DMA, no check */
req = mpt->els_cmd_ptrs[ioindex];              /* OOB if ioindex >= 64 */
```

- `els_cmd_ptrs` is allocated as `MPT_MAX_ELS * sizeof(request_t *)` =
  **64 entries** (`sys/dev/disk/mpt/mpt_cam.c:4208`, with
  `MPT_MAX_ELS = 64` at `sys/dev/disk/mpt/mpt.h:373`).
- `TransactionContext` is a U32 field of the IOC reply frame that the driver
  receives via DMA. The driver originally posts it from a known-good ioindex
  (`mpt_cam.c:4149: tep->TransactionContext[0] = htole32(ioindex)`), but the
  field is in IOC-owned memory between post and reply.
- There is **no bounds check** on `ioindex` before indexing. If an IOC firmware
  defect or a malicious FC peer (capable of crafting LINK_SERVICE_BUFFER_POST
  replies with `MsgLength > 5` and an arbitrary `TransactionContext`) supplies
  `ioindex >= 64`, the read fetches an attacker-controlled kernel-pointer from
  beyond the array.
- The pointer is then dereferenced as a live `request_t *`: `TAILQ_REMOVE`
  unlink (`mpt_cam.c:2971`, `2985`, `3062`, `3072`), bitfield writes
  (`req->state &= ~REQ_STATE_QUEUED`), `memcpy` through `req_vbuf`
  (`mpt_cam.c:2943` + handler body), and re-post via
  `mpt_fc_post_els(mpt, req, ioindex)` at `mpt_cam.c:3075`.

**Effect:** heap OOB read → arbitrary-pointer-as-`request_t` → memory corruption
and likely panic; in principle exploitable as a corrupt-pointer primitive by an
adjacent FC peer with a buggy/malicious IOC.

**Note the asymmetric hardening that already exists**: when the *same* ELS
reply arrives as `MPI_FUNCTION_FC_LINK_SRVC_RSP` (no `TransactionContext`
field available), the code at `mpt_cam.c:2886-2892` walks the array linearly
and `KASSERT`s the index is in range. The `BUF_POST` path with
`TransactionContext` skips that safety walk and trusts the IOC-supplied index
directly — that is the gap.

The initiator-side analogue in `mpt.c:765` correctly bounds
`tgt_cmd_ptrs[ctxt_idx]` against `tgt_cmds_allocated`. The els path has no
equivalent guard.

## Trigger reachability on this guest

- No LSI-Logic MPT-Fusion FC HBA is present in the QEMU guest (`pciconf -lv`
  shows none). The mpt(4) driver attaches to no FC device, so `mpt->is_fc`
  is never set; `mpt_fc_els_reply_handler` is never dispatched.
- The vulnerable code is in the running kernel (compiled-in) but is only
  reachable from an interrupt fired by FC HW.

## Fix

`fix.diff` adds the symmetric bounds check before the array access:
```c
ioindex = le32toh(rp->TransactionContext);
if (ioindex >= mpt->els_cmds_allocated) {       /* mirrors mpt.c:765 */
    mpt_prt(mpt, "ELS_REPLY: ioindex %u >= els_cmds_allocated %u\n",
        ioindex, mpt->els_cmds_allocated);
    return (TRUE);
}
req = mpt->els_cmd_ptrs[ioindex];
```
This mirrors the existing `tgt_cmds_allocated` guard in `mpt.c:765` and uses
`els_cmds_allocated` (the actually-allocated count, set at `mpt_cam.c:4238`)
rather than the compile-time `MPT_MAX_ELS` upper bound.

## Build verification of the fix

`mpt.ko` built successfully from the patched source on the guest
(`cc 8.3 [DragonFly]`):
```
--- mpt_cam.o ---
cc  -O2 -pipe ... -Werror -DKLD_MODULE ... -c mpt_cam.c
--- mpt.ko ---
cc -Wl,--build-id=sha1 -nostdlib -Wl,--hash-style=sysv   -r -Wl,-d -o mpt.ko mpt.o mpt_cam.o mpt_debug.o mpt_pci.o mpt_raid.o mpt_user.o
BUILD_EXIT=0
```
Full output: `build.log`. The fix is build-clean.

## Fix-validation status

`not_testable` — the bug requires an LSI-Logic MPT-Fusion FC HBA (or an
emulator that fabricates IOC replies). The guest has neither. We confirmed
the fix **applies cleanly** and the patched module **compiles**; the change
is a 5-line bounds check identical in form to the existing
`tgt_cmds_allocated` guard in the same driver family.
