DragonFlyBSD Kernel Audit
DF-1065 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/bus/u4b/uvc/uvc_buf.h b/sys/bus/u4b/uvc/uvc_buf.h
--- a/sys/bus/u4b/uvc/uvc_buf.h
+++ b/sys/bus/u4b/uvc/uvc_buf.h
@@ -76,6 +76,7 @@
 
 	uint64_t	buf_size;
 	uint64_t	buf_count;
+	int		mapped;	/* a userspace mmap() of bq->mem exists */
 	struct uvc_buf	buf[UVC_BUF_MAX_BUFFERS];
 
 	STAILQ_HEAD(, uvc_buf)	consumer;
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
@@ -125,6 +125,16 @@
 
 	*paddr = atop(vtophys((uint8_t *)bq->mem + offset));
 
+	/*
+	 * Record that a userspace mapping of bq->mem now exists.  Old-style
+	 * d_mmap has no per-mapping unmap callback, so once any fault succeeds
+	 * the kernel cannot know when the mapping is torn down; the buffer must
+	 * therefore outlive the mapping to avoid a use-after-free (the device
+	 * pager installs a wired fictitious page whose phys_addr is never
+	 * revoked by kfree).
+	 */
+	bq->mapped = 1;
+
 	return (0);
 }
 
@@ -518,9 +528,27 @@
 uvc_buf_queue_free_bufs_locked(struct uvc_buf_queue *bq)
 {
 	if (bq->mem) {
+		/*
+		 * If a userspace mmap() of this memory exists we must NOT free
+		 * it: the device pager keeps a wired fictitious page pointing
+		 * at these physical pages, so kfree() here would leave a
+		 * dangling mapping over slab-reusable kernel heap -- a UAF
+		 * readable (info leak) and writable (corruption).  Zero the
+		 * contents to kill any residual info leak and keep the
+		 * allocation pinned until the device (and its mappings) goes
+		 * away.  The full fix is a vm_object-backed lifecycle; this is
+		 * the minimal security mitigation.
+		 */
+		if (bq->buf_size && bq->buf_count)
+			explicit_bzero(bq->mem, bq->buf_size * bq->buf_count);
+		if (bq->mapped) {
+			/* keep pinned: mappings still reference it */
+			return;
+		}
 		kfree(bq->mem, M_UVC);
 		bq->mem = NULL;
 		bq->buf_count = 0;
+		bq->buf_size = 0;
 	}
 }
 
@@ -551,6 +579,16 @@
 		num = UVC_BUF_MAX_BUFFERS;
 
 	UVC_LOCK(&bq->mtx);
+	/*
+	 * Refuse to (re)allocate while an existing buffer is still mmap()'d:
+	 * the entry-point free below would otherwise kfree memory that
+	 * userspace still maps.  Old-style d_mmap has no unmap callback so we
+	 * cannot revoke the mapping; require the caller to drop mappings first.
+	 */
+	if (bq->mapped) {
+		UVC_UNLOCK(&bq->mtx);
+		return EBUSY;
+	}
 	uvc_buf_queue_free_bufs_locked(bq);
 	if (!num)
 		goto done;