Module Name:    src
Committed By:   riastradh
Date:           Sun Apr 17 13:17:30 UTC 2022

Modified Files:
        src/sys/dev/usb: uvideo.c

Log Message:
uvideo(4): Parse descriptors more robustly.

Validate lengths and types before barging ahead.

Not sure exactly which missing validation syzbot tripped on here, but
I'm pretty sure I caught all the cases.

Reported-by: syzbot+60f0a25c077b67547...@syzkaller.appspotmail.com


To generate a diff of this commit:
cvs rdiff -u -r1.77 -r1.78 src/sys/dev/usb/uvideo.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/usb/uvideo.c
diff -u src/sys/dev/usb/uvideo.c:1.77 src/sys/dev/usb/uvideo.c:1.78
--- src/sys/dev/usb/uvideo.c:1.77	Sun Apr 17 13:17:19 2022
+++ src/sys/dev/usb/uvideo.c	Sun Apr 17 13:17:30 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: uvideo.c,v 1.77 2022/04/17 13:17:19 riastradh Exp $	*/
+/*	$NetBSD: uvideo.c,v 1.78 2022/04/17 13:17:30 riastradh Exp $	*/
 
 /*
  * Copyright (c) 2008 Patrick Mahoney
@@ -42,7 +42,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: uvideo.c,v 1.77 2022/04/17 13:17:19 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: uvideo.c,v 1.78 2022/04/17 13:17:30 riastradh Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_usb.h"
@@ -442,8 +442,12 @@ static void print_vs_format_dv_descripto
 	const uvideo_vs_format_dv_descriptor_t *);
 #endif /* !UVIDEO_DEBUG */
 
