Unchecked reply-frame offset in mpt_intr allows OOB read/write via malicious IOC address reply
Summary
mpt_intr address-reply path at mpt.c:738-739: reply_baddr=MPT_REPLY_BADDR(reply_desc), offset=reply_baddr-reply_phys. NO check offset<2*PAGE_SIZE. reply_frame=MPT_REPLY_OTOV(mpt,offset)=&m->reply[offset] (:743) -> le32toh(reply_frame->MsgContext) OOB read (:744). Event deferral path mpt.c:588-589: evtf->context=context OOB write. Malicious IOC posts crafted reply desc with out-of-range address. Fix: bounds-check offset before MPT_REPLY_OTOV.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1280 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger_analysis.c | trigger-source | documentation marker | 703 B | view raw |
| fix.diff | suggested-fix | bounds-check offset against 2*PAGE_SIZE before MPT_REPLY_OTOV in mpt_intr | 1.0 KB | view raw |
| build.sh | build-script | syntax-checks the marker | 432 B | view raw |
| run.sh | run-script | documents INCONCLUSIVE status | 647 B | view raw |
| combined_build.log | build-log | combined X86_64_GENERIC rebuild log; rc=0 | 5.6 MB | β download |
| env.txt | environment | uname, pciconf -l, kldstat -v | 1015 B | view raw |
| README.md | readme | human-readable summary | 2.8 KB | β raw |
| VERDICT.md | verdict | detailed source-level analysis | 3.8 KB | β raw |
DF-1280 β Unchecked reply-frame offset in mpt_intr (OOB read/write)
Finding
The address-reply path in mpt_intr (sys/dev/disk/mpt/mpt.c:709) at lines
738-744:
reply_baddr = MPT_REPLY_BADDR(reply_desc); /* 738 */
offset = reply_baddr - (mpt->reply_phys & 0xFFFFFFFF); /* 739 */
bus_dmamap_sync_range(... offset, MPT_REPLY_SIZE, ...); /* 740-742 */
reply_frame = MPT_REPLY_OTOV(mpt, offset); /* 743 */
ctxt_idx = le32toh(reply_frame->MsgContext); /* 744 */
There is no bounds check on offset. The reply DMA pool is exactly
2 * PAGE_SIZE bytes (allocated in mpt_pci.c:723-738); MPT_REPLY_OTOV
expands to &m->reply[offset] (mpt.h:842-843), so a reply_desc whose
address bits encode an out-of-window reply_baddr drives both
bus_dmamap_sync_range and the dereference at line 744 out of bounds.
The event-deferral fast path at lines 588-589 (evtf->context = context;)
then writes back into the same out-of-range reply_frame, so the bug is an
OOB read and an OOB write β into an attacker-influenced kernel heap
offset.
A malicious or buggy IOC that posts a crafted reply descriptor (e.g. via
PCIe DMA of a malicious reply queue entry) triggers this on the next
interrupt. The MPI_ADDRESS_REPLY_A_BIT mask is just the LSB of the
descriptor; the upper 31 bits are taken straight from the descriptor.
Fix
Bounds-check offset against the actual reply pool size
(2 * PAGE_SIZE) before bus_dmamap_sync_range and MPT_REPLY_OTOV. If
out of range, drop the reply (MPT_REPLY_EMPTY) and continue the loop, so
the bad descriptor neither crashes the kernel nor feeds bogus MsgContext
into the request-pool indexing downstream.
Verification on this guest
- The mpt driver is statically compiled in but no MPT HBA exists in QEMU
(see DF-1278 README), so
mpt_intris never invoked. Bug is not runtime-triggerable on this guest. - Source-level confirmation:
mpt_pci.c:724-727β reply DMA tag maxsize is2 * PAGE_SIZE.mpt_pci.c:733-738βbus_dmamem_allocallocates that exact size.mpt.h:840-843βMPT_REPLY_BADDR(x) = (x << 1),MPT_REPLY_OTOV(m, i) = ((void *)(&m->reply[i])).mpt.c:739βoffset = reply_baddr - reply_phys;(no validation).mpt.c:743-744βreply_frame = &m->reply[offset]; ctxt_idx = ...->MsgContext;mpt.c:588-589β event-deferral path writesevtf->contextback through the same out-of-range pointer.- Fix verified to compile (combined build with DF-1278/1279/1285/1287).
Realistic impact ceiling
Hardware-attacker OOB read+write into the kernel heap via a crafted IOC
address-reply descriptor. CVSS reflects C:H/I:H/A:H at AV:P because the
write targets (function pointers, refcounts) are bucket-dependent but
attacker-influenced through the MsgContext indexing path.
VERDICT β DF-1280
Status
INCONCLUSIVE (source-confirmed; not runtime-triggerable on this guest).
Mechanism (source-confirmed)
The address-reply path in mpt_intr (sys/dev/disk/mpt/mpt.c:709) at
lines 738-744:
reply_baddr = MPT_REPLY_BADDR(reply_desc); /* 738 */
offset = reply_baddr - (mpt->reply_phys & 0xFFFFFFFF); /* 739 */
bus_dmamap_sync_range(mpt->reply_dmat, mpt->reply_dmap,
offset, MPT_REPLY_SIZE, BUS_DMASYNC_POSTREAD); /* 740-742 */
reply_frame = MPT_REPLY_OTOV(mpt, offset); /* 743 */
ctxt_idx = le32toh(reply_frame->MsgContext); /* 744 */
There is no bounds check on offset. The reply DMA pool is exactly
2 * PAGE_SIZE bytes (allocated in mpt_pci.c:724-738).
MPT_REPLY_OTOV(m, i) expands to ((void *)(&m->reply[i])) (mpt.h:842-843),
so a reply_desc whose address bits encode an out-of-window reply_baddr
drives both bus_dmamap_sync_range and the dereference at line 744 out of
bounds.
The event-deferral fast path at lines 588-589 writes back into the same out-of-range pointer:
evtf = (struct mpt_evtf_record *)reply_frame;
evtf->context = context;
So the bug is both an OOB read (line 744) and an OOB write (line 589), into an attacker-influenced kernel heap offset.
A malicious or buggy IOC posts a crafted reply descriptor via DMA into the
host-visible reply queue; on the next interrupt this path dereferences it.
The MPI_ADDRESS_REPLY_A_BIT is just the LSB of the descriptor; the upper
31 bits come straight from the descriptor.
Fix
Bounds-check offset against the reply pool size (2 * PAGE_SIZE) before
bus_dmamap_sync_range and MPT_REPLY_OTOV. If out of range, drop the
reply (MPT_REPLY_EMPTY) and continue the loop, so the bad descriptor
neither crashes the kernel nor feeds bogus MsgContext into the
request-pool indexing downstream.
Verification on this guest
mptis statically compiled intoX86_64_GENERIC(sys/config/X86_64_GENERIC:93βdevice mpt).- No MPT HBA exists in the QEMU guest, so
mpt_intris never invoked. Not runtime-triggerable. - Source-level proof of the OOB:
mpt_pci.c:724-727β reply DMA tag maxsize is2 * PAGE_SIZE.mpt_pci.c:733-738βbus_dmamem_allocof that exact size.mpt.h:840-843βMPT_REPLY_BADDR(x) = (x << 1);MPT_REPLY_OTOV(m, i) = ((void *)(&m->reply[i])).mpt.c:739βoffset = reply_baddr - reply_phys;(no validation).mpt.c:743-744βreply_frame = &m->reply[offset]; β¦->MsgContext(OOB read).mpt.c:588-589β event-deferral pathevtf->context = context;(OOB write).- Fix validation: combined
X86_64_GENERICrebuild with this fix applied exitedrc=0. Full log:combined_build.log.
Exploit chain
None on this guest (no HW). On a system with a malicious MPT HBA, the primitive is OOB read+write into kernel heap from IRQ context at the offset the IOC chose. The realistic ceiling is determined by what slab object sits at the chosen offset in the 2*PAGE_SIZE pool's neighborhood β but no escalation is attempted here because the path is unreachable on this guest.
Realistic impact ceiling
Hardware-attacker kernel heap OOB read+write. CVSS:
AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H β the write target is
attacker-influenced through MsgContext-indexed request-pool lookups, so
function-pointer / refcount corruption is conceivable in the right bucket.
PoC changes
Folder was empty; added trigger_analysis.c, build.sh, run.sh,
README.md, this VERDICT.md, fix.diff, manifest.json,
combined_build.log.
Recommended fix
Matches the finding proposal: bounds-check offset before
MPT_REPLY_OTOV. See fix.diff.
Fix status
not_testable β bug requires absent hardware. fix.diff applies cleanly
and compiles into X86_64_GENERIC (combined build rc=0).
Fix verification
not_testablecompile validated
nativekernel rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. mpt_intr reply offset no bounds -> OOB read+write via reply pool. mpt in GENERIC, no MPT HW.
No comments yet.