SG-capacity heap overflow: firmware-fed max_sg_count writes scatter-gather entries past the 34-entry capacity of srb->req[600] into srb->hba/ccb/next/dma_map (itl non-highmem, mv, mvfrey paths)
Summary
All three post paths overlay the request at srb->req[600] and write nsegs*16 bytes of SG entries from offset 48 - capacity 34 entries. The only runtime bound is nsegs > max_sg_count with max_sg_count stored verbatim from the HBA get_config reply; nothing checks the buffer capacity. nsegs>=35 overwrites srb->hba, srb->ccb/next, srb->dma_map/phy_addr - pointers the completion callbacks dereference (xpt_done on forged ccb). Distinct from DF-1311 (stack local, HIGH_MEM, itl-only). Marginal on this platform (MAXPHYS caps at <=34 segs, no in-tree SCATTER_VALID initiator) - hence Medium. Clamp-at-attach fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3961 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 2.6 KB | β raw | |
| trigger.c | β | 3.0 KB | view raw | |
| build.sh | β | 67 B | view raw | |
| run.sh | β | 91 B | view raw | |
| run.log | β | 107 B | view raw | |
| env.txt | β | 1021 B | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| build.log | β | 7.1 KB | view raw | |
| manifest.json | β | 370 B | view raw | |
| verdict.json | β | 4.2 KB | view raw |
DF-3961 β hptiop SG-capacity heap overflow into srb->req[600] (non-highmem itl/mv/mvfrey)
File: sys/dev/raid/hptiop/hptiop.c (pass 2; knowns DF-1311/1312/1313 excluded)
What
hptiop_post_req_itl (non-HIGH_MEM branch), hptiop_post_req_mv and
hptiop_post_req_mvfrey write nsegs SG entries into
((struct hpt_iop_request_scsi_command *)srb)->sg_list β the front of the
fixed 600-byte srb->req[] DMA-coherent buffer. Capacity is 34 entries
(offsetof(sg_list)=48; (600β48)/16=34); the only runtime gate is
nsegs > hba->max_sg_count (hptiop.c:2660) where max_sg_count is taken
verbatim from the HBA firmware (hptiop.c:1923β1933), and io_dmat is created
with nsegments = max_sg_count (hptiop.c:1952). If the firmware reports β₯35,
any CCB with β₯35 segments overwrites srb->hba (offset 600), srb->ccb,
srb->next, srb->dma_map, srb->phy_addr (offsets 608β639) and then the
adjacent srb (stride 672). The completion callbacks then do
ccb = (union ccb *)srb->ccb; β¦ xpt_done(ccb) β wild-pointer deref of
physically-shaped values.
Distinct from DF-1311, which covers only the stack local req in the
HIGH_MEM_ACESS branch of hptiop_post_req_itl (hptiop.c:2417β2457).
Build / Run (hardware recipe)
See trigger.c. No build on the audit guest: guest has no HighPoint HBA
(env.txt). On a host with a (misbehaving) RR3xxx/4xxx reporting
max_sg_count β₯ 35:
1. kldload hptiop (module ships in /boot/kernel/hptiop.ko)
2. issue β₯35-segment CCB (CAM_SCATTER_VALID originator, or >136 KB I/O)
Expected
Kernel memory corruption of the srb soft structures β panic on completion
(xpt_done on forged ccb) or silent slab corruption.
Honesty
Reachability on this platform is marginal: MAXPHYS=128 KB pins the load path at β€34 segments (scsi_da.c:1248), and no in-tree initiator uses CAM_SCATTER_VALID. The missing capacity bound is certain; exploitation additionally requires firmware to report β₯35 and a β₯35-segment originator.
DF-3961 verdict β source-certain missing bound, marginal platform reachability
Reproduced?
No. The audit guest has no HighPoint PCI device (pciconf: no vendor 0x1103 β
env.txt). hptiop.ko is present in /boot/kernel and one kldload away on any
host with the hardware, but the trigger additionally needs the HBA firmware to
report max_sg_count β₯ 35 (device-fed get_config reply, hptiop.c:1923β1933
stores it verbatim) and a CCB with β₯35 segments.
Why the bug is real (path:line)
- Buffer:
srb->req[HPT_SRB_MAX_REQ_SIZE=600](sys/dev/raid/hptiop/hptiop.h:42, struct hpt_iop_srb at hptiop.h:452β461: req at 0, hba ptr at 600, ccb at 608, next at 616, dma_map at 624, phy_addr at 632, srb_flag 640, index 644; HPT_SRB_MAX_SIZE stride = 672). - Sinks: hptiop.c:2471β2481 (itl non-highmem), 2539β2547 (mv),
2593β2601 (mvfrey) β
psg = req->sg_list(offset 48 in srb), loop writesnsegs*16bytes; entry 34 lands on [592,608) βsrb->hbacorrupted at nsegs=35,srb->ccb/dma_mapat 36β37. - Only bound:
nsegs > hba->max_sg_count(hptiop.c:2660); io_dmat built with nsegments=max_sg_count (hptiop.c:1946β1959) so the DMA layer enforces the same firmware value, never the buffer capacity. - DF-1311 covered the stack
reqof the HIGH_MEM_ACESS branch only (hptiop.c:2417β2457); this heap variant is a different sink and reaches all three families.
Reachability analysis (why severity is Medium, not High)
- scsi_da.c:1248 pins disk I/O to MAXPHYS=128 KB (x86_64) β bus_dmamap_load yields β€ 33 segments (+1 for a 4 GB boundary split) = β€34 β no overflow from ordinary disk I/O even with permissive firmware.
- CAM_SCATTER_VALID path (hptiop.c:2329β2342) passes
sglist_cntthrough unfiltered, but no in-tree DragonFly initiator sets CAM_SCATTER_VALID (grep: only the definition in cam_ccb.h). - β overflow requires firmware reporting β₯35 AND a β₯35-segment originator (out-of-tree/future periph, or a kernel with MAXPHYS > 136 KB).
Fix
fix.diff clamps the firmware value to the real capacity at attach
(HPT_SRB_MAX_SG_COUNT = 34), which simultaneously bounds post_scsi_command,
io_dmat nsegments and the maxsize computation. git apply --check passes
against pristine sys/.
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_testablefix.diff clamps hba->max_sg_count to HPT_SRB_MAX_SG_COUNT (34) at attach, which simultaneously bounds the post_scsi_command check, io_dmat nsegments and the maxsize math; git apply --check passes against pristine sys/. Runtime validation impossible on the audit guest (baseline itself needs the HBA) fix.diff applied to the guest /usr/src copy and the hptiop.ko module rebuilt clean with -Werror (build.log; combined with the other four hptiop fix.diffs of this batch); runtime validation still impossible without the HBA.
fix.diff (git apply --check clean); no fix_build.log/fix_run.log possible without hardware; fix.diff applied to the guest /usr/src copy and the hptiop.ko module rebuilt clean with -Werror (build.log; combined with the other four hptiop fix.diffs of this batch); runtime validation still impossible without the HBA.
Confirmed kernel references
- sys/dev/raid/hptiop/hptiop.c:2471
- sys/dev/raid/hptiop/hptiop.c:2480
- sys/dev/raid/hptiop/hptiop.c:2539
- sys/dev/raid/hptiop/hptiop.c:2593
- sys/dev/raid/hptiop/hptiop.c:2600
- sys/dev/raid/hptiop/hptiop.c:2660
- sys/dev/raid/hptiop/hptiop.c:1933
- sys/dev/raid/hptiop/hptiop.c:1952
- sys/dev/raid/hptiop/hptiop.h:42
- sys/dev/raid/hptiop/hptiop.h:452
- sys/bus/cam/scsi/scsi_da.c:1248
Detail
Exploit chain
device reports max_sg_count>=35 -> originator supplies 35+ segment CCB -> SG entries 34..37 overwrite srb->hba/ccb/dma_map/phy_addr with {ds_len, 0, ds_addr} triples (semi-controlled physical addresses of the I/O buffer) -> firmware completes the command -> hptiop_request_callback_* reads ccb from corrupted srb->ccb and calls xpt_done(ccb) on a forged pointer -> panic or groomed corruption (undemonstrated; requires hardware).
Evidence (decisive lines)
VERDICT.md (layout math + path:line chain), trigger.c (sink/bound analysis + hardware recipe), env.txt (guest hardware absence), fix.diff (capacity clamp, git apply --check clean)
PoC changes
No runnable PoC: trigger is hardware-gated (HighPoint HBA + firmware reporting max_sg_count>=35). trigger.c documents the exact sink math, both preconditions and the on-hardware recipe.
Verified recommended fix
Clamp firmware-reported max_sg_count to the real srb->req[] SG capacity (34) at attach (fix.diff: HPT_SRB_MAX_SG_COUNT).
Verdict
Source-certain missing SG-capacity bound: hptiop_post_req_itl(non-highmem)/mv/mvfrey write nsegs*16 bytes of scatter-gather into the 600-byte srb->req[] buffer whose capacity is 34 entries (offsetof(sg_list)=48), with the only gate being the firmware-fed max_sg_count (hptiop.c:2660 vs :1933; io_dmat nsegments=max_sg_count at :1952). nsegs>=35 overwrites srb->hba/ccb/next/dma_map/phy_addr (hptiop.h:452-461 layout) which the completion callbacks dereference (ccb=srb->ccb; xpt_done(ccb)). Not DF-1311 (that is the stack local of the HIGH_MEM_ACESS itl branch at :2417-2457). Not reproduced: guest has no HighPoint HBA (env.txt); additionally, on this platform reachability is marginal because MAXPHYS=128K caps the bus_dmamap_load path at <=34 segments (scsi_da.c:1248) and no in-tree initiator sets CAM_SCATTER_VALID β so exploitation needs firmware reporting >=35 AND a >=35-segment originator. Downgraded to Medium accordingly.
No comments yet.