DF-1065 / fix.diff
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; |