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

1-byte heap OOB read in OUT-data transfer path of imm_do_scsi

Summary

In data-transfer loop OUT-direction branch (device status 0x88 wants more data) selects transfer length of 2 whenever fewer than VP0_SECTOR_SIZE(512) bytes remain. Preceding guard at :739 only guarantees at least 1 byte remains. When exactly 1 byte left imm_outstr(vpo &buffer[*count] 2) reads 2 bytes - 1-byte OOB read of adjacent kernel heap. Leaked byte clocked out to parallel-port device. Sibling IN-direction branch (:751-755) correctly uses fallback of 1 confirming 2 is defect. OUT path reads in 2-byte microsequence pairs. Attacker: malicious parallel-port ZIP/Matchmaker device returns 0x88 status forcing OUT branch with odd dxfer_len (INQUIRY allocation length 255). Impact: 1 byte adjacent kernel heap leaked per CCB. Physical access required. [VERIFIED pass2: reproduced - OUT branch len 2 with 1 byte left clocks guard byte buffer 255 5a to the peripheral count 256; fallback-1 fix A/B zero OOB]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2529 Β· 8 files
FileTypeDescriptionSize
README.md β€” 882 B ↓ raw
VERDICT.md β€” 1.7 KB ↓ raw
build.sh β€” 99 B view raw
run.log β€” 359 B view raw
env.txt β€” 963 B view raw
fix.diff β€” 464 B view raw
manifest.json β€” 659 B view raw
verdict.json β€” 2.0 KB view raw

DF-2529 -- 1-byte heap OOB read in imm_do_scsi OUT-data path

Verification used the shared rig tree at /root/rigs on the audit guest (see manifest). The rig for this finding: dfv_vpo (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_vpo.ko; sysctl kern.dfv_vpo_run=0; sysctl -n kern.dfv_vpo_status

Expected

run.log: OOB reads=1 byte=0x5a at buffer[255]; count=256; fixed 0.

Fix validation

fix.diff uses 1 as the short OUT fallback (matches the IN branch); A/B zero OOB.

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

VERDICT.md
↓ download raw

DF-2529 verdict -- 1-byte heap OOB read in imm_do_scsi OUT-data path

status: reproduced reproduced: 1 impact: leak confidence: certain attempts: 2 runtime_sec: 700

The verbatim imm_do_scsi OUT branch picks a 2-byte transfer whenever fewer than VP0_SECTOR_SIZE bytes remain (immio.c:743-746); the :739 guard only guarantees 1 byte. With blen=255 and a scripted malicious ZIP+ (status 0x88 until the end), the loop ran to count=256 and the final imm_outstr(&buffer[254], 2) clocked out the guard byte at buffer[255] (0x5A) - one byte past the transfer buffer, captured as OUT[off=255]=5a OOB-READ. The sibling IN branch uses a fallback of 1, confirming the 2 is the defect. Fixed source (fallback 1) A/B: OOB reads=0, transfer completes.

Evidence (decisive lines)

DF2529: OUT transfer blen=255, buffer[255]=0x5A guard; count=256 ret=0 error=0 DF2529: OOB reads=1 byte=0x5a at buffer[255] (PAST the transfer buffer) DF2529: log: OUT[off=255]=5a OOB-READ DF2529: RESULT=1-BYTE HEAP OOB READ clocked to the peripheral (len=2 picked with exactly 1 byte remaining) --- fixed source A/B --- DF2529: OOB reads=0 ... RESULT=no OOB

Exploit chain / privesc assessment

malicious parallel-port peripheral (physical access); 1 adjacent kernel heap byte disclosed to the device per transfer.

Fix + A/B

fix.diff uses 1 as the short OUT fallback (matches the IN branch); A/B zero OOB.

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-2529

Confirmed kernel references

Detail

Exploit chain

malicious parallel-port peripheral (physical access); 1 adjacent kernel heap byte disclosed to the device per transfer.

Evidence (decisive lines)

run.log: OOB reads=1 byte=0x5a at buffer[255]; count=256; fixed 0.

PoC changes

scripted peripheral at the ppb transport boundary (stateful imm_wait); first scripted version answered the command phase with the data-phase status and was fixed.

Verified recommended fix

use 1 (not 2) as the short OUT transfer fallback

Verdict

The verbatim imm_do_scsi OUT branch picks a 2-byte transfer whenever fewer than VP0_SECTOR_SIZE bytes remain (immio.c:743-746); the :739 guard only guarantees 1 byte. With blen=255 and a scripted malicious ZIP+ (status 0x88 until the end), the loop ran to count=256 and the final imm_outstr(&buffer[254], 2) clocked out the guard byte at buffer[255] (0x5A) - one byte past the transfer buffer, captured as OUT[off=255]=5a OOB-READ. The sibling IN branch uses a fallback of 1, confirming the 2 is the defect. Fixed source (fallback 1) A/B: OOB reads=0, transfer completes.