-#define GET(type, descp, field) (((const type *)(descp))->field)
-#define GETP(type, descp, field) (&(((const type *)(descp))->field))
+#define GET(type, descp, field)						      \
+	(KASSERT((descp)->bLength >= sizeof(type)),			      \
+	    ((const type *)(descp))->field)
+#define GETP(type, descp, field)					      \
+	(KASSERT((descp)->bLength >= sizeof(type)),			      \
+	    &(((const type *)(descp))->field))
 
 /*
  * Given a format descriptor and frame descriptor, copy values common
@@ -789,10 +793,11 @@ uvideo_init_control(struct uvideo_softc 
 	/* count number of units and terminals */
 	nunits = 0;
 	while ((desc = usb_desc_iter_next_non_interface(iter)) != NULL) {
+		if (desc->bDescriptorType != UDESC_CS_INTERFACE ||
+		    desc->bLength < sizeof(*uvdesc))
+			continue;
 		uvdesc = (const uvideo_descriptor_t *)desc;
 
-		if (uvdesc->bDescriptorType != UDESC_CS_INTERFACE)
-			continue;
 		if (uvdesc->bDescriptorSubtype < UDESC_INPUT_TERMINAL ||
 		    uvdesc->bDescriptorSubtype > UDESC_EXTENSION_UNIT)
 			continue;
@@ -815,10 +820,11 @@ uvideo_init_control(struct uvideo_softc 
 
 	/* iterate again, initializing the units */
 	while ((desc = usb_desc_iter_next_non_interface(iter)) != NULL) {
+		if (desc->bDescriptorType != UDESC_CS_INTERFACE ||
+		    desc->bLength < sizeof(*uvdesc))
+			continue;
 		uvdesc = (const uvideo_descriptor_t *)desc;
 
-		if (uvdesc->bDescriptorType != UDESC_CS_INTERFACE)
-			continue;
 		if (uvdesc->bDescriptorSubtype < UDESC_INPUT_TERMINAL ||
 		    uvdesc->bDescriptorSubtype > UDESC_EXTENSION_UNIT)
 			continue;
@@ -1116,9 +1122,6 @@ uvideo_stream_init(struct uvideo_stream 
  * multiple times because there may be several alternate interfaces
  * associated with the same interface number.
  */
-/*
- * XXX XXX XXX: This function accesses descriptors in an unsafe manner.
- */
 static usbd_status
 uvideo_stream_init_desc(struct uvideo_stream *vs,
 			const usb_interface_descriptor_t *ifdesc,
@@ -1142,10 +1145,10 @@ uvideo_stream_init_desc(struct uvideo_st
 	 * interface.
 	 */
 	while ((desc = usb_desc_iter_next_non_interface(iter)) != NULL) {
-		uvdesc = (const uvideo_descriptor_t *)desc;
-
-		switch (uvdesc->bDescriptorType) {
+		switch (desc->bDescriptorType) {
 		case UDESC_ENDPOINT:
+			if (desc->bLength < sizeof(usb_endpoint_descriptor_t))
+				goto baddesc;
 			bmAttributes = GET(usb_endpoint_descriptor_t,
 					   desc, bmAttributes);
 			bEndpointAddress = GET(usb_endpoint_descriptor_t,
@@ -1204,6 +1207,9 @@ uvideo_stream_init_desc(struct uvideo_st
 			}
 			break;
 		case UDESC_CS_INTERFACE:
+			if (desc->bLength < sizeof(*uvdesc))
+				goto baddesc;
+			uvdesc = (const uvideo_descriptor_t *)desc;
 			if (ifdesc->bAlternateSetting != 0) {
 				DPRINTF(("uvideo_stream_init_alternate: "
 					 "unexpected class-specific descriptor "
@@ -1244,11 +1250,12 @@ uvideo_stream_init_desc(struct uvideo_st
 			}
 			break;
 		default:
+		baddesc:
 			DPRINTF(("uvideo_stream_init_desc: "
-				 "unknown descriptor "
+				 "bad descriptor "
 				 "len=%d type=0x%02x\n",
-				 uvdesc->bLength,
-				 uvdesc->bDescriptorType));
+				 desc->bLength,
+				 desc->bDescriptorType));
 			break;
 		}
 	}
@@ -1300,8 +1307,9 @@ uvideo_stream_init_frame_based_format(st
 	struct uvideo_pixel_format *pformat, *pfiter;
 	enum video_pixel_format pixel_format;
 	struct uvideo_format *format;
+	const usb_descriptor_t *desc;
 	const uvideo_descriptor_t *uvdesc;
-	uint8_t subtype, default_index, index;
+	uint8_t subtype, subtypelen, default_index, index;
 	uint32_t frame_interval;
 	const usb_guid_t *guid;
 
@@ -1313,7 +1321,14 @@ uvideo_stream_init_frame_based_format(st
 	switch (format_desc->bDescriptorSubtype) {
 	case UDESC_VS_FORMAT_UNCOMPRESSED:
 		DPRINTF(("%s: uncompressed\n", __func__));
+		if (format_desc->bLength <
+		    sizeof(uvideo_vs_format_uncompressed_descriptor_t)) {
+			DPRINTF(("uvideo: truncated uncompressed format: %d",
+				format_desc->bLength));
+			return USBD_INVAL;
+		}
 		subtype = UDESC_VS_FRAME_UNCOMPRESSED;
+		subtypelen = sizeof(uvideo_vs_frame_uncompressed_descriptor_t);
 		default_index = GET(uvideo_vs_format_uncompressed_descriptor_t,
 				    format_desc,
 				    bDefaultFrameIndex);
@@ -1336,14 +1351,28 @@ uvideo_stream_init_frame_based_format(st
 		break;
 	case UDESC_VS_FORMAT_FRAME_BASED:
 		DPRINTF(("%s: frame-based\n", __func__));
+		if (format_desc->bLength <
+		    sizeof(uvideo_format_frame_based_descriptor_t)) {
+			DPRINTF(("uvideo: truncated frame-based format: %d",
+				format_desc->bLength));
+			return USBD_INVAL;
+		}
 		subtype = UDESC_VS_FRAME_FRAME_BASED;
+		subtypelen = sizeof(uvideo_frame_frame_based_descriptor_t);
 		default_index = GET(uvideo_format_frame_based_descriptor_t,
 				    format_desc,
 				    bDefaultFrameIndex);
 		break;
 	case UDESC_VS_FORMAT_MJPEG:
 		DPRINTF(("%s: mjpeg\n", __func__));
+		if (format_desc->bLength <
+		    sizeof(uvideo_vs_format_mjpeg_descriptor_t)) {
+			DPRINTF(("uvideo: truncated mjpeg format: %d",
+				format_desc->bLength));
+			return USBD_INVAL;
+		}
 		subtype = UDESC_VS_FRAME_MJPEG;
+		subtypelen = sizeof(uvideo_vs_frame_mjpeg_descriptor_t);
 		default_index = GET(uvideo_vs_format_mjpeg_descriptor_t,
 				    format_desc,
 				    bDefaultFrameIndex);
@@ -1355,6 +1384,8 @@ uvideo_stream_init_frame_based_format(st
 		return USBD_INVAL;
 	}
 
+	KASSERT(subtypelen >= sizeof(*uvdesc));
+
 	pformat = NULL;
 	SIMPLEQ_FOREACH(pfiter, &vs->vs_pixel_formats, entries) {
 		if (pfiter->pixel_format == pixel_format) {
@@ -1376,10 +1407,27 @@ uvideo_stream_init_frame_based_format(st
 	 * format descriptor, and add a format to the format list for
 	 * each frame descriptor.
 	 */
-	while ((uvdesc = (const uvideo_descriptor_t *)usb_desc_iter_peek(iter)) &&
-	       (uvdesc != NULL) && (uvdesc->bDescriptorSubtype == subtype))
-	{
-		uvdesc = (const uvideo_descriptor_t *) usb_desc_iter_next(iter);
+	while ((desc = usb_desc_iter_peek(iter)) != NULL) {
+		if (desc->bDescriptorType != UDESC_CS_INTERFACE)
+			break;
+		if (desc->bLength < sizeof(*uvdesc)) {
+			DPRINTF(("uvideo: truncated CS descriptor, length %d",
+				desc->bLength));
+			break;
+		}
+		uvdesc = (const uvideo_descriptor_t *)desc;
+		if (uvdesc->bDescriptorSubtype != subtype)
+			break;
+		if (uvdesc->bLength < subtypelen) {
+			DPRINTF(("uvideo:"
+				" truncated CS subtype-0x%x descriptor,"
+				" length %d < %d",
+				uvdesc->bLength, subtypelen));
+			break;
+		}
+
+		/* We peeked; now consume.  */
+		(void)usb_desc_iter_next(iter);
 
 		format = kmem_zalloc(sizeof(*format), KM_SLEEP);
 		format->format.pixel_format = pixel_format;

Reply via email to