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

TRIM req->data heap buffer overflow and OOB DMA read via device-controlled small secsize

Summary

dastart TRIM inner while(count>0) loop at scsi_da.c:1345-1360 writes req->data[ranges*8+0..7] incrementing ranges with NO bound against TRIM_MAX_RANGES=512. First bio processed unconditionally: count=bp->b_bcount/secsize (secsize device-controlled). DAIOCTRIM :463 b_bcount up to 0x7FFF8000; secsize=1 -> count=2147418112 -> 32768 ranges into data[4096] -> ~258KB heap overflow into da_softc. cam_fill_csio :1378 dxfer_len=((ranges+63)/64)*512=262656 with data_ptr=req->data (4096 valid) -> OOB DMA read ~258KB kernel heap sent to device (info leak). Merge check :1365-1367 only bounds SUBSEQUENT bios not the first. Trigger: malicious device secsize=1 + root DAIOCTRIM or large FS-trim. Fix: KKASSERT(ranges<TRIM_MAX_RANGES); min(dxfer_len,sizeof(data)).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1018 Β· 10 files
FileTypeDescriptionSize
harness.c trigger-source source-level TRIM overflow arithmetic 3.5 KB view raw
build.sh build-script cc -o harness harness.c 86 B view raw
run.sh run-script ./harness 87 B view raw
build.log build-log harness build output 13 B view raw
run.log run-log harness output: 258KB overflow, OOB DMA 756 B view raw
env.txt environment guest env: no da device 322 B view raw
fix.diff suggested-fix bound ranges against trim_max_ranges in inner loop 552 B view raw
fix_build.log fix-build-log combined-fix kernel build rc=0 191 B view raw
VERDICT.md verdict full source-level analysis 3.5 KB ↓ raw
README.md readme build/run instructions 778 B ↓ raw
README.md readme build/run instructions
↓ download raw

DF-1018 β€” dastart TRIM unbounded ranges heap overflow + OOB DMA

Build

./build.sh

(or: cc -o harness harness.c)

Run

./run.sh

(or: ./harness)

Expected output

The harness computes: with secsize=1 and b_bcount=0x7FFF8000 (DAIOCTRIM max), the TRIM inner loop produces 32768 ranges into data[512], overflowing by 258048 bytes. OOB DMA dxfer_len = 262144 bytes (info leak to device).

Preconditions (for runtime trigger)

  • A SCSI Direct Access device with secsize=1 (malicious READ_CAPACITY).
  • A TRIM request (root DAIOCTRIM or large FS-trim).
  • This QEMU guest has NO da device β€” source-level only.

Fix

fix.diff β€” bound ranges against softc->trim_max_ranges inside the inner while(count>0) loop to prevent overflow past data[].

VERDICT.md verdict full source-level analysis
↓ download raw

DF-1018 β€” dastart TRIM unbounded ranges heap overflow + OOB DMA β€” VERDICT

Verdict: INCONCLUSIVE (runtime) / CONFIRMED (source-level)

The bug is confirmed real by line-by-line source tracing. Runtime reproduction is blocked by missing hardware (no da device on this guest).

Mechanism (source-level trace)

  1. Attacker input: A malicious SCSI device returns READ_CAPACITY with length = 1 (or any small value), setting softc->params.secsize = 1 via dasetgeom() (no validation β€” see DF-1017).

  2. DAIOCTRIM ioctl at scsi_da.c:463: byte_count = MIN(bytes_left, 0x7FFF8000); β€” caps b_bcount at ~2GB. Then bp->b_bcount = byte_count and dev_dstrategy() enqueues the bio.

  3. dastart TRIM path at scsi_da.c:1341: count = bp->b_bcount / softc->params.secsize; β€” with secsize=1, count = 0x7FFF8000 = 2147418112.

  4. Unbounded inner loop at scsi_da.c:1345-1360: c while (count > 0) { int c = min(count, 0xffff); int off = ranges * 8; req->data[off+0..7] = ...; // writes into fixed-size data[4096] ranges++; // NO bound check against TRIM_MAX_RANGES } req->data is uint8_t data[TRIM_MAX_RANGES * 8] = data[512*8] = 4096 bytes (scsi_da.c:135, TRIM_MAX_BLOCKS=8, TRIM_MAX_RANGES=512). With count=2.1B, the loop produces ~32768 ranges, writing 262144 bytes into a 4096-byte array β†’ 258KB heap overflow past trim_request.data[] into bios[] and da_softc.

  5. Merge check gap: The merge check at scsi_da.c:1365-1367 only bounds SUBSEQUENT bios (bio1 = bioq_first(...)), not the FIRST bio which is processed unconditionally in the while(1) loop.

  6. OOB DMA (info leak): cam_fill_csio() at scsi_da.c:1378 sets dxfer_len = ((ranges+63)/64)*512 = ~262KB with data_ptr = req->data (only 4096 valid). The SCSI controller DMAs ~258KB of kernel heap content to the device β€” kernel memory disclosure to a malicious device.

Exploit chain

The overflow writes ~258KB past trim_request.data[], corrupting: - trim_request.bios[] (512 pointers at offset 4096) - Subsequent da_softc fields - Adjacent kernel heap objects

With heap grooming, this is a kernel heap write primitive. However, the trigger requires a malicious SCSI device with secsize=1, which is a device-attacker scenario (not local user). The trigger is also reachable via root DAIOCTRIM with a malicious device. This is a device-attacker exploit, not a local privilege escalation from an unprivileged user.

Why runtime reproduction is blocked

No da device on this QEMU guest (only DVD-ROM). The TRIM code path in scsi_da.c requires a Direct Access SCSI disk.

PoC changes

Created harness.c β€” arithmetic demonstration showing 32768 ranges into data[512] = 258048 byte overflow, plus OOB DMA dxfer_len calculation. Built and verified on the guest.

Fix validation

The fix (fix.diff) adds if (ranges >= softc->trim_max_ranges) break; at the top of the inner while(count>0) loop. Applied + compiled in the combined-fix kernel (#1, rc=0, boots cleanly). Runtime before/after not possible (no da device).

fix_status: not_testable (diff applies + compiles; runtime blocked by missing HW).

Bound ranges against softc->trim_max_ranges inside the inner loop:

if (ranges >= softc->trim_max_ranges)
    break;

Also clamp dxfer_len at cam_fill_csio to sizeof(req->data). Matches the finding proposal. The inner-loop bound is the root-cause fix.

Fix verification

not_testable

compile validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. dastart TRIM unbounded ranges -> 258KB heap overflow + OOB DMA. Needs secsize=1 + da device.