[PATCH v6 2/8] media: meson: vdec: Fix concurrent STREAMON / STREAMOFF race conditions

From: Anand Moon

Date: Sat May 30 2026 - 05:44:34 EST


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 <nicolas@xxxxxxxxxxxx>
Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
Closes: https://lore.kernel.org/all/20260525104345.C8D501F00A3C@xxxxxxxxxxxxxxx/
Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver")
Signed-off-by: Anand Moon <linux.amoon@xxxxxxxxx>
---
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