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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3961 Β· 11 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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 writes nsegs*16 bytes; entry 34 lands on [592,608) β†’ srb->hba corrupted at nsegs=35, srb->ccb/dma_map at 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 req of 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_cnt through 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_testable
baseline no→ patch + rebuild →patched clean

fix.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.
↓ fix.diffper-fix-DF-3961

Confirmed kernel references

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.