[PATCH v2 1/2] media: uvcvideo: Fix race condition for meta buffer list
From: Ricardo Ribalda
Date: Mon Jun 29 2026 - 13:32:10 EST
queue->irqueue contains a list of the buffers owned by the driver. The
list is protected by queue->irqlock. uvc_queue_get_current_buffer()
returns a pointer to the current buffer in that list, but does not
remove the buffer from it. This can lead to race conditions.
Inspecting the code, it seems that the candidate for such race is
uvc_queue_return_buffers(). For the capture queue, that function is
called with the device streamoff, so no race can occur. On the other
hand, the metadata queue, could trigger a race condition, because
stop_streaming can be called with the device in any streaming state.
We can solve this issue introducing a flag, stream->meta.in_flight,
protected with a spinlock. When there is a buffer in flight that can
write into metadata the flag is raised, notifying the stop streaming
that it needs to wait.
Reported-by: Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>
Closes: https://lore.kernel.org/linux-media/20250630141707.GG20333@xxxxxxxxxxxxxxxxxxxxxxxxxx/
Cc: stable@xxxxxxxxxxxxxxx
Fixes: 088ead255245 ("media: uvcvideo: Add a metadata device node")
Signed-off-by: Ricardo Ribalda <ribalda@xxxxxxxxxxxx>
---
drivers/media/usb/uvc/uvc_queue.c | 14 ++++++++++++++
drivers/media/usb/uvc/uvc_video.c | 30 +++++++++++++++++++++++++++++-
drivers/media/usb/uvc/uvcvideo.h | 2 ++
3 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c
index 3c002c8f442f..af9dbfcf6f53 100644
--- a/drivers/media/usb/uvc/uvc_queue.c
+++ b/drivers/media/usb/uvc/uvc_queue.c
@@ -209,10 +209,24 @@ static void uvc_stop_streaming_video(struct vb2_queue *vq)
static void uvc_stop_streaming_meta(struct vb2_queue *vq)
{
struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
+ struct uvc_streaming *stream = queue->stream;
lockdep_assert_irqs_enabled();
+ spin_lock_irq(&stream->meta.irqlock);
+ while (stream->meta.in_flight) {
+ spin_unlock_irq(&stream->meta.irqlock);
+ schedule();
+ spin_lock_irq(&stream->meta.irqlock);
+ }
+ stream->meta.in_flight = true;
+ spin_unlock_irq(&stream->meta.irqlock);
+
uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR);
+
+ scoped_guard(spinlock_irq, &stream->meta.irqlock) {
+ stream->meta.in_flight = false;
+ }
}
static const struct vb2_ops uvc_queue_qops = {
diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
index fc3536a4399f..f6b55b3a3308 100644
--- a/drivers/media/usb/uvc/uvc_video.c
+++ b/drivers/media/usb/uvc/uvc_video.c
@@ -1732,6 +1732,26 @@ static void uvc_video_encode_bulk(struct uvc_urb *uvc_urb,
urb->transfer_buffer_length = stream->urb_size - len;
}
+static struct uvc_buffer *
+uvc_video_get_current_meta_buffer(struct uvc_streaming *stream)
+{
+ struct uvc_video_queue *queue = &stream->meta.queue;
+ struct uvc_buffer *buf;
+
+ buf = uvc_queue_get_current_buffer(queue);
+ if (!buf)
+ return NULL;
+
+ guard(spinlock_irqsave)(&stream->meta.irqlock);
+
+ if (stream->meta.in_flight)
+ return NULL;
+
+ stream->meta.in_flight = true;
+
+ return buf;
+}
+
static void uvc_video_complete(struct urb *urb)
{
struct uvc_urb *uvc_urb = urb->context;
@@ -1767,7 +1787,7 @@ static void uvc_video_complete(struct urb *urb)
buf = uvc_queue_get_current_buffer(queue);
if (vb2_qmeta)
- buf_meta = uvc_queue_get_current_buffer(qmeta);
+ buf_meta = uvc_video_get_current_meta_buffer(stream);
/* Re-initialise the URB async work. */
uvc_urb->async_operations = 0;
@@ -1778,6 +1798,12 @@ static void uvc_video_complete(struct urb *urb)
*/
stream->decode(uvc_urb, buf, buf_meta);
+ if (buf_meta) {
+ scoped_guard(spinlock_irqsave, &stream->meta.irqlock) {
+ stream->meta.in_flight = false;
+ }
+ }
+
/* If no async work is needed, resubmit the URB immediately. */
if (!uvc_urb->async_operations) {
ret = usb_submit_urb(uvc_urb->urb, GFP_ATOMIC);
@@ -2330,6 +2356,8 @@ int uvc_video_init(struct uvc_streaming *stream)
for_each_uvc_urb(uvc_urb, stream)
INIT_WORK(&uvc_urb->work, uvc_video_copy_data_work);
+ spin_lock_init(&stream->meta.irqlock);
+
return 0;
}
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
index b6bcee4a222f..6f1a3381d392 100644
--- a/drivers/media/usb/uvc/uvcvideo.h
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -484,6 +484,8 @@ struct uvc_streaming {
struct uvc_video_queue queue;
u32 format;
u32 buffersize;
+ bool in_flight;
+ spinlock_t irqlock; /* Protects in_flight. */
} meta;
/* Context data used by the bulk completion handler. */
--
2.55.0.rc0.799.gd6f94ed593-goog