git: 8b1c0ba41168 - stable/13 - scsi_cd: Improve TOC access validation

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Wed, 10 Nov 2021 14:23:06 UTC
The branch stable/13 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=8b1c0ba41168dfda015f58fc1f3d28208a0e9c66

commit 8b1c0ba41168dfda015f58fc1f3d28208a0e9c66
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2021-11-03 19:09:17 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2021-11-10 14:22:39 +0000

    scsi_cd: Improve TOC access validation
    
    1. During CD probing, we read the TOC header to find the number of
       entries, then read the TOC itself.  The header determines the number
       of entries, which determines the amount of data to read from the
       device into the softc in the CD_STATE_MEDIA_TOC_FULL state.  We
       hard-code a limit of 99 tracks (plus one for the lead-out) in the
       softc, but were not validating that the size reported by the media
       would fit in this hard-coded limit.  Kernel memory corruption could
       occur if not.[1]  Add validation to check this, and refuse to cache
       the TOC if it would not fit.
    
    2. The CDIOCPLAYTRACKS ioctl uses caller provided track numbers to index
       into the TOC, but we only validate the starting index.  Add
       validation of the ending index.
    
    Also, raise the hard-coded limit from 100 tracks to 170, per a
    suggestion from Ken.
    
    Reported by:    C Turt <ecturt@gmail.com> [1]
    Reviewed by:    ken, avg
    Sponsored by:   The FreeBSD Foundation
    
    (cherry picked from commit 6afabf00920fb8d41b8f013090f282c17c117efc)
---
 sys/cam/scsi/scsi_cd.c | 37 +++++++++++++++++++------------------
 1 file changed, 19 insertions(+), 18 deletions(-)

diff --git a/sys/cam/scsi/scsi_cd.c b/sys/cam/scsi/scsi_cd.c
index ee59da3e53c9..0c7068bf287e 100644
--- a/sys/cam/scsi/scsi_cd.c
+++ b/sys/cam/scsi/scsi_cd.c
@@ -136,9 +136,13 @@ typedef enum {
 #define ccb_state ppriv_field0
 #define ccb_bp ppriv_ptr1
 
+/*
+ * According to the MMC-6 spec, 6.25.3.2.11, the lead-out is reported by
+ * READ_TOC as logical track 170, so at most 169 tracks may be reported.
+ */
 struct cd_tocdata {
 	struct ioc_toc_header header;
-	struct cd_toc_entry entries[100];
+	struct cd_toc_entry entries[170];
 };
 
 struct cd_toc_single {
@@ -1596,12 +1600,13 @@ cddone(struct cam_periph *periph, union ccb *done_ccb)
 		}
 
 		/* Number of TOC entries, plus leadout */
-		num_entries = (toch->ending_track - toch->starting_track) + 2;
-		cdindex = toch->starting_track + num_entries -1;
+		num_entries = toch->ending_track - toch->starting_track + 2;
+		cdindex = toch->starting_track + num_entries - 1;
 
 		if ((done_ccb->ccb_h.ccb_state & CD_CCB_TYPE_MASK) ==
 		     CD_CCB_MEDIA_TOC_HDR) {
-			if (num_entries <= 0) {
+			if (num_entries <= 0 ||
+			    num_entries > nitems(softc->toc.entries)) {
 				softc->flags &= ~CD_FLAG_VALID_TOC;
 				bzero(&softc->toc, sizeof(softc->toc));
 				/*
@@ -1838,23 +1843,19 @@ cdioctl(struct disk *dp, u_long cmd, void *addr, int flag, struct thread *td)
 			 */
 			if (softc->flags & CD_FLAG_VALID_TOC) {
 				union msf_lba *sentry, *eentry;
+				struct ioc_toc_header *th;
 				int st, et;
 
-				if (args->end_track <
-				    softc->toc.header.ending_track + 1)
+				th = &softc->toc.header;
+				if (args->end_track < th->ending_track + 1)
 					args->end_track++;
-				if (args->end_track >
-				    softc->toc.header.ending_track + 1)
-					args->end_track =
-					    softc->toc.header.ending_track + 1;
-				st = args->start_track -
-					softc->toc.header.starting_track;
-				et = args->end_track -
-					softc->toc.header.starting_track;
-				if ((st < 0)
-				 || (et < 0)
-				 || (st > (softc->toc.header.ending_track -
-				     softc->toc.header.starting_track))) {
+				if (args->end_track > th->ending_track + 1)
+					args->end_track = th->ending_track + 1;
+				st = args->start_track - th->starting_track;
+				et = args->end_track - th->starting_track;
+				if (st < 0 || et < 0 ||
+				    st > th->ending_track - th->starting_track ||
+				    et > th->ending_track - th->starting_track) {
 					error = EINVAL;
 					cam_periph_unlock(periph);
 					break;