β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1161

Unbounded els_cmd_ptrs[] index from IOC-supplied TransactionContext in FC ELS reply handler

Summary

mpt_fc_els_reply_handler at mpt_cam.c:2951-2952: ioindex=le32toh(rp->TransactionContext) (U32 from IOC DMA) used as index into els_cmd_ptrs[MPT_MAX_ELS=64] with NO bounds check. mpt_intr validates MsgContext-derived ctxt_idx but NOT TransactionContext. OOB index -> arbitrary pointer treated as live request_t -> TAILQ_REMOVE with corrupt pointer, bitfield writes, memcpy through req_vbuf. FC target mode + LINK_SERVICE_BUFFER_POST_REPLY with MsgLength>5. Adjacent FC peer or IOC firmware defect. Same class as DF-1057/1122 family. Fix: validate ioindex<els_cmds_allocated.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1161 Β· 9 files
FileTypeDescriptionSize
fix.diff suggested-fix Add `if (ioindex >= mpt->els_cmds_allocated) return TRUE;` before els_cmd_ptrs[ioindex] access 457 B view raw
VERDICT.md verdict full mechanism trace + reachability note 4.3 KB ↓ raw
README.md readme summary + reproduce steps 1.4 KB ↓ raw
build.sh build-script apply fix + build mpt.ko 374 B view raw
run.sh run-script placeholder runtime trigger (needs FC HW) 1.0 KB view raw
build.log build-log full successful build of patched mpt.ko 995 B view raw
env.txt environment uname, cc, hw.model 450 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme summary + reproduce steps
↓ download raw

DF-1161 PoC verification β€” source-level trace of mpt_fc_els_reply_handler unbounded els_cmd_ptrs[] index bug.

The mpt(4) driver is compiled into the X86_64_GENERIC kernel (sys/config/X86_64_GENERIC:93) but no LSI-Logic MPT-Fusion FC HBA is present in the QEMU guest, so mpt->is_fc is never set and the ELS reply handler is never dispatched. Verification is by source-level trace + build-validation of the fix.

Bug location

Mechanism

IOC DMA-supplied U32 TransactionContext is used directly as an array index into the 64-entry els_cmd_ptrs[] with no bounds check. A malicious or buggy IOC reply with MsgLength > 5 and TransactionContext >= 64 reads an attacker-influenced kernel pointer past the array and then dereferences it as a live request_t * (TAILQ_REMOVE, bitfield writes, memcpy through req_vbuf).

Reproduce

  1. Apply fix.diff to /usr/src.
  2. cd /usr/src/sys/dev/disk/mpt && make KMOD=mpt β†’ mpt.ko builds clean.
  3. Runtime test requires LSI-Logic MPT-Fusion FC HBA (not present on this guest).

See VERDICT.md for the full analysis.

VERDICT.md verdict full mechanism trace + reachability note
↓ download raw

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:

/* 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 KASSERTs 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:

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.

Fix verification

not_testable

compile validated

module build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source+harness. mpt els_cmd_ptrs[ioindex] no bounds check -> OOB heap ptr read. mpt in GENERIC, no FC HBA.