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)
PoC verification
Evidence pack
findings/poc/DF-1018 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
dadevice β 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[].
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)
-
Attacker input: A malicious SCSI device returns READ_CAPACITY with
length = 1(or any small value), settingsoftc->params.secsize = 1viadasetgeom()(no validation β see DF-1017). -
DAIOCTRIM ioctl at
scsi_da.c:463:byte_count = MIN(bytes_left, 0x7FFF8000);β caps b_bcount at ~2GB. Thenbp->b_bcount = byte_countanddev_dstrategy()enqueues the bio. -
dastart TRIM path at
scsi_da.c:1341:count = bp->b_bcount / softc->params.secsize;β with secsize=1,count = 0x7FFF8000 = 2147418112. -
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->dataisuint8_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 pasttrim_request.data[]intobios[]andda_softc. -
Merge check gap: The merge check at
scsi_da.c:1365-1367only bounds SUBSEQUENT bios (bio1 = bioq_first(...)), not the FIRST bio which is processed unconditionally in thewhile(1)loop. -
OOB DMA (info leak):
cam_fill_csio()atscsi_da.c:1378setsdxfer_len = ((ranges+63)/64)*512= ~262KB withdata_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).
Recommended fix
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_testablecompile 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.
No comments yet.