asr_intr maps the adapter-posted FromFIFO reply offset into a kernel pointer with no validation β forged reply frame yields a forged ccb with LIST_REMOVE/callout_stop writes; FAIL path uses device-controlled MMIO offsets unbounded
Summary
Reply = (frame *)(ReplyOffset - ha_Msgs_Phys + ha_Msgs) with no check that ReplyOffset lies in the pool window; the InitiatorContext64 loaded through the forged Reply becomes a union asr_ccb* that ASR_ccbRemove writes through (callout_stop + LIST_REMOVE unlink), followed by status/resid/sense writes and xpt_done dispatch; the FAIL branch reads/writes bus-space at the device-supplied PreservedMFA offsets. Malicious/faulty IOP firmware -> arbitrary kernel read/write engine (forged ccb unlink writes) and out-of-BAR MMIO accesses; FreeBSD ancestor shares the defect. Validate the offset against the pool window.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3823 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.7 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| fix.diff | β | 944 B | view raw | |
| env.txt | β | 323 B | view raw | |
| verdict.json | β | 2.6 KB | view raw |
DF-3823 β asr(4) maps the adapter-supplied FromFIFO reply offset into a kernel pointer with no validation (device-controlled arbitrary-kernel deref/write), and reads/writes MMIO at device-controlled frame offsets
- File:
sys/dev/raid/asr/asr.c - Lines: 2886-2898 (reply mapping), 2906-2953 (FAIL path MMIO offsets)
- Class: CWE-129/CWE-20 β unchecked device index β wild kernel pointer β arbitrary read/write (device-trust)
- Reach: adapter interrupt only (I2O outbound FIFO). No software path produces the value.
Analysis
asr_intr():
2886: if (((ReplyOffset = asr_get_FromFIFO(sc)) == EMPTY_QUEUE)
2887: && ((ReplyOffset = asr_get_FromFIFO(sc)) == EMPTY_QUEUE)) {
2888: break;
2889: }
2890: Reply = (PI2O_SCSI_ERROR_REPLY_MESSAGE_FRAME)(ReplyOffset
2891: - sc->ha_Msgs_Phys + (char *)(sc->ha_Msgs));
ReplyOffset is whatever the IOP posts in the outbound FIFO. It is
assumed to lie inside the ha_Msgs reply-frame pool
(contigmalloc'd at asr.c:1910-1914, ha_Msgs_Count frames), but
nothing checks:
ReplyOffset >= sc->ha_Msgs_Phys
&& ReplyOffset < sc->ha_Msgs_Phys + ha_Msgs_Count * sizeof(frame)
An offset outside that window makes Reply an arbitrary offset from the
pool base β including negative (below the pool). The handler then:
- reads the 64-bit
InitiatorContext64fromReply->β¦StdMessageFrame(asr.c:2896-2898) and treats it as aunion asr_ccb *; - passes that pointer to
ASR_ccbRemove()(asr.c:2971) which doescallout_stop(&ccb->ccb_h.timeout_ch)andLIST_REMOVEβ two write primitives through the forged pointer; - writes completion status into
ccb->ccb_h.status,csio.scsi_status,resid, andbcopys sense data fromReply->SenseData(asr.c:2975-3052); - finally
xpt_done(ccb)/wakeup(ccb).
In the message-failure branch (asr.c:2899-2954) it additionally uses the
device-supplied PreservedMFA (MessageOffset, asr.c:2906-2908) as a
bus-space offset for a 4-byte MMIO read (asr.c:2917-2921) and an
MMIO write of a whole I2O_UTIL_NOP_MESSAGE (asr.c:2948-2949) β
unbounded offsets into the frame BAR (device-side corruption / potential
host bus faults).
Impact ceiling
With a compromised or faulty IOP, this is a full arbitrary
kernel-read/write engine: choose ReplyOffset so Reply lands on
controlled DMA'd memory (or anywhere in physmap), return a crafted
InitiatorContext64, and ASR_ccbRemove performs unlink-style writes at
the chosen address before an indirect dispatch through the CAM layer.
Severity capped Low for the hardware precondition.
Verdict
inconclusive β the audit guest has no DPT/Adaptec I2O HBA
(see ../DF-3825/run.log and ../DF-3819/env.txt), so asr_intr never
runs. Source-level: certain.
VERDICT β DF-3823
Status: inconclusive (device-gated; the offending value is produced
by the IOP's outbound FIFO. The audit guest has no asr-compatible HBA β
/dev/asr0 ENOENT, zero PCI vendor-0x1044 devices β so asr_intr()
never executes). Source-level confidence: certain.
Trace
asr_intr()readsReplyOffsetfrom the FromFIFO register (asr.c:2886-2888). The only value rejected isEMPTY_QUEUE(0xffffffff).Reply = (frame *)(ReplyOffset - sc->ha_Msgs_Phys + (char *)sc->ha_Msgs)(asr.c:2890-2891) β pointer arithmetic on an unvalidated u32. The reply pool spans[ha_Msgs_Phys, ha_Msgs_Phys + ha_Msgs_Count * sizeof(I2O_SCSI_ERROR_REPLY_MESSAGE_FRAME))(allocation at asr.c:1900-1915, FIFO population at asr.c:1918-1925 β every posted frame is one of these addresses, so any other value is by construction a firmware error or attack).ccb = (union asr_ccb *)(long) I2O_MESSAGE_FRAME_getInitiatorContext64(&Reply->β¦)(asr.c:2896-2898) β 8 bytes read from whereverReplypoints (the macro is a raw u_int64_t load, i2odep.h:552-555; on this driverI2O_64BIT_CONTEXT == 0so the 64-bit context is split acrossInitiatorContextand the following dword β both under device control through the forgedReply).ASR_ccbRemove(sc, ccb)(asr.c:2971 β 785-792):callout_stop(ccb->ccb_h.timeout_ch)β writes to the callout through the forged pointer's field;LIST_REMOVE(&ccb->ccb_h, sim_links.le)β the classic unlink arbitrary-write (fwd->back = back; back->fwd = fwd).- Status/resid/sense writes into the forged
ccb(asr.c:2975-3052), thenxpt_done(ccb)(indirect dispatch) orwakeup(ccb). - FAIL branch:
MessageOffset=PreservedMFAfrom the reply (asr.c:2906-2908) is used forbus_space_read_4(..., MessageOffset + offsetof(...))(asr.c:2917-2921) andasr_set_frame(sc, β¦, MessageOffset, sizeof(NOP))(asr.c:2948-2949) β unbounded MMIO offsets into/out of the messaging BAR.
Contrast with the rest of the driver
asr_intr's sense copy (asr.c:3029-3053) does clamp its device-supplied
length four ways β showing the authors clamped what they noticed; the
ReplyOffset (and MessageOffset) mapping simply was never checked.
The FreeBSD ancestor carries the same defect (shared-hardening).
Fix
fix.diff: reject any ReplyOffset outside the ha_Msgs pool window
before forming Reply (print + break). The MessageOffset MMIO
offsets additionally need a BAR-bound check β noted in the diff header;
minimal fix ships the pool-window check, which kills the primary
forged-Reply primitive.
Fix validation
not_testable β needs the IOP to post an out-of-window frame.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableBaseline and fix runs both require an asr IOP posting frames; absent on the guest. fix.diff adds the pool-window check.
fix.diff
Confirmed kernel references
Detail
Exploit chain
device: post out-of-window ReplyOffset -> forged Reply over DMA-controlled memory -> chosen InitiatorContext64 -> forged ccb -> callout_stop + LIST_REMOVE unlink writes + sense bcopy -> kernel code execution. Precondition: compromised/faulty IOP firmware.
Evidence (decisive lines)
VERDICT.md step-by-step trace with line refs; fix.diff (pool-window validation); ../DF-3825/run.log + ../DF-3819/env.txt (no asr hardware on guest)
PoC changes
No seed; analysis pack (interrupt-time device data cannot be synthesized without the HBA).
Verified recommended fix
Validate ReplyOffset against [ha_Msgs_Phys, ha_Msgs_Phys + ha_Msgs_Count*sizeof(frame)) before forming Reply (asr.c:2890).
Verdict
asr_intr() converts the adapter-posted FromFIFO value into a kernel pointer (ReplyOffset - ha_Msgs_Phys + ha_Msgs, asr.c:2890-2891) with no check that it lies inside the ha_Msgs reply pool. An out-of-window offset (faulty or malicious IOP) yields a forged PI2O_SCSI_ERROR_REPLY_MESSAGE_FRAME whose InitiatorContext64 (raw u64 load, asr.c:2896-2898) becomes a union asr_ccb * that ASR_ccbRemove (asr.c:2971, 785-792) writes through: callout_stop on a forged callout pointer plus LIST_REMOVE unlink writes, then status/resid/sense writes and xpt_done dispatch - a device-driven arbitrary kernel write engine. The FAIL branch additionally uses the device-supplied PreservedMFA as unbounded bus-space offsets for MMIO read/write (asr.c:2917-2921, 2948-2949). Guest has no I2O HBA (/dev/asr0 ENOENT, zero vendor-0x1044 PCI devices) so asr_intr never runs; source analysis certain. Note the same function clamps its sense lengths four ways (asr.c:3033-3042) - the offset mapping was simply never validated.
No comments yet.