Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 22 Jul 2026 15:12:47 +0000
From:      Baptiste Daroussin <bapt@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: f12dd1d5f030 - main - uvideo: lock the mmap queue and read path
Message-ID:  <6a60ddef.3377b.5517bd77@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by bapt:

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

commit f12dd1d5f0303fe3bc5030293bda04b04995b72c
Author:     Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-07-22 07:26:18 +0000
Commit:     Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2026-07-22 15:10:57 +0000

    uvideo: lock the mmap queue and read path
    
    qbuf(), dqbuf() and read() manipulated sc_mmap_q / sc_mmap_cur /
    sc_frames_ready without sc_mtx, racing with the USB transfer callbacks
    (producer) that run under the mutex.  This could corrupt the queue or
    trigger use-after-free.
    
    Take sc_mtx around qbuf(), use mtx_sleep() and protect the queue
    operations in dqbuf(), and use mtx_sleep() with a snapshot of sc_fsize
    in read().
    
    Also reject S_FMT and S_PARM with EBUSY while streaming: both
    re-negotiate the probe/commit controls with the device, which disrupts
    the active USB transfers (a second client opening the device would
    otherwise freeze the first one's stream).
---
 sys/dev/usb/video/uvideo.c | 72 +++++++++++++++++++++++++++++++++++++---------
 1 file changed, 59 insertions(+), 13 deletions(-)

diff --git a/sys/dev/usb/video/uvideo.c b/sys/dev/usb/video/uvideo.c
index 9c6076bc76d0..708660adeafb 100644
--- a/sys/dev/usb/video/uvideo.c
+++ b/sys/dev/usb/video/uvideo.c
@@ -2893,7 +2893,7 @@ uvideo_cdev_read(struct cdev *dev, struct uio *uio, int ioflag)
 {
 	struct uvideo_softc *sc = dev->si_drv1;
 	usb_error_t error;
-	int ret;
+	int ret, fsize;
 
 	if (sc == NULL || sc->sc_dying)
 		return (ENXIO);
@@ -2942,23 +2942,31 @@ uvideo_cdev_read(struct cdev *dev, struct uio *uio, int ioflag)
 		return (EBUSY);
 
 	/* Wait for a frame */
+	mtx_lock(&sc->sc_mtx);
 	while (sc->sc_frames_ready == 0) {
-		if (ioflag & IO_NDELAY)
+		if (ioflag & IO_NDELAY) {
+			mtx_unlock(&sc->sc_mtx);
 			return (EWOULDBLOCK);
-		ret = tsleep(sc, PCATCH, "uvread", hz * 10);
-		if (ret != 0)
+		}
+		ret = mtx_sleep(sc, &sc->sc_mtx, PCATCH, "uvread", hz * 10);
+		if (ret != 0) {
+			mtx_unlock(&sc->sc_mtx);
 			return (ret);
-		if (sc->sc_dying)
+		}
+		if (sc->sc_dying) {
+			mtx_unlock(&sc->sc_mtx);
 			return (ENXIO);
+		}
 	}
 
 	sc->sc_frames_ready--;
+	fsize = sc->sc_fsize;
+	mtx_unlock(&sc->sc_mtx);
 
-	if (sc->sc_fsize == 0)
+	if (fsize == 0)
 		return (0);
 
-	return (uiomove(sc->sc_fbuffer, MIN(uio->uio_resid, sc->sc_fsize),
-	    uio));
+	return (uiomove(sc->sc_fbuffer, MIN(uio->uio_resid, fsize), uio));
 }
 
 static int
@@ -3367,6 +3375,16 @@ uvideo_s_fmt(struct uvideo_softc *sc, struct v4l2_format *fmt)
 	if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
 		return (EINVAL);
 
+	/* Reject format changes while streaming: re-negotiating the probe
+	 * and commit controls with the device would disrupt the active USB
+	 * transfers.  V4L2 mandates EBUSY in this case. */
+	mtx_lock(&sc->sc_mtx);
+	if (sc->sc_streaming) {
+		mtx_unlock(&sc->sc_mtx);
+		return (EBUSY);
+	}
+	mtx_unlock(&sc->sc_mtx);
+
 	DPRINTFN(1, "s_fmt: requested %dx%d\n",
 	    fmt->fmt.pix.width, fmt->fmt.pix.height);
 
@@ -3448,6 +3466,15 @@ uvideo_s_parm(struct uvideo_softc *sc, struct v4l2_streamparm *parm)
 {
 	usb_error_t error;
 
+	/* Reject parameter changes while streaming for the same reason as
+	 * S_FMT: they re-negotiate with the device. */
+	mtx_lock(&sc->sc_mtx);
+	if (sc->sc_streaming) {
+		mtx_unlock(&sc->sc_mtx);
+		return (EBUSY);
+	}
+	mtx_unlock(&sc->sc_mtx);
+
 	if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
 		if (parm->parm.capture.timeperframe.numerator == 0 ||
 		    parm->parm.capture.timeperframe.denominator == 0)
@@ -3652,8 +3679,11 @@ uvideo_qbuf(struct uvideo_softc *sc, struct v4l2_buffer *qb)
 	    qb->index >= sc->sc_mmap_count)
 		return (EINVAL);
 
+	/* Serialize with the USB transfer callbacks (producer). */
+	mtx_lock(&sc->sc_mtx);
 	sc->sc_mmap[qb->index].v4l2_buf.flags &= ~V4L2_BUF_FLAG_DONE;
 	sc->sc_mmap[qb->index].v4l2_buf.flags |= V4L2_BUF_FLAG_QUEUED;
+	mtx_unlock(&sc->sc_mtx);
 
 	DPRINTFN(2, "buffer %d ready for queueing\n", qb->index);
 
@@ -3670,24 +3700,40 @@ uvideo_dqbuf(struct uvideo_softc *sc, struct v4l2_buffer *dqb)
 	    dqb->memory != V4L2_MEMORY_MMAP)
 		return (EINVAL);
 
