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)
PoC verification
Evidence pack
findings/poc/DF-1017 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 withlength=0. - This QEMU guest has NO
dadevice (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.
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)
-
Attacker input: A malicious SCSI device (USB/iSCSI/FC) responds to READ_CAPACITY with
length = 0in the 8-byte response struct. -
Unvalidated propagation: -
dadone()atsys/bus/cam/scsi/scsi_da.c:1831:block_size = scsi_4btoul(rdcap->length);β device-controlled, no validation. -dagetcapacity()atscsi_da.c:2228:block_len = scsi_4btoul(rcap->length);β same pattern, no validation. -dadone()atscsi_da.c:1853:dasetgeom(periph, block_size, maxsector);β passes 0 directly. -
Sink β secsize set to 0:
dasetgeom()atscsi_da.c:2289:c dp->secsize = block_len; /* block_len=0, NO zero check */ -
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()TRIMscsi_da.c:1341:count = bp->b_bcount / secsize-dastart()TRIMscsi_da.c:1342:lba = bio1->bio_offset / secsize-dastart()RWscsi_da.c:1490:KKASSERT(bio->bio_offset % secsize == 0)-dastart()RWscsi_da.c:1500:bio->bio_offset / secsize-dastart()RWscsi_da.c:1501:bp->b_bcount / secsize -
Automatic trigger: After
dasetgeom,dadonesetsinfo.d_media_blksize = softc->params.secsize(line 1863) and callsdisk_setdiskinfo(). This triggers automatic partition probing (disk_probe_sliceβdev_dstrategyβdastart), which divides bysecsize=0β #DE trap β kernel panic. No user interaction needed. -
Error path gap: At
scsi_da.c:1948, the error path setsinfo.d_media_blksize = 512but does NOT setsoftc->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 devlistshows only<QEMU QEMU DVD-ROM>(cd device, handled byscsi_cd.c, notscsi_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).
Recommended fix
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_testablecompile 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.
No comments yet.