The Meson VDEC driver’s start/stop streaming paths previously
updated core->cur_sess and sess->status without synchronization,
leaving a race window between concurrent STREAMON/STREAMOFF calls.

Following change introduces proper locking discipline:

- Hold core->lock when checking or updating core->cur_sess and
  sess->status in vdec_start_streaming().
- Snapshot sess->status under the lock in vdec_stop_streaming()
  to safely evaluate hardware state after releasing the mutex.
- Ensure error unwind paths clear core->cur_sess and reset
  sess->status inside the lock.

This prevents TOCTOU races, avoids data corruption when multiple
sessions contend for the hardware, and ensures consistent session
lifecycle management.

Cc: Nicolas Dufresne <[email protected]>
Reported-by: Sashiko <[email protected]>
Closes: https://lore.kernel.org/all/[email protected]/
Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver")
Signed-off-by: Anand Moon <[email protected]>
---
 drivers/staging/media/meson/vdec/vdec.c | 62 ++++++++++++++++++-------
 1 file changed, 46 insertions(+), 16 deletions(-)

diff --git a/drivers/staging/media/meson/vdec/vdec.c 
b/drivers/staging/media/meson/vdec/vdec.c
index 4ffebba2341d..7233000e2232 100644
--- a/drivers/staging/media/meson/vdec/vdec.c
+++ b/drivers/staging/media/meson/vdec/vdec.c
@@ -286,11 +286,6 @@ static int vdec_start_streaming(struct vb2_queue *q, 
unsigned int count)
        struct vb2_v4l2_buffer *buf;
        int ret;
 
-       if (core->cur_sess && core->cur_sess != sess) {
-               ret = -EBUSY;
-               goto bufs_done;
-       }
-
        if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
                sess->streamon_out = 1;
        else
@@ -308,9 +303,29 @@ static int vdec_start_streaming(struct vb2_queue *q, 
unsigned int count)
        }
 
        if (sess->status == STATUS_RUNNING ||
-           sess->status == STATUS_NEEDS_RESUME ||
-           sess->status == STATUS_INIT)
+           sess->status == STATUS_NEEDS_RESUME)
+               return 0;
+
+       /*
+        * Secure the core hardware lock before checking availability
+        * and updating session states to prevent STREAMON race conditions.
+        */
+       mutex_lock(&core->lock);
+       if (core->cur_sess && core->cur_sess != sess) {
+               mutex_unlock(&core->lock);
+               ret = -EBUSY;
+               goto bufs_done;
+       }
+
+       /* If already half-initialized, do not re-initialize */
+       if (sess->status == STATUS_INIT) {
+               mutex_unlock(&core->lock);
                return 0;
+       }
+
+       sess->status = STATUS_INIT;
+       core->cur_sess = sess;
+       mutex_unlock(&core->lock);
 
        sess->vififo_size = SIZE_VIFIFO;
        sess->vififo_vaddr =
@@ -341,8 +356,6 @@ static int vdec_start_streaming(struct vb2_queue *q, 
unsigned int count)
                sess->recycle_thread = kthread_run(vdec_recycle_thread, sess,
                                                   "vdec_recycle");
 
-       sess->status = STATUS_INIT;
-       core->cur_sess = sess;
        schedule_work(&sess->esparser_queue_work);
        return 0;
 
@@ -350,6 +363,12 @@ static int vdec_start_streaming(struct vb2_queue *q, 
unsigned int count)
        dma_free_coherent(sess->core->dev, sess->vififo_size,
                          sess->vififo_vaddr, sess->vififo_paddr);
 bufs_done:
+       mutex_lock(&core->lock);
+       if (core->cur_sess == sess)
+               core->cur_sess = NULL;
+       sess->status = STATUS_STOPPED;
+       mutex_unlock(&core->lock);
+
        while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx)))
                v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED);
        while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx)))
@@ -399,10 +418,23 @@ static void vdec_stop_streaming(struct vb2_queue *q)
        struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
        struct amvdec_core *core = sess->core;
        struct vb2_v4l2_buffer *buf;
+       enum amvdec_status old_status;
 
-       if (sess->status == STATUS_RUNNING ||
-           sess->status == STATUS_INIT ||
-           (sess->status == STATUS_NEEDS_RESUME &&
+       /*
+        * Safely snapshot the status and clear the hardware owner inside
+        * the mutex to prevent data races with concurrent STREAMON requests.
+        */
+       mutex_lock(&core->lock);
+       old_status = sess->status;
+       if (core->cur_sess == sess)
+               core->cur_sess = NULL;
+       sess->status = STATUS_STOPPED;
+       mutex_unlock(&core->lock);
+
+       /* Evaluate the hardware state using our snapshot */
+       if (old_status == STATUS_RUNNING ||
+           old_status == STATUS_INIT ||
+           (old_status == STATUS_NEEDS_RESUME &&
             (!sess->streamon_out || !sess->streamon_cap))) {
                if (vdec_codec_needs_recycle(sess))
                        kthread_stop(sess->recycle_thread);
@@ -415,8 +447,6 @@ static void vdec_stop_streaming(struct vb2_queue *q)
                vdec_reset_bufs_recycle(sess);
                kfree(sess->priv);
                sess->priv = NULL;
-               core->cur_sess = NULL;
-               sess->status = STATUS_STOPPED;
        }
 
        if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
@@ -425,8 +455,8 @@ static void vdec_stop_streaming(struct vb2_queue *q)
 
                sess->streamon_out = 0;
        } else {
-               /* Drain remaining refs if was still running */
-               if (sess->status >= STATUS_RUNNING && codec_ops->drain)
+               /* Drain remaining refs if was still running using the snapshot 
*/
+               if (old_status >= STATUS_RUNNING && codec_ops->drain)
                        codec_ops->drain(sess);
 
                while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx)))
-- 
2.50.1

Reply via email to