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

dpt_detect_cache log-page walk bounded by device-fed 16-bit page length (up to 65539) instead of the 512 bytes actually DMA'd β€” OOB kernel read up to ~64KB past the transfer buffer

Summary

The HBA is asked for exactly 512 bytes but the walk bound is the controller-written header length, and the NEXT macro reads param[3] before the bound check. A hostile/faulty controller reporting a large length walks up to buff+65539, reading kernel memory the device never wrote; values only feed cache_type (no disclosure) - ceiling is a page-fault panic at attach. Arithmetic proof with verbatim macros on the guest (16383 accepted steps; 15361 reads past the whole 4096-byte sg page). Clamp-to-512 fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4244 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.3 KB ↓ raw
VERDICT.md β€” 1.5 KB ↓ raw
dpt_df4244_walk.c β€” 4.5 KB view raw
build.sh β€” 104 B view raw
run.sh β€” 98 B view raw
build.log β€” 92 B view raw
run.log β€” 3.3 KB view raw
env.txt β€” 1.0 KB view raw
fix.diff β€” 597 B view raw
verdict.json β€” 2.0 KB view raw
manifest.json β€” 767 B view raw

DF-4244 β€” dpt_detect_cache() log-page walk bounded by device-fed length, not by the 512-byte transfer

The HBA is asked for exactly 512 bytes of log page (cp_datalen = htonl(512), dpt_scsi.c:618), DMA'd into buff = &dccb[1]. The parameter walk's bound, however, is bytes = DPT_HCP_LENGTH(buff) (dpt_scsi.c:648) β€” the page header's 16-bit length field written by the controller (dpt.h:341), i.e. up to 65539 β€” and each DPT_HCP_NEXT(param) step reads param[3] (another device-fed byte) before the bound check at dpt_scsi.c:663-669. A hostile/faulty controller that reports a large page length walks param up to ~64 KB past the 512-byte transfer, reading kernel memory the device never wrote (stale sg-page contents, then adjacent kernel memory past the page). The values only influence cache_type (no direct disclosure), but a walk into an unmapped page faults at attach β†’ kernel panic (DoS ceiling).

Reproduce (arithmetic proof, no hardware needed)

  • build: cc -O2 -Wall -o dpt_df4244_walk dpt_df4244_walk.c
  • run: ./dpt_df4244_walk
  • expected (guest transcript in run.log): bytes=65539, DMA=512, page=4096 16383 accepted steps, farthest offset buff+65536 16257 iterations read outside the 512-byte DMA buffer 15361 of those read past the entire 4096-byte sg page
VERDICT.md
↓ download raw

DF-4244 verdict β€” UNTESTED at kernel level (hardware-gated); walk arithmetic PROVEN on guest

Reproduced?

Harness: YES β€” dpt_df4244_walk.c embeds the DPT_HCP_* macros verbatim (dpt.h:341-344) and the exact loop (dpt_scsi.c:663-669) over a faithful model (512-byte DMA buffer inside a 4096-byte bus_dmamem page). Guest run (run.log):

walk stopped after 16383 accepted steps at buff+65536
reads OUTSIDE the 512-byte DMA buffer : 16257 iterations
  of which past the whole 4096B sg page: 15361
bound used by driver : &buff[bytes] = buff+65539   <- device-fed
bound that is safe   : &buff[512]                  <- actual transfer

Kernel-level: UNTESTED β€” needs a DPT controller returning a bogus page length; none in the guest (env.txt).

Impact

OOB kernel read (up to ~65 KB past the transfer), values never copied to userspace (only cache_type decisions, dpt_scsi.c:671-681) β†’ no leak; the realistic ceiling is a page-fault panic during attach from reading unmapped kernel VA, plus unbounded loop time. Severity Low.

Fix validation

Not possible without hardware; fix.diff clamps bytes to the 512 bytes actually requested.

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 authored against the read-only sys/ tree; kernel-level fix validation impossible without DPT SmartRAID hardware (none in guest, no QEMU emulation exists).

fix.diff
↓ fix.diffper-fix-DF-4244

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (DF-4244 block); dpt_scsi.c:618,648,663-669; dpt.h:341-344

PoC changes

No seed PoC existed; evidence authored from scratch in this run.

Verified recommended fix

Clamp bytes to the 512 bytes actually DMA'd before the walk (fix.diff).

Verdict

Untested at kernel level (no DPT hardware; no QEMU emulation). Walk arithmetic proven on the guest with the verbatim macros: with a device-fed length of 65539 the loop accepts pointers to buff+65536 while only 512 bytes were DMA'd β€” 15361 iterations read past the entire sg page. Bound should be the transfer size, not the device-fed header length.