Unbounded scatter-gather loop overflows pmap->psg[17], tmpSg[512] stack, and PRD heap table
Summary
OsSendCommand CAM_SCATTER_VALID at entry.c:2859: for(idx=0;idx<ccb->csio.sglist_cnt;idx++) writes pCmd->pSgTable[idx]=pmap->psg[idx]. psg[MAX_SG_DESCRIPTORS=17]. sglist_cnt is u16 from userland CAMIOCOMMAND, XPT does NOT validate against SIM segment limit. sglist_cnt>17 -> OOB heap write past psg into adjacent BUS_DMAMAP (pAdapter/dma_map ptrs dereferenced later). fDeviceSendCommand:1787 do{*sg1++=*sg2}while(...) into tmpSg[512]. PRD fill :1853-1860. Operator/root via /dev/passN. Fix: check sglist_cnt<=MAX_SG_DESCRIPTORS.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1284 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | object-level proof: replays OsSendCommand CAM_SCATTER_VALID loop with sglist_cnt=64 > 17 | 4.3 KB | view raw |
| fix.diff | suggested-fix | reject CCB (CAM_REQ_TOO_BIG) when sglist_cnt > MAX_SG_DESCRIPTORS | 844 B | view raw |
| build.sh | repro-script | cc -O2 -o harness harness.c | 101 B | view raw |
| run.sh | repro-script | ./harness | 60 B | view raw |
| build.log | build-log | harness build, full output | 65 B | view raw |
| run.log | run-log | harness decisive run: 47 guard entries / 752 bytes overflow | 363 B | view raw |
| env.txt | environment | uname + cc version | 188 B | view raw |
| README.md | readme | summary + reproduce | 2.6 KB | β raw |
| VERDICT.md | verdict | full mechanism + reachability + fix + compile validation | 4.1 KB | β 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 |
DF-1284 β Unbounded scatter-gather loop overflows pmap->psg[17] in hptmv OsSendCommand
File: sys/dev/raid/hptmv/entry.c:2859 (sink); :2800 (pSgTable = pmap->psg)
Class: CWE-787 Out-of-bounds Write (heap)
Severity: High
The bug (source-confirmed)
In OsSendCommand (the SIM XPT_SCSI_IO action handler), the CAM_SCATTER_VALID
branch (entry.c:2852) copies a CAM CCB's scatter-gather list into the per-command
SG table with an unbounded loop (entry.c:2859):
pCmd->pSgTable = pmap->psg; /* :2800 */
...
for (idx = 0; idx < ccb->csio.sglist_cnt; idx++) { /* :2859 */
pCmd->pSgTable[idx].dSgAddress = ...; /* :2860 */
pCmd->pSgTable[idx].wSgSize = ...; /* :2861 */
pCmd->pSgTable[idx].wSgFlag = ...; /* :2862 */
}
pSgTable points at pmap->psg, which is SCAT_GATH psg[MAX_SG_DESCRIPTORS]
(osbsd.h:143, MAX_SG_DESCRIPTORS = 17, global.h:47). sglist_cnt is
u_int16_t (cam_ccb.h:604) β range 0..65535 β and is not validated against
the SIM's segment limit anywhere in this path. (The other path,
hptmv_dmamap_callback, does have HPT_ASSERT(nsegs <= MAX_SG_DESCRIPTORS) at
:2652; the CAM_SCATTER_VALID path has none.)
A CCB with sglist_cnt > 17 therefore writes past psg[17] into the rest of the
BUS_DMAMAP struct (next/pAdapter/dma_map linkage follow it) and beyond into
the adjacent slab object β an attacker-controlled heap overflow.
Reachability / threat model
The hptmv driver attaches to HighPoint RocketRAID (RR18xx) controllers. None are
present in the audit QEMU guest (pciconf -l shows no such device), so the SIM
action path is not runtime-reachable here. On real hardware, an operator/root user
with access to a /dev/passN (CAM pass-through) node for an hptmv target can issue
a CAMIOCOMMAND with CAM_SCATTER_VALID and a hand-crafted sglist_cnt, reaching
the loop. See VERDICT.md.
Reproduce (harness)
./build.sh && ./run.sh
Decisive output:
[DF-1284] attacker sglist_cnt=64 (max allowed for psg[]=17) [DF-1284] overflow past psg[17]: YES (47 guard entries clobbered; 752 bytes of adjacent heap overwritten)
Fix
fix.diff rejects the CCB (CAM_REQ_TOO_BIG) when sglist_cnt > MAX_SG_DESCRIPTORS,
mirroring the existing cleanup path (FreeCommand / dmamap_put / xpt_done).
Validated: applies cleanly; entry.c compiles under -Werror via the kmod
framework. (A twin unbounded loop exists at entry.c:2524 in fOsBuildSgl; the
cited attacker-reachable CAM path is fixed here.)
DF-1284 β VERDICT
Verdict: REPRODUCED (primitive confirmed at object/harness level; runtime path is hardware-bound and not present on the audit guest).
Mechanism (source trace)
OsSendCommand (sys/dev/raid/hptmv/entry.c) is the SIM XPT_SCSI_IO action
handler for HighPoint RocketRAID controllers.
-
SG table pointer β
entry.c:2800:c pCmd->pSgTable = pmap->psg;psgisSCAT_GATH psg[MAX_SG_DESCRIPTORS](osbsd.h:143), 17 entries of 16 bytes each (global.h:103). -
Attacker-controlled loop bound β
entry.c:2859:c for (idx = 0; idx < ccb->csio.sglist_cnt; idx++) { pCmd->pSgTable[idx].dSgAddress = (ULONG_PTR)(UCHAR *)sgList[idx].ds_addr; pCmd->pSgTable[idx].wSgSize = sgList[idx].ds_len; pCmd->pSgTable[idx].wSgFlag = (idx==ccb->csio.sglist_cnt-1)?SG_FLAG_EOT:0; }sglist_cntisu_int16_t(cam_ccb.h:604), supplied by userland in the CAM CCB (e.g. viaCAMIOCOMMAND/pass(4)). The CAM XPT layer does not clamp it to the SIM's advertised segment limit, and this branch has no local check. -
Contrast with the safe sibling β
entry.c:2652(hptmv_dmamap_callback, the non-CAM_SCATTER_VALIDpath) hasHPT_ASSERT(nsegs <= MAX_SG_DESCRIPTORS). TheCAM_SCATTER_VALIDpath has no equivalent guard.
sglist_cnt > 17 overflows psg[], clobbering the trailing BUS_DMAMAP fields
(next, pAdapter, dma_map) and the adjacent slab object β an attacker-shaped
heap overflow (dSgAddress/wSgSize are attacker-controlled from the SG list).
Primitive characterization
- Write size: up to
(sglist_cnt - 17) * sizeof(SCAT_GATH)bytes (= 16 bytes/segment, attacker-controlled content via the SG list). - Target: the
BUS_DMAMAPallocation (pmap) and the adjacent slab object; the corruptedpAdapter/dma_mappointers are dereferenced later byfOsCommandDone/bus_dmamap_load.
Harness proof
harness.c builds a BUS_DMAMAP-shaped buffer (psg[17] + a canary guard) and
replays the loop with sglist_cnt = 64. Output (run.log):
[DF-1284] attacker sglist_cnt=64 (max allowed for psg[]=17) [DF-1284] overflow past psg[17]: YES (47 guard entries clobbered; 752 bytes of adjacent heap overwritten)
Why not a live in-kernel reproduction (valid hard blocker)
The hptmv driver attaches to HighPoint RR18xx PCI controllers; pciconf -l on the
audit guest shows no such device (only virtio + QEMU std-vga). The SIM action
handler therefore never runs here. Live trigger conditions: an hptmv-attached
target reachable via a CAM pass-through (/dev/passN) device node, with an
operator/root credential issuing a CAMIOCOMMAND carrying CAM_SCATTER_VALID and
a crafted sglist_cnt. Primitive proven at the object/harness level. Escalation to
uid=0 requires the primitive to fire in a running kernel β not demonstrable on
this guest because the driver never attaches. Honest reported impact: the heap
corruption primitive itself.
Fix
fix.diff adds, at the head of the CAM_SCATTER_VALID branch, a rejection of any
CCB with ccb->csio.sglist_cnt > MAX_SG_DESCRIPTORS: it logs, frees the command,
sets ccb->ccb_h.status = CAM_REQ_TOO_BIG, returns the dmamap, decrements
outstandingCommands, calls xpt_done(ccb) and goto Command_Complished β
exactly mirroring the existing error-cleanup block at :2879-2886.
Validated: patch -p1 succeeds; entry.c compiles cleanly under -Werror
via the kmod framework (entry.o produced). A structurally-identical unbounded
loop exists at entry.c:2524 in fOsBuildSgl (internal command-build path); this
fix targets the attacker-reachable CAM path cited by the finding.
Fix-validation status
not_testable for a live before/after (the PoC driver path cannot run on the
guest β no HighPoint controller). Evidence the fix is correct: (1) harness
before/after shows the rejection closes the overflow; (2) entry.c with the fix
compiles under -Werror in-tree. (Note: the hptmv module's Makefile has a
pre-existing uudecode rule bug that breaks a full make all in this environment
independent of the fix; validation was done by compiling entry.o directly.)
Fix verification
not_testablecompile+harness validated
module build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (harness). hptmv OsSendCommand sglist_cnt no bounds vs psg[17] -> 752B OOB. No HighPoint controller.
No comments yet.