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

Unbounded NumPhysDisks/NumActiveVolumes loop counts read past IOC config-page trailing arrays

Summary

Loop terminators derived from device-supplied U8 count fields (vol_pg NumPhysDisks ioc_page3 NumPhysDisks ioc_page2 NumActiveVolumes) iterate trailing variable-length PhysDisk[]/RaidVolume[] arrays of IOC config pages without confirming count fits in allocated page buffer. Malicious/faulty IOC reporting count larger than page causes OOB reads of page buffer and combined with DF-2484 the unbounded indices read from OOB slots used for further corruption. Amplifies DF-2484. [VERIFIED pass2: reproduced - NumPhysDisks 3 with 0 trailing entries drives 3 OOB PhysDisk reads with OOB-fetched index writing raid_disks 3 flags guard word captured; PageLength-clamp fix A/B no smash]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2485 Β· 8 files
FileTypeDescriptionSize
README.md β€” 1.0 KB ↓ raw
VERDICT.md β€” 2.6 KB ↓ raw
build.sh β€” 99 B view raw
run.log β€” 647 B view raw
env.txt β€” 963 B view raw
fix.diff β€” 1.8 KB view raw
manifest.json β€” 779 B view raw
verdict.json β€” 2.9 KB view raw

DF-2485 -- mpt_raid config-page count loops walk past the page allocation

Verification used the shared rig tree at /root/rigs on the audit guest (see manifest). The rig for this finding: dfv_mpt (rig.c = harness, gen.sh = verbatim extractor, dfv_*_extract.h = generated production text). The decisive outputs below were captured live on the guest during the 2026-10-24 verification session; full session logs are summarized per run.

Build

sh build.sh            # extracts verbatim driver text + builds the KLD

Run (guest, root)

kldload ./dfv_rig.ko

kldload dfv_mpt.ko; for i in 0 1 2 3; do sysctl kern.dfv_mpt_run=$i >/dev/null; sysctl -n kern.dfv_mpt_status; done

Expected

run.log: 3 refresh_disk walks, raid_disks-guard[70]=aaaaaaab (OOB write); announce 5 walks on OOB data; fixed: no smash.

Fix validation

fix.diff clamps all three counts to the page-header-derived maximum; note in diff: allocation-size validation belongs in the page-read path.

Verdict: reproduced / impact=limited_write (see verdict.json, VERDICT.md)

VERDICT.md
↓ download raw

DF-2485 verdict -- mpt_raid config-page count loops walk past the page allocation

status: reproduced reproduced: 1 impact: limited_write confidence: certain attempts: 2 runtime_sec: 900

The verbatim mpt_refresh_raid_data and mpt_announce_vol iterate device-supplied counts (ioc_page3->NumPhysDisks :1401, ioc_page2->NumActiveVolumes :1432, vol_pg->NumPhysDisks :1149) over trailing arrays without checking the counts fit the page. The rig plants honest page headers (PageLength covering the header only) plus oversubscribed counts - exactly what a hostile IOC delivers - with the raid arrays in a single guarded allocation. Hostile page3 NumPhysDisks=3 (page holds 0 entries): the loop ran 3 times reading PhysDisk[0..2] past the page (0x03 canary), and the OOB-fetched PhysDiskNum indexed raid_disks[3] - one past the 2-entry array - whose flags |= MPT_RDF_REFERENCED|ACTIVE write landed in the guard (raid_disks-guard[70]=aaaaaaab captured). The announce loop (NumPhysDisks=5) similarly printed garbage flags read past the allocation (the OOB-read amplification arm of DF-2484). Control (honest counts) clean. Fix (clamp counts to the PageLength-derived maximum) A/B: no guard smash. HW-gated at runtime (no LSI MPT controller on the guest).

Evidence (decisive lines)

DF2485[HOSTILE]: page3 NumPhysDisks=3 (page holds 0 entries), page2 NumActiveVolumes=2 (page holds 0) DF2485: driver log: read_cfg_page(len=0) refresh_disk refresh_disk refresh_disk read_cfg_page(len=0) mpt_announce_disk D raid_disks-guard[70]=aaaaaaab DF2485: RESULT=COUNT-DRIVEN OOB (loops walked past the page allocation; OOB-fetched PhysDiskNum indexed raid_disks[] OOB) DF2485[announce]: 5 member lines printed from OOB PhysDisk[] reads (Quiesced/Was Non-Optimal/Offline/Other garbage) --- control --- DF2485[CONTROL]: ... RESULT=clean (honest counts) --- fixed source A/B --- DF2485: guard smashed=0 (clamped loops; no raid_disks OOB write)

Exploit chain / privesc assessment

malicious/faulty IOC (PCI trust boundary); OOB read of the page plus an IOC-indexed OOB |= write into raid_disks (limited write primitive per this findings scope; DF-2484 owns the far-index arm).

Fix + A/B

fix.diff clamps all three counts to the page-header-derived maximum; note in diff: allocation-size validation belongs in the page-read path.

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

fixed
↓ fix.diffper-fix-DF-2485

Confirmed kernel references

Detail

Exploit chain

malicious/faulty IOC (PCI trust boundary); OOB read of the page plus an IOC-indexed OOB |= write into raid_disks (limited write primitive per this findings scope; DF-2484 owns the far-index arm).

Evidence (decisive lines)

run.log: 3 refresh_disk walks, raid_disks-guard[70]=aaaaaaab (OOB write); announce 5 walks on OOB data; fixed: no smash.

PoC changes

rig needed honest PageLength values planted in the fabricated pages (first attempt left them zero, masking the fix effect); guard placed in the same allocation after the arrays.

Verified recommended fix

clamp NumPhysDisks/NumActiveVolumes/NumPhysDisks to the page header length

Verdict

The verbatim mpt_refresh_raid_data and mpt_announce_vol iterate device-supplied counts (ioc_page3->NumPhysDisks :1401, ioc_page2->NumActiveVolumes :1432, vol_pg->NumPhysDisks :1149) over trailing arrays without checking the counts fit the page. The rig plants honest page headers (PageLength covering the header only) plus oversubscribed counts - exactly what a hostile IOC delivers - with the raid arrays in a single guarded allocation. Hostile page3 NumPhysDisks=3 (page holds 0 entries): the loop ran 3 times reading PhysDisk[0..2] past the page (0x03 canary), and the OOB-fetched PhysDiskNum indexed raid_disks[3] - one past the 2-entry array - whose flags |= MPT_RDF_REFERENCED|ACTIVE write landed in the guard (raid_disks-guard[70]=aaaaaaab captured). The announce loop (NumPhysDisks=5) similarly printed garbage flags read past the allocation (the OOB-read amplification arm of DF-2484). Control (honest counts) clean. Fix (clamp counts to the PageLength-derived maximum) A/B: no guard smash. HW-gated at runtime (no LSI MPT controller on the guest).