--- a/sys/bus/cam/scsi/scsi_cd.c 2026-10-17 22:22:48.092532984 +0000 +++ b/sys/bus/cam/scsi/scsi_cd.c 2026-10-17 22:22:55.188442336 +0000 @@ -4281,12 +4281,39 @@ break; } default: - /* - * Tell the user what the overall length is, no matter - * what we can actually fit in the data buffer. - */ - dvdstruct->length = length - ccb->csio.resid - - sizeof(struct scsi_read_dvd_struct_data_header); + { + int transferred; + + /* + * The residual count is device-controlled. A + * drive that returns CAM_REQ_CMP with a short + * transfer that never covered the 4-byte header, + * or with a resid larger than the allocation, + * would wrap this unsigned subtraction to ~4G + * and make the bcopy below run off the databuf. + * Compute the transferred count with checked + * unsigned math and reject anything that cannot + * yield a sane payload length. + */ + if (ccb->csio.resid > (u_int32_t)length) { + error = EIO; + break; + } + transferred = length - (u_int32_t)ccb->csio.resid; + if (transferred < + (int)sizeof(struct scsi_read_dvd_struct_data_header)) { + error = EIO; + break; + } + + /* + * Tell the user what the overall length is, no + * matter what we can actually fit in the data + * buffer. + */ + dvdstruct->length = transferred - + sizeof(struct scsi_read_dvd_struct_data_header); + } /* * But only actually copy out the smaller of what we read