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

cdsize accepts blksize=0 from READ_CAPACITY, cdstart divide-by-zero panics on read

Summary

cdsize at scsi_cd.c:3058 softc->params.blksize=scsi_4btoul(rcap_buf->length) no lower bound. :3068 only narrows 2049-2352 -> 2048, does NOT catch 0. cdcheckmedia succeeds (CD_FLAG_VALID_MEDIA). First read: cdstart :1530 KKASSERT(bio_offset % blksize == 0) -> #DE; :1540/:1541 bio_offset/blksize -> #DE -> kernel panic. Malicious SCSI/iSCSI CD-ROM READ_CAPACITY length=0. mount succeeds, any read panics. Fix: if(blksize==0 || !power_of_2) blksize=2048.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0988 Β· 7 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix: reject media when blksize==0 in cdsize() 891 B view raw
fix_build.log build-log scsi_cd.c compiles under -Werror with the fix applied 1.1 KB view raw
env.txt environment uname, kern.version 260 B view raw
VERDICT.md verdict detailed analysis: mechanism, reachability, fix 4.6 KB ↓ raw
README.md readme summary 1.0 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme summary
↓ download raw

DF-0988 β€” cdsize accepts blksize=0 from READ_CAPACITY

Summary

cdsize() at sys/bus/cam/scsi/scsi_cd.c:3058 reads softc->params.blksize = scsi_4btoul(rcap_buf->length) with no lower-bound check. The narrowing at line 3068 only catches 2049-2352 β†’ 2048; it does not catch blksize=0. On the next read, cdstart() at line 1530 asserts bio_offset % blksize == 0 and divides by blksize at lines 1540/1541 β†’ kernel divide-by-zero panic.

Reachability: Requires a malicious SCSI/iSCSI CD-ROM that returns READ_CAPACITY with length=0. On the default guest, /dev/cd0 is root:operator mode 0640 (maxx cannot open) and the virtual QEMU CD-ROM returns well-formed data. Not triggerable by an unprivileged user.

Files

  • fix.diff β€” git-apply-able fix: reject media if blksize==0
  • fix_build.log β€” kernel build output showing scsi_cd.c compiles cleanly under -Werror with the fix applied
  • env.txt β€” guest environment
  • VERDICT.md β€” detailed analysis
  • manifest.json β€” artifact catalog
VERDICT.md verdict detailed analysis: mechanism, reachability, fix
↓ download raw

DF-0988 β€” cdsize accepts blksize=0 from READ_CAPACITY β†’ divide-by-zero panic

Verdict

NOT REPRODUCED at runtime on the default guest β€” bug is real as a code pattern, but triggering requires a malicious SCSI/iSCSI CD-ROM that returns READ_CAPACITY data with length=0. Defense-in-depth fix authored + compiled.

Mechanism

cdsize() at sys/bus/cam/scsi/scsi_cd.c:3026 reads the result of a SCSI READ_CAPACITY command into rcap_buf and stores the block size without a lower bound:

/* scsi_cd.c:3058-3069 */
softc->params.disksize = scsi_4btoul(rcap_buf->addr) + 1;
softc->params.blksize  = scsi_4btoul(rcap_buf->length);
/*
 * SCSI-3 mandates that the reported blocksize shall be 2048.
 * Older drives sometimes report funny values, trim it down to
 * 2048, or other parts of the kernel will get confused.
 */
if (softc->params.blksize > 2048 && softc->params.blksize <= 2352)
    softc->params.blksize = 2048;

The narrowing at line 3068 only catches the (2048, 2352] range; a malicious device reporting length = 0 (or any non-2048-family value not in 2049-2352) sets softc->params.blksize = 0.

On the next read, cdstart() at sys/bus/cam/scsi/scsi_cd.c:1530 asserts and divides by this zero:

/* scsi_cd.c:1530 */
KKASSERT(bio->bio_offset % softc->params.blksize == 0);
/* scsi_cd.c:1540-1541 */
bio->bio_offset / softc->params.blksize,
bp->b_bcount / softc->params.blksize,

β†’ kernel panic (divide-by-zero / INVARIANTS assertion). Mount succeeds (cdcheckmedia reports CD_FLAG_VALID_MEDIA), the first read panics.

Reachability analysis (the crucial question)

cdsize() is called from cdcheckmedia() (sys/bus/cam/scsi/scsi_cd.c:1049) on the CD periph's media-validation path. It is reached when:

  1. A SCSI CD-ROM (or ATAPI CD via atapi-cam) reports media present, and
  2. The kernel re-validates the media (mount, open, media-change event).

The READ_CAPACITY data comes from the device firmware. To trigger the panic, the device must return rcap_buf->length == 0. This is device-controlled data; the kernel cannot influence it.

On the audit guest:

  • /dev/cd0 is crw-r----- root operator β€” maxx (uid 1001, only group maxx) cannot open it. camcontrol devlist requires operator membership maxx lacks (couldn't open /dev/xpt0: Permission denied).
  • The virtual QEMU PIIX CD-ROM behind /dev/cd0 returns well-formed READ_CAPACITY data (blksize=2048). No malicious device is attached.

For an unprivileged user to trigger this on a real deployment, they would need:

  • A malicious iSCSI target presenting a CD-ROM LUN that returns READ_CAPACITY with length=0 (requires the admin to have configured the iSCSI initiator against the attacker's target, OR the target to be compromised), or
  • A malicious USB CD-ROM / SCSI device physically plugged in (physical access or social engineering).

Both preconditions are realistic for some deployments (the audit treats malicious filesystem images and compromised storage as in-scope), but neither is present on the default audit guest. This is a valid hard blocker: the bug is real but cannot be triggered by an unprivileged syscall on the default kernel + default devices.

Exploit chain

Not applicable / blocked by valid hard blocker (no malicious device on default guest). The primitive is a kernel divide-by-zero β†’ DoS/panic; there is no privilege-escalation path because no attacker-controlled memory corruption occurs (just an arithmetic fault). Fixing the validation is the right action.

Fix

The fix in fix.diff adds an explicit blksize==0 (and lower-bound) check in cdsize() that refuses the media with ENXIO rather than letting cdstart() divide by zero:

if (softc->params.blksize == 0) {
    xpt_print(periph->path, "cdsize: device reported blocksize of 0, "
        "rejecting media\n");
    error = ENXIO;
}

Fix validation

Compile-only. The bug requires a malicious device not present on the guest, so a runtime behavior-change cannot be demonstrated on either the patched or unpatched kernel (fix_status: not_testable). Validated:

  • fix.diff applies cleanly with patch -p1 (rc=0).
  • A single-fix kernel was built with make -j6 nativekernel KERNCONF=X86_64_GENERIC; scsi_cd.c compiled under -Werror (no warnings, no errors); the kernel linked successfully (/usr/obj/usr/src/sys/X86_64_GENERIC/kernel.stripped, today's build). See fix_build.log.

PoC changes

The PoC directory was seeded empty. No runtime trigger is possible on the default guest (no accessible /dev/cd0, no malicious device). Authored fix.diff and this VERDICT.md.

Fix verification

not_testable

compile validated

kernel build rc=0 -Werror

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. cdsize blksize=0 from READ_CAPACITY -> div-by-zero. Needs malicious CD device. Compile validated.