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

Untrusted READ_CAPACITY block_len=0 causes kernel divide-by-zero panic (secsize used as divisor without validation)

Summary

dasetgeom at scsi_da.c:2289 dp->secsize=block_len with NO zero validation. block_len from scsi_4btoul(rdcap->length) device-controlled at :1831/:1850/:2228/:2263. secsize used as divisor at 7 sites: dadump :797-798, dastart trim :1341-1342, dastart rw :1490/:1500/:1501. Malicious SCSI device (USB/iSCSI/FC) READ_CAPACITY length=0 -> secsize=0 -> automatic partition probe (disk_setdiskinfo->disk_probe_slice->l32_readdisklabel->dev_dstrategy->dastart) divides by 0 -> #DE trap -> panic. No user interaction. Also error path :1948 sets info.d_media_blksize=512 but NOT softc->params.secsize -> stays 0. Fix: if(block_len<512||block_len>MAXPHYS) block_len=512 in dasetgeom.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1017 Β· 10 files
FileTypeDescriptionSize
harness.c trigger-source source-level divide-by-zero demonstration 3.3 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 confirming 7 divide-by-zero sites 828 B view raw
env.txt environment guest env: no da device, GENERIC kernel 322 B view raw
fix.diff suggested-fix validate block_len in dasetgeom, fall back to 512 622 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.8 KB ↓ raw
README.md readme build/run instructions 690 B ↓ raw
README.md readme build/run instructions
↓ download raw

DF-1017 β€” READ_CAPACITY block_len=0 divide-by-zero

Build

./build.sh

(or: cc -o harness harness.c)

Run

./run.sh

(or: ./harness)

Expected output

The harness demonstrates that secsize=0 (from a malicious READ_CAPACITY response) causes division-by-zero at 7 kernel sites in dastart()/dadump(). The kernel would panic with a fatal #DE trap.

Preconditions (for runtime trigger)

  • A SCSI Direct Access (da) device returning READ_CAPACITY with length=0.
  • This QEMU guest has NO da device (only a DVD-ROM) β€” source-level only.

Fix

fix.diff β€” validate block_len in dasetgeom(): reject 0 and out-of-range values, falling back to 512.

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

DF-1017 β€” READ_CAPACITY block_len=0 divide-by-zero β€” 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: this QEMU guest has no SCSI Direct Access (da) device, only a DVD-ROM (cd0). The da driver code path is compiled into the GENERIC kernel but not exercised at runtime.

Mechanism (source-level trace)

  1. Attacker input: A malicious SCSI device (USB/iSCSI/FC) responds to READ_CAPACITY with length = 0 in the 8-byte response struct.

  2. Unvalidated propagation: - dadone() at sys/bus/cam/scsi/scsi_da.c:1831: block_size = scsi_4btoul(rdcap->length); β€” device-controlled, no validation. - dagetcapacity() at scsi_da.c:2228: block_len = scsi_4btoul(rcap->length); β€” same pattern, no validation. - dadone() at scsi_da.c:1853: dasetgeom(periph, block_size, maxsector); β€” passes 0 directly.

  3. Sink β€” secsize set to 0: dasetgeom() at scsi_da.c:2289: c dp->secsize = block_len; /* block_len=0, NO zero check */

  4. Divide-by-zero sites (7 locations): - dadump() scsi_da.c:797: ap->a_offset / secsize - dadump() scsi_da.c:798: ap->a_length / secsize - dastart() TRIM scsi_da.c:1341: count = bp->b_bcount / secsize - dastart() TRIM scsi_da.c:1342: lba = bio1->bio_offset / secsize - dastart() RW scsi_da.c:1490: KKASSERT(bio->bio_offset % secsize == 0) - dastart() RW scsi_da.c:1500: bio->bio_offset / secsize - dastart() RW scsi_da.c:1501: bp->b_bcount / secsize

  5. Automatic trigger: After dasetgeom, dadone sets info.d_media_blksize = softc->params.secsize (line 1863) and calls disk_setdiskinfo(). This triggers automatic partition probing (disk_probe_slice β†’ dev_dstrategy β†’ dastart), which divides by secsize=0 β†’ #DE trap β†’ kernel panic. No user interaction needed.

  6. Error path gap: At scsi_da.c:1948, the error path sets info.d_media_blksize = 512 but does NOT set softc->params.secsize, so it stays 0 β€” subsequent I/O still divides by zero.

Exploit chain

Not applicable β€” this is a divide-by-zero (DoS), not memory corruption. A malicious SCSI device causes an immediate kernel panic. No privilege escalation primitive. Impact ceiling: kernel DoS / panic from a malicious physical or virtual device.

Why runtime reproduction is blocked

  • This QEMU guest has no SCSI Direct Access disk. camcontrol devlist shows only <QEMU QEMU DVD-ROM> (cd device, handled by scsi_cd.c, not scsi_da.c).
  • Adding a SCSI disk would require modifying the QEMU command line, which is outside the scope of this audit guest.
  • The bug is in code compiled into GENERIC but not reachable at runtime without a da-class device.

PoC changes

Created harness.c β€” a source-level arithmetic demonstration showing that secsize=0 triggers division-by-zero at all 7 sink sites. Built and verified on the guest (compiles cleanly, produces expected output).

Fix validation

The fix (fix.diff) adds validation in dasetgeom(): if block_len < 512 or block_len > MAXPHYS, fall back to 512. Applied + compiled successfully in a combined-fix kernel build (#1, rc=0, boots cleanly). Runtime before/after testing not possible (no da device to trigger the bug).

fix_status: not_testable (diff applies + compiles; runtime test blocked by missing HW β€” the da code path cannot be exercised on this guest).

Add block_len validation at the top of dasetgeom() (scsi_da.c:2286):

if (block_len < 512 || block_len > MAXPHYS)
    block_len = 512;

This matches the finding proposal. The fix is at the single point where secsize is set, protecting all 7 divisor sites.

Fix verification

not_testable

compile validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. dasetgeom secsize=block_len no zero check -> #DE at 7 sites. No da device on guest.