DragonFlyBSD Kernel Audit
DF-1066 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/bus/u4b/uvc/uvc_buf.c b/sys/bus/u4b/uvc/uvc_buf.c
--- a/sys/bus/u4b/uvc/uvc_buf.c
+++ b/sys/bus/u4b/uvc/uvc_buf.c
@@ -104,22 +104,35 @@
 static __inline int
 uvc_buf_queue_mmap_locked(struct uvc_buf_queue *bq, vm_paddr_t *paddr, vm_offset_t offset)
 {
-	 uint64_t max_offset = (uint64_t)(bq->buf_size * bq->buf_count) - PAGE_SIZE;
+	uint64_t total, max_offset;
 
-	if (bq->mem == NULL)
+	/*
+	 * Reject the degenerate queue state outright.  buf_size==0 (reachable
+	 * when REQBUFS was called with len==0, e.g. a camera probe returning
+	 * dwMaxFrameSize==0) used to make the old
+	 * "(uint64_t)(buf_size*buf_count) - PAGE_SIZE" subtraction underflow to
+	 * 0xFFFFFFFFFFFFF000, defeating the offset bounds check below and
+	 * turning vtophys(bq->mem + offset) into an arbitrary kernel-VA ->
+	 * physical-page translator.  The old "offset < 0" / "max_offset < 0"
+	 * checks were dead code (both operands are unsigned).
+	 */
+	if (bq->mem == NULL || bq->buf_size == 0 || bq->buf_count == 0)
 		return EINVAL;
 
-	if (offset < 0)
+	/* Guard the multiplication against uint64 overflow. */
+	if (bq->buf_size > UINT64_MAX / bq->buf_count)
 		return EINVAL;
 
-	if (max_offset < 0)
+	total = bq->buf_size * bq->buf_count;
+	if (total < (uint64_t)PAGE_SIZE)
 		return EINVAL;
+	max_offset = total - PAGE_SIZE;
 
-	if (offset > max_offset) {
-		kprintf("WARNING: %s offset out of bound: %ld\n; max: %ld",
+	if ((uint64_t)offset > max_offset) {
+		kprintf("WARNING: %s offset out of bound: %ld\n; max: %lu",
 				__func__,
 				offset,
-				max_offset);
+				(unsigned long)max_offset);
 		return EINVAL;
 	}
 
@@ -543,6 +556,10 @@
 	unsigned long rl;
 
 	DPRINTF("%s\n", __func__);
+	/* Reject a zero frame size: it would yield buf_size==0, which is not a
+	 * meaningful buffer and used to enable the mmap offset underflow. */
+	if (len == 0)
+		return EINVAL;
 	rl = round_page(len);
 	DPRINTF("page align size:%lu-real size:%u\n", rl, len);