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

cdreaddvdstructure integer underflow in length math leaks kernel heap to userspace

Summary

cdreaddvdstructure default branch at scsi_cd.c:4288 dvdstruct->length = length - ccb->csio.resid - sizeof(header). length int, resid u_int32_t, sizeof size_t -> evaluated as size_t. Malicious device returns short data resid==length -> 0-4 = (size_t)-4 -> widened to u32 0xFFFFFFFC. :4295 bcopy(databuf+4, dvdstruct->data, min(sizeof(dvdstruct->data)=2048, dvdstruct->length)). For DVD_STRUCT_BCA databuf=192 bytes -> reads 2048 bytes from databuf+4 where only 188 valid -> 1860 bytes kernel heap leaked to userspace via ioctl. Repeatable indefinitely -> KASLR bypass/slab layout disclosure. Fix: if(resid>length || (length-resid)<sizeof(header)) dvdstruct->length=0; else compute normally; clamp bcopy to min(dvdstruct->length, length-sizeof(header)).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0989 Β· 7 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix: clamp length computation in cdreaddvdstructure() default branch 1.6 KB 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 with type-promotion proof of the underflow 4.9 KB ↓ raw
README.md readme summary 1.2 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-0989 β€” cdreaddvdstructure integer underflow in length math

Summary

cdreaddvdstructure() at sys/bus/cam/scsi/scsi_cd.c:4288 (default branch of the result switch) computes:

dvdstruct->length = length - ccb->csio.resid - sizeof(header);

With length int, resid u_int32_t, sizeof(header) size_t β€” promoted to size_t. A malicious device returning short data (resid == length) yields (size_t)-4, truncated to u_int32_t 0xFFFFFFFC. The subsequent bcopy(databuf+4, dvdstruct->data, min(2048, 0xFFFFFFFC)) reads 2048 bytes from databuf+4, but databuf was only length bytes β†’ kernel heap OOB read leaked to userspace via DVDIOCREADSTRUCTURE ioctl.

Reachability: Requires operator group membership to open /dev/cd0 PLUS a malicious device returning short READ_DVD_STRUCTURE data. maxx is not in operator group; default QEMU CD-ROM returns well-formed data. Not triggerable by an unprivileged user.

Files

  • fix.diff β€” git-apply-able fix: clamp the length computation to avoid underflow
  • fix_build.log β€” kernel build output showing scsi_cd.c compiles cleanly
  • env.txt β€” guest environment
  • VERDICT.md β€” detailed analysis with type-analysis proof
  • manifest.json β€” artifact catalog
VERDICT.md verdict detailed analysis with type-promotion proof of the underflow
↓ download raw

DF-0989 β€” cdreaddvdstructure integer underflow in length math

Verdict

NOT REPRODUCED at runtime on the default guest β€” bug is real as a code pattern (would leak up to 2048 bytes of kernel heap to userspace), but triggering requires a malicious SCSI/iSCSI CD-ROM returning short READ_DVD_STRUCTURE data with high resid. Defense-in-depth fix authored + compiled.

Mechanism

cdreaddvdstructure() at sys/bus/cam/scsi/scsi_cd.c:4107 allocates a buffer of length bytes and issues a READ_DVD_STRUCTURE SCSI command:

/* scsi_cd.c:4207-4208 */
if (length != 0)
    databuf = kmalloc(length, M_DEVBUF, M_INTWAIT | M_ZERO);

After the command completes, the default branch of the result switch (for DVD_STRUCT_BCA, DVD_STRUCT_DISCKEY, DVD_STRUCT_MANUFACT, etc.) computes the user-visible length:

/* scsi_cd.c:4288-4289 */
dvdstruct->length = length - ccb->csio.resid -
    sizeof(struct scsi_read_dvd_struct_data_header);

Type analysis: - length is int (declared line 4113). - ccb->csio.resid is u_int32_t. - sizeof(struct scsi_read_dvd_struct_data_header) is size_t. - The expression is promoted to size_t for the subtraction. - dvdstruct->length is u_int32_t (see sys/sys/dvdio.h:64).

