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)
PoC verification
Evidence pack
findings/poc/DF-0989 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 underflowfix_build.logβ kernel build output showingscsi_cd.ccompiles cleanlyenv.txtβ guest environmentVERDICT.mdβ detailed analysis with type-analysis proofmanifest.jsonβ artifact catalog
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)is2048(seesys/sys/dvdio.h:65).min(2048, 0xFFFFFFFC)evaluates to2048.- bcopy reads 2048 bytes starting at
databuf + 4. Butdatabufwas allocated with onlylengthbytes (which for DVD_STRUCT_BCA issizeof(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:
- Open
/dev/cd0(requiresoperatorgroup membership or root). - Issue
ioctl(fd, DVDIOCREADSTRUCTURE, &dvdstruct)withformatset to one of the default-branch formats (e.g.DVD_STRUCT_BCA). - The underlying device must return short data with high resid.
On the audit guest:
/dev/cd0iscrw-r----- root operatorβmaxx(uid 1001, only groupmaxx) cannot open it, somaxxcannot 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
operatorgroup (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.diffapplies cleanly withpatch -p1(rc=0).- Built as part of a single kernel with the DF-0988 fix;
scsi_cd.ccompiled under-Werror(no warnings, no errors); kernel linked successfully. Seefix_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_testablecompile 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.
No comments yet.