-	if (STAILQ_EMPTY(&sc->sc_mmap_q)) {
-		error = tsleep(sc, PCATCH, "uvdqbuf", hz * 10);
-		if (error)
+	/*
+	 * Serialize with the USB transfer callbacks (producer) that insert
+	 * completed buffers into sc_mmap_q under sc_mtx.  Use mtx_sleep so
+	 * the wait and the queue inspection are atomic.
+	 */
+	mtx_lock(&sc->sc_mtx);
+	while (STAILQ_EMPTY(&sc->sc_mmap_q)) {
+		error = mtx_sleep(sc, &sc->sc_mtx, PCATCH, "uvdqbuf", hz * 10);
+		if (error != 0) {
+			mtx_unlock(&sc->sc_mtx);
 			return (EINVAL);
+		}
+		if (sc->sc_dying) {
+			mtx_unlock(&sc->sc_mtx);
+			return (ENXIO);
+		}
 	}
 
 	mmap = STAILQ_FIRST(&sc->sc_mmap_q);
-	if (mmap == NULL)
+	if (mmap == NULL) {
+		mtx_unlock(&sc->sc_mtx);
 		return (EINVAL);
+	}
 
 	bcopy(&mmap->v4l2_buf, dqb, sizeof(struct v4l2_buffer));
 
 	mmap->v4l2_buf.flags &= ~V4L2_BUF_FLAG_DONE;
 	mmap->v4l2_buf.flags &= ~V4L2_BUF_FLAG_QUEUED;
 
+	STAILQ_REMOVE_HEAD(&sc->sc_mmap_q, q_frames);
+	mtx_unlock(&sc->sc_mtx);
+
 	DPRINTFN(2, "frame dequeued from index %d\n",
 	    mmap->v4l2_buf.index);
-	STAILQ_REMOVE_HEAD(&sc->sc_mmap_q, q_frames);
 
 	return (0);
 }


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a60ddef.3377b.5517bd77>