If a malicious device returns short data with resid == length, then length - resid - sizeof(header) = 0 - 4 = (size_t)-4 = 0xFFFFFFFFFFFFFFFC, truncated to u_int32_t 0xFFFFFFFC.

The subsequent bcopy:

/* scsi_cd.c:4295-4297 */
bcopy(databuf + sizeof(struct scsi_read_dvd_struct_data_header),
      dvdstruct->data,
      min(sizeof(dvdstruct->data), dvdstruct->length));
  • sizeof(dvdstruct->data) is 2048 (see sys/sys/dvdio.h:65).
  • min(2048, 0xFFFFFFFC) evaluates to 2048.
  • bcopy reads 2048 bytes starting at databuf + 4. But databuf was allocated with only length bytes (which for DVD_STRUCT_BCA is sizeof(struct scsi_read_dvd_struct_data_bca), just a few bytes).

β†’ Out-of-bounds read of the kernel heap (up to ~2048 bytes past databuf) copied into dvdstruct->data, which is then returned to userspace via the DVDIOCREADSTRUCTURE ioctl. This is a kernel heap info leak.

Reachability analysis

cdreaddvdstructure() is reached from cdioctl(DVDIOCREADSTRUCTURE) at sys/bus/cam/scsi/scsi_cd.c:2751-2757. To trigger:

  1. Open /dev/cd0 (requires operator group membership or root).
  2. Issue ioctl(fd, DVDIOCREADSTRUCTURE, &dvdstruct) with format set to one of the default-branch formats (e.g. DVD_STRUCT_BCA).
  3. The underlying device must return short data with high resid.

On the audit guest:

  • /dev/cd0 is crw-r----- root operator β€” maxx (uid 1001, only group maxx) cannot open it, so maxx cannot issue the ioctl at all.
  • Even with access, the virtual QEMU PIIX CD-ROM returns well-formed READ_DVD_STRUCTURE data. The malicious short-data precondition does not arise.

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

  • Membership in the operator group (or root), AND
  • A malicious iSCSI / USB CD-ROM returning short READ_DVD_STRUCTURE data.

The operator group requirement alone means this is not an unprivileged-user trigger; even with operator membership, a malicious device is still required. 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 (operator-group + malicious device required). The primitive is a kernel heap OOB read (info leak); a chain to uid=0 would require pairing with a separate write primitive, which is out of scope for this single finding. The right action is to fix the underflow.

Fix

The fix in fix.diff rewrites the length computation with explicit clamping that cannot underflow:

size_t hdr_sz = sizeof(struct scsi_read_dvd_struct_data_header);
size_t avail  = (size_t)length;
size_t resid  = ccb->csio.resid;
size_t copied;

if (avail > resid)
    copied = avail - resid;
else
    copied = 0;
if (copied > hdr_sz)
    copied -= hdr_sz;
else
    copied = 0;
if (copied > sizeof(dvdstruct->data))
    copied = sizeof(dvdstruct->data);

dvdstruct->length = copied;
bcopy(databuf + sizeof(struct scsi_read_dvd_struct_data_header),
      dvdstruct->data, copied);

This guarantees dvdstruct->length is always a valid in-range value and the bcopy never reads past databuf + length.

Fix validation

Compile-only. The bug requires operator-group access + a malicious device, neither present on the guest. fix_status: not_testable. Validated:

  • fix.diff applies cleanly with patch -p1 (rc=0).
  • Built as part of a single kernel with the DF-0988 fix; scsi_cd.c compiled under -Werror (no warnings, no errors); kernel linked successfully. See fix_build.log.

PoC changes

The PoC directory was seeded empty. No runtime trigger is possible on the default guest. 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. cdreaddvdstructure length underflow -> 2048B heap OOB read. Needs operator+malicious device. Compile validated.