media: meson: vdec: Fix lifecycles, race conditions, and stability bugs
Subsystems (7)
v7 cover media: meson: vdec: Fix lifecycles, race conditions, and stability bugs
This patch series addresses a collection of critical race conditions, memory leaks, use-after-free bugs, and resource management issues within the Amlogic Meson video decoder (meson-vdec) driver and associated components.
The primary focus is tightening session lifecycles, safely synchronising the V4L2 m2m framework callbacks, hardening work queue operations (esparser_queue_work), and correcting buffer validations to prevent stability issues like KASAN out-of-bounds errors or stream-on timeouts.
Summary of Changes:
* Race Conditions & Synchronisation: Fixes multi-threading and execution races across stream-on, stream-off, job abort, and teardown pathways.
* Memory Lifecycle: Eliminates memory leaks and use-after-free vulnerabilities by safely pinning, canceling, and sequencing device removals.
* VP9 & Buffer Hardening: Addresses payload handling bugs in the ES parser, properly tracks ongoing buffers via atomic counters, and prevents invalid memory writes.
* Platform/DMA Adjustments: Adjusts DMA segment configurations, canvas handling, and buffer sizes to match modern hardware demands.
Note on an outstanding issue:
An execution deadlock occurs in the driver workqueue path during high stress or long playback sessions. This stall is triggered when the Amlogic hardware internal video FIFO buffer becomes completely full. When esparser_queue_all_src runs, the capacity validation check is tripped due to a design limitation in how buffer exhaustion is handled. This blocks progress and leaves the workqueue worker waiting indefinitely on a core mutex lock.
The resulting hung task call trace shows the worker thread blocking on the internal lock:
Workqueue: events esparser_queue_all_src [meson_vdec]
...__mutex_lockmutex_lock_nestedesparser_queue_all_src
I have tried to address this issue, but the underlying deadlock persists under extreme conditions.
Any feedback on this new appock is welcome.
Testing was done using v4l2-compliance and active decoding pipelines on Meson platforms (like Hardkernel ODROID-N2+ and ODROID-C4) SBCs.
The v4l2-compliance tests passed successfully via:
$ v4l2-compliance -s -v -d /dev/video0
Pipelines were validated using the following GStreamer script.
[1] https://gist.github.com/moonlinux/08295ba1f17ce7155550773f2d2cb6e5
Previous changes:
v6: https://lore.kernel.org/all/[email protected]/
Thanks
-Annad
Anand Moon (19):
media: meson: vdec: Fix m2m device lifetime and cleanup path
media: meson: vdec: Fix STREAMON / STREAMOFF race conditions and
session teardown
media: meson: vdec: Fix lifecycle leaks and race conditions in
recycle_thread
media: meson: vdec: Fix use-after-free race between teardown and ISR
routines
media: meson: vdec: Fix race condition and synchronize esparser IRQ
media: meson: vdec: Fix race condition by canceling work sync
media: meson: vdec: Refactor esparser work queue and fix teardown race
media: meson: vdec: Fix concurrent execution races and unsafe teardown
media: meson: vdec: Fix vp9 header update failure on invalid payloads
media: meson: vdec: Fix race conditions and leaks in esparser pipeline
media: meson: vdec: Update core m2m stream state during transitions
media: meson: vdec: Coordinate m2m task execution inside async loop
media: meson: vdec: Fix race conditions in job abort sequence
media: meson: vdec: Correct atomic counter placement in dst_buf_done
media: meson: vdec: Fix concurrent firmware loading race and hardware
timeout
media: meson: vdec: Configure DMA mask and segment size in probe
media: meson: canvas: Fix Use-After-Free by linking canvas provider
device
media: meson: vdec: Increase VIFIFO buffer size to 32 MiB
gpu: drm: meson: Fix DMA segment size limits and maximize allocation
boundaries
drivers/gpu/drm/meson/meson_drv.c | 6 + drivers/soc/amlogic/meson-canvas.c | 14 +- drivers/staging/media/meson/vdec/esparser.c | 127 +++++-- drivers/staging/media/meson/vdec/vdec.c | 320 ++++++++++++++---- drivers/staging/media/meson/vdec/vdec.h | 8 +- drivers/staging/media/meson/vdec/vdec_1.c | 16 +- .../staging/media/meson/vdec/vdec_helpers.c | 3 + 7 files changed, 388 insertions(+), 106 deletions(-)
base-commit: a13c140cc289c0b7b3770bce5b3ad42ab35074aa
Signature · 1 line
2.50.1
PATCH v7 1/19 media: meson: vdec: Fix m2m device lifetime and cleanup path
The vdec driver was incorrectly initializing a new v4l2_m2m device instance per session inside vdec_open() and releasing it in vdec_close(). This design is faulty because the m2m device models the core hardware engine and must persist across multiple sessions.
Fix the lifetime by moving v4l2_m2m_init() into vdec_probe() and releasing it in vdec_remove() which resolves passing the global core->m2m_dev down to session contexts so all sessions share the same hardware instance.
This change aligns the driver with proper v4l2_m2m usage, ensuring the hardware device lifetime is tied to the platform driver core, not individual sessions, and making teardown safe and predictable.
--- drivers/staging/media/meson/vdec/vdec.c | 36 +++++++++++++------------ drivers/staging/media/meson/vdec/vdec.h | 4 +-- 2 files changed, 21 insertions(+), 19 deletions(-)
drivers/staging/media/meson/vdec/vdec.c +19 -17
@@ -153,7 +153,7 @@ static void vdec_m2m_job_abort(void *priv) {struct amvdec_session *sess = priv; -v4l2_m2m_job_finish (sess->m2m_dev, sess->m2m_ctx); +v4l2_m2m_job_finish (sess->core->m2m_dev, sess->m2m_ctx); }static const struct v4l2_m2m_ops vdec_m2m_ops = {
@@ -873,23 +873,16 @@ static int vdec_open(struct file *file) sess->core = core; - sess->m2m_dev =v4l2_m2m_init (&vdec_m2m_ops); -if (IS_ERR (sess->m2m_dev)) { -dev_err (dev,"Fail to v4l2_m2m_init\n" ); - ret =PTR_ERR (sess->m2m_dev); - goto err_free_sess; - } - - sess->m2m_ctx =v4l2_m2m_ctx_init (sess->m2m_dev, sess, m2m_queue_init); + sess->m2m_ctx =v4l2_m2m_ctx_init (core->m2m_dev, sess, m2m_queue_init);if (IS_ERR (sess->m2m_ctx)) {dev_err (dev,"Fail to v4l2_m2m_ctx_init\n" ); ret =PTR_ERR (sess->m2m_ctx); - goto err_m2m_release; + goto err_free_sess; } ret =vdec_init_ctrls (sess);if (ret) - goto err_m2m_ctx_release; + goto err_release_ctx; sess->pixfmt_cap = formats[0 ].pixfmts_cap[0 ]; sess->fmt_out = &formats[0 ];
@@ -913,10 +906,8 @@ static int vdec_open(struct file *file)return 0 ; -err_m2m_ctx_release: +err_release_ctx:v4l2_m2m_ctx_release (sess->m2m_ctx); -err_m2m_release: -v4l2_m2m_release (sess->m2m_dev); err_free_sess:kfree (sess);return ret;
@@ -927,9 +918,9 @@ static int vdec_close(struct file *file)struct amvdec_session *sess =file_to_amvdec_session (file);v4l2_m2m_ctx_release (sess->m2m_ctx); -v4l2_m2m_release (sess->m2m_dev);v4l2_fh_del (&sess->fh, file);v4l2_fh_exit (&sess->fh); +v4l2_ctrl_handler_free (&sess->ctrl_handler);mutex_destroy (&sess->lock);mutex_destroy (&sess->bufs_recycle_lock);
@@ -1059,16 +1050,23 @@ static int vdec_probe(struct platform_device *pdev)if (ret)return ret; + core->m2m_dev =v4l2_m2m_init (&vdec_m2m_ops); +if (IS_ERR (core->m2m_dev)) { +dev_err (dev,"Failed to initialize v4l2 m2m device\n" ); +return PTR_ERR (core->m2m_dev); + } + ret =v4l2_device_register (dev, &core->v4l2_dev);if (ret) {dev_err (dev,"Couldn't register v4l2 device\n" ); -return -ENOMEM ; + ret = -ENOMEM ; + goto err_m2m_release; } vdev =video_device_alloc ();if (!vdev) { ret = -ENOMEM ; - goto err_vdev_release; + goto err_v4l2_unregister; } core->vdev_dec = vdev;
@@ -1096,7 +1094,10 @@ static int vdec_probe(struct platform_device *pdev) err_vdev_release:video_device_release (vdev); +err_v4l2_unregister:v4l2_device_unregister (&core->v4l2_dev); +err_m2m_release: +v4l2_m2m_release (core->m2m_dev);return ret; }
@@ -1105,6 +1106,7 @@ static void vdec_remove(struct platform_device *pdev)struct amvdec_core *core =platform_get_drvdata (pdev);video_unregister_device (core->vdev_dec); +v4l2_m2m_release (core->m2m_dev);v4l2_device_unregister (&core->v4l2_dev); }
drivers/staging/media/meson/vdec/vdec.h +2 -2
@@ -63,6 +63,7 @@ struct amvdec_session;* @vdec_hevcf_clk: VDEC_HEVCF clock * @esparser_reset: RESET for the PARSER * @vdev_dec: video device for the decoder +* @m2m_dev: v4l2 m2m device * @v4l2_dev: v4l2 device * @cur_sess: current decoding session * @lock: video device lock
@@ -87,6 +88,7 @@ struct amvdec_core {struct reset_control *esparser_reset;struct video_device *vdev_dec; +struct v4l2_m2m_dev *m2m_dev;struct v4l2_device v4l2_dev;struct amvdec_session *cur_sess;
@@ -183,7 +185,6 @@ enum amvdec_status {* * @core: reference to the vdec core struct * @fh: v4l2 file handle -* @m2m_dev: v4l2 m2m device * @m2m_ctx: v4l2 m2m context * @ctrl_handler: V4L2 control handler * @ctrl_min_buf_capture: V4L2 control V4L2_CID_MIN_BUFFERS_FOR_CAPTURE
@@ -230,7 +231,6 @@ struct amvdec_session {struct amvdec_core *core;struct v4l2_fh fh; -struct v4l2_m2m_dev *m2m_dev;struct v4l2_m2m_ctx *m2m_ctx;struct v4l2_ctrl_handler ctrl_handler;struct v4l2_ctrl *ctrl_min_buf_capture;
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 01/19] media: meson: vdec: Fix m2m device lifetime and cleanup path
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Moving v4l2_m2m_dev allocation to the driver core introduces an exploitable Use-After-Free (UAF) during device removal when file descriptors remain open.
Pre-existing issues:
- [High] The amvdec_core structure is improperly allocated using devm_kzalloc, leading to Use-After-Free bugs when the device is unbound. --
--- Patch [1]: [PATCH v7 01/19] media: meson: vdec: Fix m2m device lifetime and cleanup path --- commit f97dc3a43da5c78cbae7f710eb7770593a75ff3e
Author: Anand Moon <[email protected]>
media: meson: vdec: Fix m2m device lifetime and cleanup path
The vdec driver was incorrectly initializing a new v4l2_m2m device instance per session inside vdec_open() and releasing it in vdec_close(). This design is faulty because the m2m device models the core hardware engine and must persist across multiple sessions.
Quoted 4 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static void vdec_remove(struct platform_device *pdev) · line 1109
Quoted 5 lines
drivers/staging/media/meson/vdec/vdec.c +1
-0@@ -1105,6 +1106,7 @@ static void vdec_remove(struct platform_device *pdev)struct amvdec_core *core =platform_get_drvdata (pdev);video_unregister_device (core->vdev_dec); +v4l2_m2m_release (core->m2m_dev);
[Severity: High]
Does releasing the m2m device here introduce a use-after-free if a file descriptor remains open?
If a user unbinds the driver, vdec_remove() synchronously frees core->m2m_dev. However, if an application still holds a file descriptor open, it will eventually close it, triggering vdec_close():
vdec_close()
v4l2_m2m_ctx_release(sess->m2m_ctx)
v4l2_m2m_cancel_job()
spin_lock_irqsave(&m2m_dev->job_spinlock, flags);Since core->m2m_dev was already freed during unbind, could this dereference cause memory corruption or a crash?
[Severity: High]
This is a pre-existing issue, but does returning from vdec_remove() without waiting for open file descriptors to close lead to a use-after-free of the core driver structure?
The core structure and its mutex are allocated via devres (devm_kzalloc) during vdec_probe(). If the device is unbound, devres will automatically free the core structure as soon as vdec_remove() returns.
If an ioctl is currently blocked waiting on vdev->lock (which points to core->lock), it will attempt to unlock a freed mutex when it resumes.
Additionally, if the asynchronous esparser_queue_work runs after unbind, it will dereference the freed sess->core.
Could the driver synchronize active sessions or use a different lifecycle for the core structure to prevent this?
Quoted 2 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0v4l2_device_unregister (&core->v4l2_dev); }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
PATCH v7 2/19 media: meson: vdec: Fix STREAMON / STREAMOFF race conditions and session teardown
In reply to Anand MoonThe vdec driver suffered from unsafe state transitions and race conditions when handling concurrent STREAMON / STREAMOFF calls and dynamic resolution change (DRC) events. Global context pointers (e.g. core->cur_sess) and session status flags were updated outside proper lock boundaries, allowing parallel threads to corrupt hardware state under load.
Address these architectural stability flaws with the following changes:
1. In vdec_start_streaming(), safely encapsulate hardware occupancy evaluations and target session context claims within a secure mutex lock (core->lock). This blocks overlapping multi-threaded STREAMON calls from creating concurrent assignment conflicts.
2. Restructure initialization error path handlers to avoid global memory maps cross-contamination. Segregate buffer flushes cleanly based strictly on the active vb2 queue type (OUTPUT vs CAPTURE) to avoid incorrectly reclaiming undecoded ready blocks.
3. In vdec_stop_streaming(), introduce state-aware tracking to accurately identify DRC conditions. If a resolution modification forces a capture queue reset while the output stream is still live, preserve the driver's inner runtime states and bypass premature hardware power-off sweeps.
4. Enforce strict null-pointer safety limits inside DMA unmapping sequences. Explicitly clear tracking metadata entries (sess->vififo_vaddr = NULL) inside the VIFIFO freeing routines to neutralize accidental double-free risks.
Together these changes harden the driver against concurrency bugs, eliminate memory leaks, and ensure predictable session lifecycle management under multi threaded workloads.
--- drivers/staging/media/meson/vdec/vdec.c | 136 +++++++++++++++++------- 1 file changed, 95 insertions(+), 41 deletions(-)
drivers/staging/media/meson/vdec/vdec.c +95 -41
@@ -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) { + ret = -EBUSY ; +mutex_unlock (&core->lock); + goto err_unlock_no_hw; + } + +/* 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 =
@@ -319,7 +334,7 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count)if (!sess->vififo_vaddr) {dev_err (sess->core->dev,"Failed to request VIFIFO buffer\n" ); ret = -ENOMEM ; - goto bufs_done; + goto err_cleanup_session; } sess->should_stop =0 ;
@@ -333,33 +348,43 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) ret =vdec_poweron (sess);if (ret) - goto vififo_free; + goto err_free_vififo; sess->sequence_cap =0 ; sess->sequence_out =0 ; +if (vdec_codec_needs_recycle (sess)) 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 ; -vififo_free: -dma_free_coherent (sess->core->dev, sess->vififo_size, - sess->vififo_vaddr, sess->vififo_paddr); -bufs_done: -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))) -v4l2_m2m_buf_done (buf,VB2_BUF_STATE_QUEUED ); - +err_free_vififo: +if (sess->vififo_vaddr) { +dma_free_coherent (sess->core->dev, sess->vififo_size, + sess->vififo_vaddr, sess->vififo_paddr); + sess->vififo_vaddr =NULL ; + sess->vififo_paddr =0 ; + } +err_cleanup_session:if (q->type ==V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ) sess->streamon_out =0 ;else sess->streamon_cap =0 ; +mutex_lock (&core->lock); +if (core->cur_sess == sess) + core->cur_sess =NULL ; +if (sess->status !=STATUS_NEEDS_RESUME ) + sess->status =STATUS_STOPPED ; +mutex_unlock (&core->lock); +err_unlock_no_hw: +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))) +v4l2_m2m_buf_done (buf,VB2_BUF_STATE_QUEUED ); +return ret; }
@@ -399,30 +424,13 @@ 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; +bool full_cleanup = false; -if (sess->status ==STATUS_RUNNING || - sess->status ==STATUS_INIT || - (sess->status ==STATUS_NEEDS_RESUME && - (!sess->streamon_out || !sess->streamon_cap))) { -if (vdec_codec_needs_recycle (sess)) -kthread_stop (sess->recycle_thread); - -vdec_poweroff (sess); -vdec_free_canvas (sess); -dma_free_coherent (sess->core->dev, sess->vififo_size, - sess->vififo_vaddr, sess->vififo_paddr); -vdec_reset_timestamps (sess); -vdec_reset_bufs_recycle (sess); -kfree (sess->priv); - sess->priv =NULL ; - core->cur_sess =NULL ; - sess->status =STATUS_STOPPED ; - } - +/* flush buffers to kill background workqueue thread */ if (q->type ==V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ) {while ((buf =v4l2_m2m_src_buf_remove (sess->m2m_ctx)))v4l2_m2m_buf_done (buf,VB2_BUF_STATE_ERROR ); - sess->streamon_out =0 ; }else {/* Drain remaining refs if was still running */
@@ -431,9 +439,55 @@ static void vdec_stop_streaming(struct vb2_queue *q)while ((buf =v4l2_m2m_dst_buf_remove (sess->m2m_ctx)))v4l2_m2m_buf_done (buf,VB2_BUF_STATE_ERROR ); - sess->streamon_cap =0 ; } + +/* Hold core lock continuously for the state and resource processing */ +mutex_lock (&core->lock); + old_status = sess->status; + +if (old_status ==STATUS_RUNNING || old_status ==STATUS_INIT || + (old_status ==STATUS_NEEDS_RESUME && (!sess->streamon_out || + !sess->streamon_cap))) { +/* +* If it's a DRC event (Capture queue streamoff only), preserve +* the status +*/ +if (old_status ==STATUS_NEEDS_RESUME && sess->streamon_out) { + full_cleanup = false; + }else { + full_cleanup = true; + sess->status =STATUS_STOPPED ; + } + } + +if (full_cleanup) { +if ((q->type !=V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + !sess->streamon_out) &&vdec_codec_needs_recycle (sess)) { +kthread_stop (sess->recycle_thread); + } + +vdec_poweroff (sess); +vdec_free_canvas (sess); + +if (sess->vififo_vaddr) { +dma_free_coherent (sess->core->dev, sess->vififo_size, + sess->vififo_vaddr, sess->vififo_paddr); + sess->vififo_vaddr =NULL ; + sess->vififo_paddr =0 ; + } + +vdec_reset_timestamps (sess); +vdec_reset_bufs_recycle (sess); + core->cur_sess =NULL ; + +kfree (sess->priv); + sess->priv =NULL ; + }else { +if (sess->status ==STATUS_NEEDS_RESUME ) + sess->changed_format =0 ; + } +mutex_unlock (&core->lock); }static int vdec_vb2_buf_prepare (struct vb2_buffer *vb)
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 02/19] media: meson: vdec: Fix STREAMON / STREAMOFF race conditions and session teardown
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
New issues:
- [High] The vdec_recycle_thread is conditionally skipped from being stopped but its resources are unconditionally freed during a partial STREAMOFF, leading to Use-After-Free and list corruption.
- [High] Failing start_streaming with -EBUSY permanently leaks streamon flags, causing state inconsistency and cross-session hardware corruption.
Pre-existing issues:
- [High] The return value of kthread_run() is not checked, leading to a kernel crash when kthread_stop() is called on an ERR_PTR.
- [High] vdec_reset_timestamps accesses the timestamps list without holding ts_spinlock, leading to list corruption.
- [High] Missing cancel_work_sync for esparser_queue_work causes UAF and unclocked register access.
--
--- Patch [2]: [PATCH v7 02/19] media: meson: vdec: Fix STREAMON / STREAMOFF race conditions and session teardown --- commit c8f8b26e4a120933b5b1d329b3cc6e97ea21778d
Author: Anand Moon <[email protected]>
Subject: media: meson: vdec: Fix STREAMON / STREAMOFF race conditions and session teardown
This commit addresses race conditions during concurrent STREAMON / STREAMOFF calls and dynamic resolution changes. It restructures initialization paths, error handling, and state tracking to prevent hardware state corruption.
Quoted 3 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0
[ ... ]
Quoted 9 lines
drivers/staging/media/meson/vdec/vdec.c +1
-2@@ -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 ;
drivers/staging/media/meson/vdec/vdec.c static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) · line 318
Quoted 10 lines
drivers/staging/media/meson/vdec/vdec.c +10
-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) { + ret = -EBUSY ; +mutex_unlock (&core->lock); + goto err_unlock_no_hw; + }
[Severity: High]
If we jump to err_unlock_no_hw here, doesn't it bypass the err_cleanup_session label that clears the streamon flags?
If session B calls VIDIOC_STREAMON on its OUTPUT queue and hits this path, sess->streamon_out will remain 1.
Could a subsequent VIDIOC_QBUF from session B cause vdec_vb2_buf_queue to schedule esparser_queue_work, potentially overwriting the active hardware registers of the current session?
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) · line 358
Quoted 6 lines
drivers/staging/media/meson/vdec/vdec.c +1
-0sess->sequence_cap =0 ; sess->sequence_out =0 ; +if (vdec_codec_needs_recycle (sess)) sess->recycle_thread =kthread_run (vdec_recycle_thread, sess,"vdec_recycle" );
[Severity: High]
This is a pre-existing issue, but does this code properly handle thread creation failure?
If kthread_run fails due to memory constraints, it returns an ERR_PTR. Later in vdec_stop_streaming, this pointer is passed unconditionally to kthread_stop.
Could this cause a kernel panic when kthread_stop attempts to dereference the ERR_PTR?
drivers/staging/media/meson/vdec/vdec.c static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) · line 361
Quoted 4 lines
drivers/staging/media/meson/vdec/vdec.c +0
-2- sess->status =STATUS_INIT ; - core->cur_sess = sess;schedule_work (&sess->esparser_queue_work);return 0 ;
[Severity: High]
This is a pre-existing issue, but does closing the file descriptor safely clean up the workqueue scheduled here?
When the client queues a buffer and then closes the file descriptor, vdec_close destroys mutexes and frees the amvdec_session memory via kfree without cancelling the pending esparser_queue_work.
When the workqueue executes vdec_esparser_queue_work, could it access the freed session memory and attempt to take a destroyed mutex?
[ ... ]
Quoted 1 line
drivers/staging/media/meson/vdec/vdec.c +0
-0@@ -399,30 +424,13 @@ static void vdec_stop_streaming(struct vb2_queue *q)
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static void vdec_stop_streaming(struct vb2_queue *q) · line 470
Quoted 7 lines
drivers/staging/media/meson/vdec/vdec.c +7
-0+if (full_cleanup) { +if ((q->type !=V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + !sess->streamon_out) &&vdec_codec_needs_recycle (sess)) { +kthread_stop (sess->recycle_thread); + } + +vdec_poweroff (sess);
[Severity: High]
If a userspace client starts streaming on both queues and then calls VIDIOC_STREAMOFF on the CAPTURE queue, the newly added condition evaluating q->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE or !sess->streamon_out will evaluate to false, skipping kthread_stop.
Since full_cleanup is true, the code proceeds to tear down resources via vdec_reset_bufs_recycle and kfree of sess->priv.
Could this lead to a use-after-free or list corruption if the recycle thread continues executing while its backing resources are freed?
drivers/staging/media/meson/vdec/vdec.c static void vdec_stop_streaming(struct vb2_queue *q) · line 480
Quoted 10 lines
drivers/staging/media/meson/vdec/vdec.c +10
-0+vdec_free_canvas (sess); + +if (sess->vififo_vaddr) { +dma_free_coherent (sess->core->dev, sess->vififo_size, + sess->vififo_vaddr, sess->vififo_paddr); + sess->vififo_vaddr =NULL ; + sess->vififo_paddr =0 ; + } + +vdec_reset_timestamps (sess);
[Severity: High]
This is a pre-existing issue, but does vdec_reset_timestamps safely access the timestamps list here?
The function iterates and frees elements of the sess->timestamps list without holding ts_spinlock.
If a concurrent threaded ISR executes amvdec_dst_buf_done_idx and calls amvdec_remove_ts, it will acquire ts_spinlock and attempt to remove an element.
Could this race condition lead to linked list corruption and use-after-free?
Quoted 11 lines
drivers/staging/media/meson/vdec/vdec.c +10
-0+vdec_reset_bufs_recycle (sess); + core->cur_sess =NULL ; + +kfree (sess->priv); + sess->priv =NULL ; + }else { +if (sess->status ==STATUS_NEEDS_RESUME ) + sess->changed_format =0 ; + } +mutex_unlock (&core->lock); }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
PATCH v7 3/19 media: meson: vdec: Fix lifecycle leaks and race conditions in recycle_thread
In reply to Anand MoonValidate the return status of kthread_run() during decoder recycling thread initialization to prevent memory leaks, and enforce robust state checking before invoking kthread_stop() to prevent kernel panics.
If the system operates under severe memory constraints, the previous implementation silently accepted error pointers returned by a failed kthread_run() operation. Attempting to pass these invalid error pointers downstream to kthread_stop() during streaming stop or application teardown routines triggered instant kernel panics.
Furthermore, when thread initialization failed, the lack of an explicit unwinding path leaked the session's private context (sess->priv) and its associated firmware memory block allocations. Because sess->status was never transitioned to STATUS_INIT, any subsequent call to stop streaming or close the driver file node failed its state validation checks, permanently orphaning active platform DMA assets and codec parameters.
Fix these thread lifetime and memory leak bugs via the following:
1. In vdec_start_streaming(), add an explicit IS_ERR() verification barrier immediately following the recycling kthread launch. If thread creation fails, capture the error code via PTR_ERR(), clear the dangling pointer tracking entry to NULL, and jump to a newly added 'err_poweroff' label to unwind hardware settings and clear out allocated structural components cleanly.
2. In vdec_stop_streaming(), protect the teardown execution track by wrapping the kthread_stop() invocation inside a safe !IS_ERR_OR_NULL() conditional block to ensure the target thread structure actually exists in system memory before stopping it.
3. In vdec_close(), cleanly wind down and terminate any lingering recycling threads at the absolute entry boundary of the routine before flushing data framework contexts and releasing the file descriptor matrices to user space.
Together, these changes ensure predictable driver behavior, prevent kernel panics during low-memory conditions, and guarantee that memory and DMA assets are fully reclaimed during teardown.
--- drivers/staging/media/meson/vdec/vdec.c | 48 +++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-)
drivers/staging/media/meson/vdec/vdec.c +45 -3
@@ -32,6 +32,10 @@ struct dummy_buf {/* 16 MiB for parsed bitstream swap exchange */ #define SIZE_VIFIFO SZ_16M +static void vdec_free_canvas (struct amvdec_session *sess); +static void vdec_reset_timestamps (struct amvdec_session *sess); +static void vdec_reset_bufs_recycle (struct amvdec_session *sess); +static u32 get_output_size (u32 width,u32 height) {return ALIGN (width * height,SZ_64K );
@@ -353,13 +357,23 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) sess->sequence_cap =0 ; sess->sequence_out =0 ; -if (vdec_codec_needs_recycle (sess)) +if (vdec_codec_needs_recycle (sess) && !sess->recycle_thread) { sess->recycle_thread =kthread_run (vdec_recycle_thread, sess,"vdec_recycle" ); - +if (IS_ERR (sess->recycle_thread)) { + ret =PTR_ERR (sess->recycle_thread); + sess->recycle_thread =NULL ; + goto err_poweroff; + } + }schedule_work (&sess->esparser_queue_work);return 0 ; +err_poweroff: +vdec_poweroff (sess); +vdec_free_canvas (sess); +vdec_reset_timestamps (sess); +vdec_reset_bufs_recycle (sess); err_free_vififo:if (sess->vififo_vaddr) { dma_free_coherent(sess->core->dev, sess->vififo_size,
@@ -464,7 +478,10 @@ static void vdec_stop_streaming(struct vb2_queue *q)if (full_cleanup) {if ((q->type !=V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || !sess->streamon_out) &&vdec_codec_needs_recycle (sess)) { -kthread_stop (sess->recycle_thread); +if (!IS_ERR_OR_NULL (sess->recycle_thread)) { +kthread_stop (sess->recycle_thread); + sess->recycle_thread =NULL ; + } }vdec_poweroff (sess);
@@ -970,6 +987,31 @@ static int vdec_open(struct file *file)static int vdec_close (struct file *file) {struct amvdec_session *sess =file_to_amvdec_session (file); +struct amvdec_core *core = sess->core; + +if (!IS_ERR_OR_NULL (sess->recycle_thread)) { +kthread_stop (sess->recycle_thread); + sess->recycle_thread =NULL ; + } + +mutex_lock (&core->lock); + +vdec_poweroff (sess); +vdec_free_canvas (sess); + core->cur_sess =NULL ; + +if (sess->vififo_vaddr) { +dma_free_coherent (core->dev, sess->vififo_size, + sess->vififo_vaddr, sess->vififo_paddr); + sess->vififo_vaddr =NULL ; + sess->vififo_paddr =0 ; + } +vdec_reset_timestamps (sess); +vdec_reset_bufs_recycle (sess); +kfree (sess->priv); + sess->priv =NULL ; + +mutex_unlock (&core->lock);v4l2_m2m_ctx_release (sess->m2m_ctx);v4l2_fh_del (&sess->fh, file);
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 03/19] media: meson: vdec: Fix lifecycle leaks and race conditions in recycle_thread
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
New issues:
- [Critical] vdec_close() frees session memory before releasing the V4L2 M2M context, triggering a NULL pointer dereference during queue teardown.
- [Critical] Unconditional execution of vdec_poweroff() and clearing of core->cur_sess in vdec_close() causes Data Abort panics and cross-session corruption.
- [Critical] Unconditional execution of vdec_poweroff() in vdec_close() leads to a double free of codec DMA allocations.
- [High] vdec_close() prematurely frees session context and DMA buffers without canceling background workqueues, causing Use-After-Free and DMA corruption.
Pre-existing issues:
- [High] Flawed logic in vdec_stop_streaming() orphans the recycle thread, leading to Use-After-Free during partial STREAMOFF.
- [Medium] The err_poweroff unwinding path in vdec_start_streaming() leaks sess->priv and associated codec assets. --
--- Patch [3]: [PATCH v7 03/19] media: meson: vdec: Fix lifecycle leaks and race conditions in recycle_thread --- commit 41dac138c8515aa623b248b4a110d8a7802b604b
Author: Anand Moon <[email protected]>
Subject: media: meson: vdec: Fix lifecycle leaks and race conditions in recycle_thread
This patch validates the kthread_run return status during recycling thread initialization to prevent memory leaks. It also ensures the thread is safely stopped in both vdec_stop_streaming and vdec_close to prevent kernel panics during teardown.
drivers/staging/media/meson/vdec/vdec.c +0 -0
[ ... ]
@@ -353,13 +357,23 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count)
Review
Quoted 8 lines
(quoted diff) +0
-0schedule_work(&sess->esparser_queue_work); return 0; err_poweroff: vdec_poweroff(sess); vdec_free_canvas(sess); vdec_reset_timestamps(sess); vdec_reset_bufs_recycle(sess);
[Severity: Medium]
This is a pre-existing issue, but does the err_poweroff unwinding path leak sess->priv?
If kthread_run() fails, execution jumps to the err_poweroff label which unwinds various hardware states but misses calling kfree(sess->priv). When the application retries VIDIOC_STREAMON, the codec's start routine unconditionally allocates a new structure over sess->priv, permanently stranding the previous allocation.
Review
Quoted 3 lines
err_free_vififo:(quoted diff) +0
-0if (sess->vififo_vaddr) { dma_free_coherent(sess->core->dev, sess->vififo_size,
[ ... ]
@@ -464,7 +478,10 @@ static void vdec_stop_streaming(struct vb2_queue *q)
Review
Quoted 3 lines
(quoted diff) +0
-0if (full_cleanup) { if ((q->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || !sess->streamon_out) && vdec_codec_needs_recycle(sess)) {
[Severity: High]
This is a pre-existing issue, but does this logic orphan the recycle thread leading to a use-after-free during partial STREAMOFF?
If userspace invokes VIDIOC_STREAMOFF exclusively on the capture queue while the output queue remains active, this condition evaluates to false, bypassing the kthread_stop() call.
Because old_status == STATUS_RUNNING, full_cleanup evaluates to true, proceeding to dismantle the session context and free sess->priv. The orphaned recycle thread may concurrently awaken and attempt to access this freed memory.
Review
Quoted 6 lines
(quoted diff) +4
-1- kthread_stop(sess->recycle_thread); + if (!IS_ERR_OR_NULL(sess->recycle_thread)) { + kthread_stop(sess->recycle_thread); + sess->recycle_thread = NULL; + } }
[ ... ]
@@ -970,6 +987,31 @@ static int vdec_open(struct file *file)
Quoted 5 lines
static int
vdec_close(struct file *file)
{(quoted diff) +2
-0struct amvdec_session *sess = file_to_amvdec_session(file); + struct amvdec_core *core = sess->core; +
Review
Quoted 10 lines
(quoted diff) +10
-0+ if (!IS_ERR_OR_NULL(sess->recycle_thread)) { + kthread_stop(sess->recycle_thread); + sess->recycle_thread = NULL; + } + + mutex_lock(&core->lock); + + vdec_poweroff(sess); + vdec_free_canvas(sess); + core->cur_sess = NULL;
[Severity: Critical]
Can this unconditional invocation of vdec_poweroff() and clearing of core->cur_sess cause Data Abort panics and cross-session corruption?
If userspace opens two instances (FDs), starts streaming on instance A (acquiring hardware ownership), and then closes instance B:
vdec_close() for instance B will unconditionally call vdec_poweroff(sess) and clear core->cur_sess, without verifying if instance B actually owns the hardware.
This forces clock disablements while instance A is actively decoding. When the hardware for instance A fires an interrupt, vdec_isr() will dereference core->cur_sess, which was just wrongfully set to NULL, causing a panic.
[Severity: Critical]
Will this unconditional vdec_poweroff() call also lead to a double free of codec DMA allocations?
If userspace cleanly shuts down streaming via VIDIOC_STREAMOFF, vdec_stop_streaming() executes vdec_poweroff(). This invokes the codec stop callback (like codec_h264_stop) which frees memory buffers but does not set their pointers to NULL.
When userspace subsequently calls close(), vdec_close() calls vdec_poweroff() again. The codec stop routine will execute a second time, passing the same non-NULL pointers to dma_free_coherent() resulting in a catastrophic double-free.
Review
Quoted 10 lines
(quoted diff) +10
-0+ + if (sess->vififo_vaddr) { + dma_free_coherent(core->dev, sess->vififo_size, + sess->vififo_vaddr, sess->vififo_paddr); + sess->vififo_vaddr = NULL; + sess->vififo_paddr = 0; + } + vdec_reset_timestamps(sess); + vdec_reset_bufs_recycle(sess); + kfree(sess->priv);
[Severity: High]
Should we call cancel_work_sync(&sess->esparser_queue_work) before freeing the session context and DMA buffers here?
If vdec_close() is called while the background workqueue is actively processing frames, the DMA buffer (sess->vififo_vaddr) and session structure (sess->priv) are immediately freed.
The concurrently executing workqueue continues its operation inside esparser_queue(), dereferencing the freed sess->priv pointer and instructing the hardware to perform DMA transfers into the freshly freed physical address, potentially causing severe memory corruption.
Review
Quoted 5 lines
(quoted diff) +3
-0+ sess->priv = NULL; + + mutex_unlock(&core->lock); v4l2_m2m_ctx_release(sess->m2m_ctx);
[Severity: Critical]
Does this sequence trigger a NULL pointer dereference during queue teardown?
If a user closes the file descriptor without calling VIDIOC_STREAMOFF, vdec_close() will free sess->priv and set it to NULL.
Immediately after, it calls v4l2_m2m_ctx_release(), which triggers vdec_stop_streaming(). Since sess->status is still STATUS_RUNNING, the driver will execute codec_ops->drain(sess).
The codec's drain function unconditionally dereferences sess->priv, which was just set to NULL, causing an immediate panic.
Quoted 1 line
v4l2_fh_del(&sess->fh, file);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
PATCH v7 4/19 media: meson: vdec: Fix use-after-free race between teardown and ISR routines
In reply to Anand MoonPrevent critical race conditions and null pointer dereferences inside the Amlogic video decoder driver by synchronizing the interrupt service routines during session teardown and file descriptor release phases.
The hardware interrupt handler (vdec_isr) and threaded handler (vdec_threaded_isr) read 'core->cur_sess' without proper synchronization. During a streaming teardown sequence via vdec_stop_streaming() or session release inside vdec_close(), the underlying session structures can be modified or freed concurrently. This creates a transient window where a late-stage hardware interrupt can wake up, read 'core->cur_sess', and dereference an invalid or NULL session pointer, causing a kernel panic.
Update both the hard and threaded interrupt service routines to capture the current session reference using an smp_load_acquire() barrier snapshot. If the snapshot resolves to NULL, terminate immediately with IRQ_HANDLED to guarantee execution contexts do not process stale structures.
--- drivers/staging/media/meson/vdec/vdec.c | 22 ++++++++++++++++++++-- drivers/staging/media/meson/vdec/vdec.h | 2 ++ 2 files changed, 22 insertions(+), 2 deletions(-)
drivers/staging/media/meson/vdec/vdec.c +20 -2
@@ -484,6 +484,9 @@ static void vdec_stop_streaming(struct vb2_queue *q) } } +/* Synchronize and flush pending hardware interrupt service routines */ +synchronize_irq (core->vdec_irq); +vdec_poweroff (sess);vdec_free_canvas (sess);
@@ -989,6 +992,9 @@ static int vdec_close(struct file *file)struct amvdec_session *sess =file_to_amvdec_session (file);struct amvdec_core *core = sess->core; +/* Synchronize and flush pending hardware interrupt service routines */ +synchronize_irq (core->vdec_irq); +if (!IS_ERR_OR_NULL (sess->recycle_thread)) {kthread_stop (sess->recycle_thread); sess->recycle_thread =NULL ;
@@ -1038,7 +1044,12 @@ static const struct v4l2_file_operations vdec_fops = {static irqreturn_t vdec_isr (int irq,void *data) {struct amvdec_core *core = data; -struct amvdec_session *sess = core->cur_sess; +struct amvdec_session *sess; + +/* Secure an atomic acquire snapshot to protect against concurrent teardown */ + sess =smp_load_acquire (&core->cur_sess); +if (!sess) +return IRQ_HANDLED ; sess->last_irq_jiffies =get_jiffies_64 ();
@@ -1048,7 +1059,12 @@ static irqreturn_t vdec_isr(int irq, void *data)static irqreturn_t vdec_threaded_isr (int irq,void *data) {struct amvdec_core *core = data; -struct amvdec_session *sess = core->cur_sess; +struct amvdec_session *sess; + +/* Prevent late-stage threaded interrupts from dereferencing a NULL session */ + sess =smp_load_acquire (&core->cur_sess); +if (!sess) +return IRQ_HANDLED ;return sess->fmt_out->codec_ops->threaded_isr (sess); }
@@ -1136,6 +1152,8 @@ static int vdec_probe(struct platform_device *pdev)if (irq <0 )return irq; + core->vdec_irq = irq; + ret =devm_request_threaded_irq (core->dev, irq, vdec_isr, vdec_threaded_isr,IRQF_ONESHOT ,"vdec" , core);
drivers/staging/media/meson/vdec/vdec.h +2 -0
@@ -67,6 +67,7 @@ struct amvdec_session;* @v4l2_dev: v4l2 device * @cur_sess: current decoding session * @lock: video device lock +* @vdec_irq: irq for video decoding */ struct amvdec_core {void __iomem *dos_base;
@@ -93,6 +94,7 @@ struct amvdec_core {struct amvdec_session *cur_sess;struct mutex lock; +int vdec_irq; };/**
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 04/19] media: meson: vdec: Fix use-after-free race between teardown and ISR routines
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] Returning IRQ_HANDLED for a NULL session in the hardware interrupt handler causes an infinite interrupt storm.
Pre-existing issues:
- [High] Calling synchronize_irq() before stopping hardware interrupts fails to fix the use-after-free race condition.
- [High] Unbind operations destroy devm-managed structures and the m2m device while open file descriptors remain, causing UAF on closure. --
--- Patch [4]: [PATCH v7 04/19] media: meson: vdec: Fix use-after-free race between teardown and ISR routines --- commit 7f1533ed7c182234db28cc4195417115cb635bb9
Author: Anand Moon <[email protected]>
media: meson: vdec: Fix use-after-free race between teardown and ISR routines
This patch attempts to prevent race conditions and null pointer dereferences by synchronizing interrupt service routines during session teardown and file descriptor release phases. It adds synchronize_irq() calls and uses smp_load_acquire() to safely capture the current session in the ISR handlers.
drivers/staging/media/meson/vdec/vdec.c static void vdec_stop_streaming(struct vb2_queue *q) · line 489
Quoted 11 lines
drivers/staging/media/meson/vdec/vdec.c +3
-0@@ -484,6 +484,9 @@ static void vdec_stop_streaming(struct vb2_queue *q) } } +/* Synchronize and flush pending hardware interrupt service routines */ +synchronize_irq (core->vdec_irq); +
[Severity: High]
This isn't a bug introduced by this patch, but does calling synchronize_irq() before vdec_poweroff() actually prevent the race condition?
Since synchronize_irq() only waits for currently executing handlers to finish and does not disable the IRQ line or prevent new hardware interrupts, it seems new interrupts can still fire immediately after it returns.
Because the hardware isn't stopped until vdec_poweroff() runs, could a newly triggered ISR observe a valid session pointer and execute concurrently with the rest of the teardown sequence?
Should the hardware be stopped via vdec_poweroff() before synchronizing IRQs?
Quoted 3 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0vdec_poweroff (sess);vdec_free_canvas (sess);
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static int vdec_close(struct file *file) · line 997
Quoted 7 lines
drivers/staging/media/meson/vdec/vdec.c +3
-0@@ -989,6 +992,9 @@ static int vdec_close(struct file *file)struct amvdec_session *sess =file_to_amvdec_session (file);struct amvdec_core *core = sess->core; +/* Synchronize and flush pending hardware interrupt service routines */ +synchronize_irq (core->vdec_irq); +
[Severity: High]
This is a pre-existing issue, but does the driver handle unbind safely if user-space keeps the video device file open?
If the driver is unbound, vdec_remove() executes:
vdec_remove()
video_unregister_device(core->vdev_dec);
v4l2_m2m_release(core->m2m_dev);
v4l2_device_unregister(&core->v4l2_dev);Since devres automatically frees the amvdec_core structure upon unbind completion, and video_unregister_device() does not forcefully close open file descriptors, could a later call to vdec_close() dereference the freed core structure?
For instance, this newly added synchronize_irq(core->vdec_irq) call would use the freed core pointer. Similarly, v4l2_m2m_ctx_release(sess->m2m_ctx) later in vdec_close() would use the freed core->m2m_dev.
Quoted 3 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0if (!IS_ERR_OR_NULL (sess->recycle_thread)) {kthread_stop (sess->recycle_thread); sess->recycle_thread =NULL ;
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static const struct v4l2_file_operations vdec_fops = { · line 1052
Quoted 11 lines
drivers/staging/media/meson/vdec/vdec.c +6
-1@@ -1038,7 +1044,12 @@ static const struct v4l2_file_operations vdec_fops = {static irqreturn_t vdec_isr (int irq,void *data) {struct amvdec_core *core = data; -struct amvdec_session *sess = core->cur_sess; +struct amvdec_session *sess; + +/* Secure an atomic acquire snapshot to protect against concurrent teardown */ + sess =smp_load_acquire (&core->cur_sess); +if (!sess) +return IRQ_HANDLED ;
[Severity: High]
Can returning IRQ_HANDLED here cause an infinite interrupt storm?
If the handler evaluates the session to NULL and returns IRQ_HANDLED without acknowledging or clearing the interrupt at the hardware level, the generic IRQ framework will unmask the interrupt line.
Since the hardware interrupt source hasn't been cleared, won't the interrupt controller immediately re-trigger the interrupt, resulting in a soft lockup or CPU stall?
Should this return IRQ_NONE so the IRQ subsystem can detect it as spurious and eventually disable the IRQ line?
Quoted 4 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0sess->last_irq_jiffies =get_jiffies_64 ();
drivers/staging/media/meson/vdec/vdec.c static irqreturn_t vdec_isr(int irq, void *data) · line 1067
Quoted 10 lines
drivers/staging/media/meson/vdec/vdec.c +6
-1@@ -1048,7 +1059,12 @@ static irqreturn_t vdec_isr(int irq, void *data)static irqreturn_t vdec_threaded_isr (int irq,void *data) {struct amvdec_core *core = data; -struct amvdec_session *sess = core->cur_sess; +struct amvdec_session *sess; + +/* Prevent late-stage threaded interrupts from dereferencing a NULL session */ + sess =smp_load_acquire (&core->cur_sess); +if (!sess) +return IRQ_HANDLED ;
[Severity: High]
Does returning IRQ_HANDLED here also incorrectly propagate the same interrupt storm behavior?
Quoted 3 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0return sess->fmt_out->codec_ops->threaded_isr (sess); }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
PATCH v7 5/19 media: meson: vdec: Fix race condition and synchronize esparser IRQ
In reply to Anand MoonDuring session teardown sequences in vdec_stop_streaming() and vdec_close(), the 'esparser' hardware interrupt handler can still be actively triggered or executing on another CPU core. This creates a transient race condition where the ISR attempts to handle stream data and allocate internal tracking state structures after session contexts have been modified or freed.
Update esparser_isr() to read the current session context utilizing an smp_load_acquire() barrier snapshot. If the pointer resolves to NULL, terminate processing early with IRQ_HANDLED to protect against concurrent dismantling.
--- drivers/staging/media/meson/vdec/esparser.c | 8 ++++++++ drivers/staging/media/meson/vdec/vdec.c | 4 ++++ drivers/staging/media/meson/vdec/vdec.h | 2 ++ 3 files changed, 14 insertions(+)
drivers/staging/media/meson/vdec/esparser.c +8 -0
@@ -60,6 +60,12 @@ static irqreturn_t esparser_isr(int irq, void *dev) {int int_status;struct amvdec_core *core = dev; +struct amvdec_session *sess; + +/* Secure an atomic snapshot to protect against concurrent teardown */ + sess =smp_load_acquire (&core->cur_sess); +if (!sess) +return IRQ_HANDLED ; int_status =amvdec_read_parser (core,PARSER_INT_STATUS );amvdec_write_parser (core,PARSER_INT_STATUS , int_status);
@@ -439,6 +445,8 @@ int esparser_init(struct platform_device *pdev, struct amvdec_core *core)if (irq <0 )return irq; + core->esparser_irq = irq; + ret =devm_request_irq (dev, irq, esparser_isr,IRQF_SHARED ,"esparserirq" , core);if (ret) {
drivers/staging/media/meson/vdec/vdec.c +4 -0
@@ -486,6 +486,8 @@ static void vdec_stop_streaming(struct vb2_queue *q)/* Synchronize and flush pending hardware interrupt service routines */ synchronize_irq (core->vdec_irq); +/* Ensure esparser ISR finishes executing */ +synchronize_irq (core->esparser_irq);vdec_poweroff (sess);vdec_free_canvas (sess);
@@ -994,6 +996,8 @@ static int vdec_close(struct file *file)/* Synchronize and flush pending hardware interrupt service routines */ synchronize_irq (core->vdec_irq); +/* Ensure esparser ISR finishes executing */ +synchronize_irq (core->esparser_irq);if (!IS_ERR_OR_NULL (sess->recycle_thread)) {kthread_stop (sess->recycle_thread);
drivers/staging/media/meson/vdec/vdec.h +2 -0
@@ -68,6 +68,7 @@ struct amvdec_session;* @cur_sess: current decoding session * @lock: video device lock * @vdec_irq: irq for video decoding +* @esparser_irq: irq for elementary stream parsing */ struct amvdec_core {void __iomem *dos_base;
@@ -95,6 +96,7 @@ struct amvdec_core {struct amvdec_session *cur_sess;struct mutex lock;int vdec_irq; +int esparser_irq; };/**
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 05/19] media: meson: vdec: Fix race condition and synchronize esparser IRQ
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] The newly added early return path in esparser_isr() incorrectly returns IRQ_HANDLED without clearing the hardware interrupt status, leading to an interrupt storm and violating shared IRQ semantics.
- [Medium] The commit message falsely claims to fix a race condition where the ISR 'attempts to handle stream data and allocate internal tracking state structures', which does not exist in the code.
Pre-existing issues:
- [High] The synchronization and early-exit logic introduced by the patch is ineffective and leaves the system vulnerable to synchronous bus faults during teardown.
- [High] Unconditional execution of vdec_poweroff() in vdec_close() causes clock underflows.
--
--- Patch [5]: [PATCH v7 05/19] media: meson: vdec: Fix race condition and synchronize esparser IRQ --- commit 2f7e559e32b43d068f1e5390f023fbf7f75f1262
Author: Anand Moon <[email protected]>
media: meson: vdec: Fix race condition and synchronize esparser IRQ
This commit attempts to fix a race condition during session teardown sequences by synchronizing the esparser IRQ and adding a NULL check for the session pointer in the ISR. It aims to prevent the interrupt handler from running while context structures are dismantled.
Quoted 6 lines
During session teardown sequences in
vdec_stop_streaming()andvdec_close(), the 'esparser' hardware interrupt handler can still be actively triggered or executing on another CPU core. This creates a transient race condition where the ISR attempts to handle stream data and allocate internal tracking state structures after session contexts have been modified or freed.
[Severity: Medium]
Does the ISR actually allocate tracking state or handle stream data?
Looking at esparser_isr(), the sess pointer is retrieved and checked for NULL, but the rest of the ISR does not appear to dereference this pointer or allocate any internal tracking structures.
Quoted 5 lines
drivers/staging/media/meson/vdec/esparser.c +0
-0
drivers/staging/media/meson/vdec/esparser.c static irqreturn_t esparser_isr(int irq, void *dev) · line 68
Quoted 9 lines
drivers/staging/media/meson/vdec/esparser.c +6
-0@@ -60,6 +60,12 @@ static irqreturn_t esparser_isr(int irq, void *dev) {int int_status;struct amvdec_core *core = dev; +struct amvdec_session *sess; + +/* Secure an atomic snapshot to protect against concurrent teardown */ + sess =smp_load_acquire (&core->cur_sess); +if (!sess) +return IRQ_HANDLED ;
[Severity: High]
Is it safe to return IRQ_HANDLED here without interacting with the hardware to clear the interrupt?
Because esparser_irq is registered with IRQF_SHARED, returning IRQ_HANDLED when cur_sess is NULL falsely tells the IRQ subsystem that this driver handled the interrupt.
Will this cause an interrupt storm for level-triggered interrupts, or break spurious interrupt detection for other devices sharing this IRQ line?
Quoted 3 lines
drivers/staging/media/meson/vdec/esparser.c +0
-0int_status =amvdec_read_parser (core,PARSER_INT_STATUS );amvdec_write_parser (core,PARSER_INT_STATUS , int_status);
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static void vdec_stop_streaming(struct vb2_queue *q) · line 493
Quoted 13 lines
drivers/staging/media/meson/vdec/vdec.c +2
-0@@ -486,6 +486,8 @@ static void vdec_stop_streaming(struct vb2_queue *q)/* Synchronize and flush pending hardware interrupt service routines */ synchronize_irq (core->vdec_irq); +/* Ensure esparser ISR finishes executing */ +synchronize_irq (core->esparser_irq);vdec_poweroff (sess);vdec_free_canvas (sess);
[Severity: High]
This is a pre-existing issue, but does this synchronization logic fully close the race condition?
The synchronize_irq() call waits for in-flight ISRs, but the interrupt line remains enabled. Later in this teardown path:
vdec_stop_streaming()
vdec_poweroff(sess);
...
smp_store_release(&core->cur_sess, NULL);If the interrupt fires after vdec_poweroff() completes, but before cur_sess is set to NULL, the ISR will pass the !sess check and attempt to read PARSER_INT_STATUS from unclocked hardware.
Could this lead to a synchronous bus fault and kernel panic?
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static int vdec_close(struct file *file) · line 1003
Quoted 9 lines
drivers/staging/media/meson/vdec/vdec.c +2
-0@@ -994,6 +996,8 @@ static int vdec_close(struct file *file)/* Synchronize and flush pending hardware interrupt service routines */ synchronize_irq (core->vdec_irq); +/* Ensure esparser ISR finishes executing */ +synchronize_irq (core->esparser_irq);if (!IS_ERR_OR_NULL (sess->recycle_thread)) {kthread_stop (sess->recycle_thread);
[Severity: High]
This is a pre-existing issue, but does vdec_close() unconditionally call vdec_poweroff() even if the device was just opened without starting streaming?
If vdec_open() is called and immediately followed by vdec_close(), calling vdec_poweroff() unconditionally would execute clk_disable_unprepare() on dos_clk and dos_parser_clk which were never enabled.
Can this cause clock framework underflow warnings and potentially disable clocks for other hardware sharing the same clock tree?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
PATCH v7 6/19 media: meson: vdec: Fix race condition by canceling work sync
In reply to Anand MoonSynchronize work-queue states cleanly during streaming teardown and close operations to eliminate asynchronous pipeline race conditions.
The esparser ISR can schedule work onto 'esparser_queue_work'. While synchronize_irq() ensures the ISR itself finishes executing, it does not prevent any already scheduled work items from running concurrently during driver teardown. This causes a race condition during stream stopping or file closing.
Remove the scheduling of esparser queue work during destination buffer completion, as freeing the vififo is handled elsewhere. Add synchronous cancellation of any pending work in vdec_stop_streaming and vdec_close before synchronizing interrupts to prevent use-after-free and race conditions.
--- drivers/staging/media/meson/vdec/vdec.c | 6 ++++++ 1 file changed, 6 insertions(+)
drivers/staging/media/meson/vdec/vdec.c +6 -0
@@ -484,11 +484,15 @@ static void vdec_stop_streaming(struct vb2_queue *q) } } +mutex_unlock (&core->lock);/* Synchronize and flush pending hardware interrupt service routines */ synchronize_irq (core->vdec_irq);/* Ensure esparser ISR finishes executing */ synchronize_irq (core->esparser_irq); +cancel_work_sync (&sess->esparser_queue_work); +mutex_lock (&core->lock); +vdec_poweroff (sess);vdec_free_canvas (sess);
@@ -999,6 +1003,8 @@ static int vdec_close(struct file *file)/* Ensure esparser ISR finishes executing */ synchronize_irq (core->esparser_irq); +cancel_work_sync (&sess->esparser_queue_work); +if (!IS_ERR_OR_NULL (sess->recycle_thread)) {kthread_stop (sess->recycle_thread); sess->recycle_thread =NULL ;
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 06/19] media: meson: vdec: Fix race condition by canceling work sync
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] Calling `cancel_work_sync(&sess->esparser_queue_work)` inside `vdec_stop_streaming()` introduces a…
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Calling cancel_work_sync(&sess->esparser_queue_work) inside vdec_stop_streaming() introduces a guaranteed AB-BA deadlock during VIDIOC_STREAMOFF.
- [High] cancel_work_sync() and synchronize_irq() are invoked before the hardware is powered off, leaving a race window where late interrupts can schedule work on a soon-to-be-freed session.
- [High] vdec_close() unconditionally powers off shared hardware state and clears the active session, allowing a rogue client to crash an ongoing decoding session.
- [Medium] The patch fails to remove the scheduling of esparser_queue_work during destination buffer completion, contradicting its own commit message.
--
--- Patch [6]: [PATCH v7 06/19] media: meson: vdec: Fix race condition by canceling work sync --- commit 9631e0314174ce9dbe778bf9146832ff6e5a0364
Author: Anand Moon <[email protected]>
media: meson: vdec: Fix race condition by canceling work sync
This commit attempts to synchronize workqueue states during teardown to eliminate race conditions. It adds synchronous cancellation of esparser_queue_work in vdec_stop_streaming() and vdec_close().
Quoted 2 lines
Remove the scheduling of esparser queue work during destination buffer completion, as freeing the vififo is handled elsewhere.
[Severity: Medium]
Did this actually get removed? Looking at dst_buf_done() in vdec_helpers.c, it still contains:
/* Buffer done probably means the vififo got freed */
schedule_work(&sess->esparser_queue_work);This seems to contradict the commit message and means the workqueue is still scheduled asynchronously during destination buffer completion.
Quoted 5 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0
drivers/staging/media/meson/vdec/vdec.c static void vdec_stop_streaming(struct vb2_queue *q) · line 493
Quoted 10 lines
drivers/staging/media/meson/vdec/vdec.c +2
-0@@ -484,11 +484,15 @@ static void vdec_stop_streaming(struct vb2_queue *q) } } +mutex_unlock (&core->lock);/* Synchronize and flush pending hardware interrupt service routines */ synchronize_irq (core->vdec_irq);/* Ensure esparser ISR finishes executing */ synchronize_irq (core->esparser_irq); +cancel_work_sync (&sess->esparser_queue_work);
[Severity: High]
Can this cause a deadlock?
When userspace calls the streamoff ioctl, the v4l2 core acquires the queue lock (&sess->lock) before calling this vdec_stop_streaming() callback. Since cancel_work_sync() waits for the worker thread to finish, and the worker esparser_queue_all_src() attempts to acquire mutex_lock(&sess->lock), won't these threads wait on each other indefinitely if the worker is already running?
[Severity: High]
Could this teardown ordering lead to a use-after-free?
Because cancel_work_sync() and synchronize_irq() are called before vdec_poweroff() disables the hardware, the hardware is still active and can generate interrupts. An interrupt could fire immediately after cancel_work_sync() returns, causing dst_buf_done() to schedule the work again. If the sess structure is subsequently freed, won't the newly scheduled work dereference a freed pointer?
Quoted 4 lines
drivers/staging/media/meson/vdec/vdec.c +2
-0+mutex_lock (&core->lock); +vdec_poweroff (sess);vdec_free_canvas (sess);
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static int vdec_close(struct file *file) · line 1006
Quoted 5 lines
drivers/staging/media/meson/vdec/vdec.c +1
-0@@ -999,6 +1003,8 @@ static int vdec_close(struct file *file)/* Ensure esparser ISR finishes executing */ synchronize_irq (core->esparser_irq); +cancel_work_sync (&sess->esparser_queue_work);
[Severity: High]
Does this have the same teardown ordering race as vdec_stop_streaming()?
The cancel_work_sync() call happens before vdec_poweroff(sess). If the hardware generates a late interrupt, could the work be scheduled again right before the session is freed?
drivers/staging/media/meson/vdec/vdec.c static int vdec_close(struct file *file) · line 1010
Quoted 4 lines
drivers/staging/media/meson/vdec/vdec.c +1
-0+if (!IS_ERR_OR_NULL (sess->recycle_thread)) {kthread_stop (sess->recycle_thread); sess->recycle_thread =NULL ;
Later in vdec_close(), the hardware is powered down and the session is cleared:
mutex_lock(&core->lock);
vdec_poweroff(sess);
vdec_free_canvas(sess);
core->cur_sess = NULL;[Severity: High]
Does this unconditionally terminate the hardware state of the active session?
Because this lacks an if (core->cur_sess == sess) check (which is safely used in vdec_stop_streaming), if a secondary client opens and immediately closes the device node, won't it forcefully power off the hardware and clear the session while the primary client is actively streaming?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
PATCH v7 7/19 media: meson: vdec: Refactor esparser work queue and fix teardown race
In reply to Anand MoonRework the esparser work queue logic to prevent concurrent queue mutations and fix a race condition during streaming teardown.
The previous implementation relied on v4l2_m2m_for_each_src_buf_safe(), which held the session mutex across the entire queue traversal. This blocked concurrent operations, caused inaccurate buffer mapping, and triggered 100+ second watchdog freezes due to an unyielding polling loop. Additionally, the low-level parser handled buffer ownership adjustments and completion callbacks directly, leading to inconsistent error paths and races between streamon and streamoff.
Fix this by utilizing the modern scoped_guard(mutex) mechanism inside the main worker loop. This allows the session lock to be automatically dropped on any conditional breakout path, preventing voluntary scheduling bugs while invoking cond_resched(). De-couple buffer lifecycles from the hardware parser routine by leveraging safe, linear fetches via v4l2_m2m_next_src_buf() and v4l2_m2m_src_buf_remove(), ensuring orderly frame transactions. Refactor esparser_queue() to return explicit error codes (such as -EBADMSG, -EIO, and -EAGAIN) to safely handle buffer states.
Clean up teardown paths: Execute cancel_work_sync() inside vdec_stop_streaming() and vdec_close() immediately after synchronize_irq(). This ensures any pending scheduled queue work is fully flushed and canceled before the driver destroys hardware instances.
--- drivers/staging/media/meson/vdec/esparser.c | 69 +++++++++++++++------ 1 file changed, 49 insertions(+), 20 deletions(-)
drivers/staging/media/meson/vdec/esparser.c +49 -20
@@ -328,15 +328,11 @@ esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf)return -EAGAIN ; } -v4l2_m2m_src_buf_remove_by_buf (sess->m2m_ctx, vbuf); - offset =esparser_get_offset (sess); ret =amvdec_add_ts (sess, vb->timestamp, vbuf->timecode, offset, vbuf->flags); -if (ret) { -v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_ERROR ); +if (ret)return ret; - }dev_dbg (core->dev,"esparser: ts = %llu pld_size = %u offset = %08X flags = %08X\n" , vb->timestamp, payload_size, offset, vbuf->flags);
@@ -348,12 +344,10 @@ esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf)if (sess->fmt_out->pixfmt ==V4L2_PIX_FMT_VP9 ) { payload_size =vp9_update_header (core, vb); -/* If unable to alter buffer to add headers */ if (payload_size ==0 ) { +dev_err (core->dev,"esparser: VP9 header update failed\n" );amvdec_remove_ts (sess, vb->timestamp); -v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_ERROR ); - -return 0 ; +return -EBADMSG ; } }
@@ -363,33 +357,68 @@ esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf)if (ret <=0 ) {dev_warn (core->dev,"esparser: input parsing error\n" );amvdec_remove_ts (sess, vb->timestamp); -v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_ERROR );amvdec_write_parser (core,PARSER_FETCH_CMD ,0 ); - -return 0 ; +return -EIO ; }atomic_inc (&sess->esparser_queued_bufs); -v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_DONE );return 0 ; }void esparser_queue_all_src (struct work_struct *work) { -struct v4l2_m2m_buffer *buf, *n;struct amvdec_session *sess = container_of(work, struct amvdec_session, esparser_queue_work); +struct device *dev = sess->core->dev_dec; +int ret; -mutex_lock (&sess->lock); -v4l2_m2m_for_each_src_buf_safe (sess->m2m_ctx, buf, n) { -if (sess->should_stop) -break ; +while (1 ) { +struct vb2_v4l2_buffer *vbuf =NULL ; +bool processed_frame = false; + + scoped_guard(mutex, &sess->lock) { +/* Safe atomic tracking check: exit loop if session is shutting down */ +if (sess->should_stop) +return ; + +/* Queue completely empty: exit work loop cleanly */ + vbuf =v4l2_m2m_next_src_buf (sess->m2m_ctx); +if (!vbuf) +break ; + +/* Stop processing if we hit the end-of-stream drain buffer */ +if (vbuf->flags &V4L2_BUF_FLAG_LAST ) +break ; + +/* Check hardware FIFO limits safely inside the locks */ + ret =esparser_queue (sess, vbuf); +if (ret == -EAGAIN ) +break ; + +/* Pop the buffer from the source queue since it is now processed */ + vbuf =v4l2_m2m_src_buf_remove (sess->m2m_ctx); +if (!vbuf) { +dev_dbg (dev,"Buffer missing during queue removal\n" ); +break ; + } + +/* Complete the buffer transaction based on parser results */ +if (ret <0 ) +v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_ERROR ); +else +v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_DONE ); -if (esparser_queue (sess, &buf->vb) <0 ) +/* Set tracking flag indicating transaction completion */ + processed_frame = true; + } + +if (processed_frame)break ; + +/* Give other threads and IRQ routines a window to execute while unlocked */ +cond_resched (); } -mutex_unlock (&sess->lock); }int esparser_power_up (struct amvdec_session *sess)
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 07/19] media: meson: vdec: Refactor esparser work queue and fix teardown race
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] The worker thread enters a 100% CPU infinite loop when the queue is empty, full, or hits an EOS buffer, and prematurely exits after a single frame on success due to an inverted loop condition and misuse of scoped_guard.
Pre-existing issues:
- [Critical] Maliciously crafted VP9 superframe headers can cause an integer overflow in total_datasize, bypassing bounds checks and triggering a massive out-of-bounds memmove(). --
--- Patch [7]: [PATCH v7 07/19] media: meson: vdec: Refactor esparser work queue and fix teardown race --- commit f4cf4afadbba1cd9ed8438a311d676dabd768d33
Author: Anand Moon <[email protected]>
media: meson: vdec: Refactor esparser work queue and fix teardown race
Rework the esparser work queue logic to prevent concurrent queue mutations and fix a race condition during streaming teardown.
Quoted 4 lines
drivers/staging/media/meson/vdec/esparser.c +0
-0
[ ... ]
Quoted 4 lines
drivers/staging/media/meson/vdec/esparser.c +0
-0@@ -348,12 +344,10 @@ esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf)if (sess->fmt_out->pixfmt ==V4L2_PIX_FMT_VP9 ) { payload_size =vp9_update_header (core, vb);
drivers/staging/media/meson/vdec/esparser.c esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) · line 352
Quoted 10 lines
drivers/staging/media/meson/vdec/esparser.c +2
-4-/* If unable to alter buffer to add headers */ if (payload_size ==0 ) { +dev_err (core->dev,"esparser: VP9 header update failed\n" );amvdec_remove_ts (sess, vb->timestamp); -v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_ERROR ); - -return 0 ; +return -EBADMSG ; } }
[Severity: Critical]
This is a pre-existing issue, but can maliciously crafted VP9 superframe headers cause an integer overflow in vp9_update_header(), leading to a massive out-of-bounds memory copy?
Looking at vp9_update_header() in the same file:
for (cur_mag = 0; cur_mag < mag; cur_mag++) {
...
frame_size[cur_frame] |=
(dp[mag_ptr] << (cur_mag * 8));
mag_ptr++;
}
...
total_datasize += frame_size[cur_frame];If untrusted user data shifted into the signed 32-bit frame_size becomes negative, multiple negative values can sum to a small positive total_datasize, which bypasses the size checks:
new_frame_size = total_datasize + num_frames * VP9_HEADER_SIZE;
if (new_frame_size >= vb2_plane_size(buf, 0)) {
return -ENOMEM;
}Then, later in the same function:
int framesize = frame_size[cur_frame];
...
memmove(fdata + VP9_HEADER_SIZE, old_framedata, framesize);The negative framesize is implicitly converted to a size_t, which causes an out-of-bounds memmove(). Could this lead to memory corruption when handling untrusted V4L2 video stream data?
[ ... ]
Quoted 45 lines
drivers/staging/media/meson/vdec/esparser.c +0
-0void esparser_queue_all_src (struct work_struct *work){
drivers/staging/media/meson/vdec/esparser.c +35
-5-struct v4l2_m2m_buffer *buf, *n;struct amvdec_session *sess = container_of(work, struct amvdec_session, esparser_queue_work); +struct device *dev = sess->core->dev_dec; +int ret; -mutex_lock (&sess->lock); -v4l2_m2m_for_each_src_buf_safe (sess->m2m_ctx, buf, n) { -if (sess->should_stop) -break ; +while (1 ) { +struct vb2_v4l2_buffer *vbuf =NULL ; +bool processed_frame = false; + + scoped_guard(mutex, &sess->lock) { +/* Safe atomic tracking check: exit loop if session is shutting down */ +if (sess->should_stop) +return ; + +/* Queue completely empty: exit work loop cleanly */ + vbuf =v4l2_m2m_next_src_buf (sess->m2m_ctx); +if (!vbuf) +break ; + +/* Stop processing if we hit the end-of-stream drain buffer */ +if (vbuf->flags &V4L2_BUF_FLAG_LAST ) +break ; + +/* Check hardware FIFO limits safely inside the locks */ + ret =esparser_queue (sess, vbuf); +if (ret == -EAGAIN ) +break ; + +/* Pop the buffer from the source queue since it is now processed */ + vbuf =v4l2_m2m_src_buf_remove (sess->m2m_ctx); +if (!vbuf) { +dev_dbg (dev,"Buffer missing during queue removal\n" ); +break ; + } + +/* Complete the buffer transaction based on parser results */ +if (ret <0 ) +v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_ERROR );
drivers/staging/media/meson/vdec/esparser.c esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) · line 417
Quoted 10 lines
drivers/staging/media/meson/vdec/esparser.c +7
-1+else +v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_DONE ); -if (esparser_queue (sess, &buf->vb) <0 ) +/* Set tracking flag indicating transaction completion */ + processed_frame = true; + } + +if (processed_frame)break ;
[Severity: High]
Does this code inadvertently create an infinite loop?
Because scoped_guard() is implemented internally as a for-loop, the break statements inside the scoped_guard() block will only exit the lock scope, not the outer while (1) loop.
If the queue is empty (!vbuf), we break out of the scoped_guard(), processed_frame remains false, and the while (1) loop continues infinitely, consuming 100% CPU.
Conversely, if a frame is successfully processed, processed_frame becomes true, which causes the outer while (1) loop to exit after just one frame instead of continuing to drain the queue.
Could the logic here be inverted, or should the inner loop use goto instead of break to exit the outer loop?
Quoted 6 lines
drivers/staging/media/meson/vdec/esparser.c +3
-1+ +/* Give other threads and IRQ routines a window to execute while unlocked */ +cond_resched (); } -mutex_unlock (&sess->lock); }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
PATCH v7 8/19 media: meson: vdec: Fix concurrent execution races and unsafe teardown
In reply to Anand MoonAddress data races involving 'should_stop' and prevent multi-session hardware clobbering by enforcing atomic tracking and strict owner validation during device teardown.
The esparser work queue reads 'sess->should_stop' outside of critical regions without serialization primitives, risking data races or visibility delays. Furthermore, vdec_close() and vdec_stop_streaming() blindly shut down hardware components (via poweroff and canvas frees) and nullify 'core->cur_sess' without confirming that the executing session actually owns the active hardware context. In multi-session scenarios, this allows a closing inactive session to inadvertently break a running session.
To fix these synchronization and lifecycle issues with the following changes use thread-safe flagging: Wrap reads and writes of 'sess->should_stop' in READ_ONCE() and WRITE_ONCE() to prevent compiler optimizations from caching the condition variables across scheduling boundaries and optimizations across workqueue execution threads.
Also safe context releasing: transition 'core->cur_sess' pointer clearings to use smp_store_release(). This ensures all prior internal memory structures are entirely flushed and visible to other execution cores.
--- drivers/staging/media/meson/vdec/esparser.c | 2 +- drivers/staging/media/meson/vdec/vdec.c | 88 ++++++++++++++------- 2 files changed, 59 insertions(+), 31 deletions(-)
drivers/staging/media/meson/vdec/esparser.c +1 -1
@@ -379,7 +379,7 @@ void esparser_queue_all_src(struct work_struct *work) scoped_guard(mutex, &sess->lock) {/* Safe atomic tracking check: exit loop if session is shutting down */ -if (sess->should_stop) +if (READ_ONCE (sess->should_stop))return ;/* Queue completely empty: exit work loop cleanly */
drivers/staging/media/meson/vdec/vdec.c +58 -30
157 lines not on this page · raw message
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 08/19] media: meson: vdec: Fix concurrent execution races and unsafe teardown
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [Critical] Unconditional nullification of `core->cur_sess` in `vdec_close()` allows concurrent sessions…
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] Unconditional nullification of core->cur_sess in vdec_close() allows concurrent sessions to clobber hardware ownership, triggering interrupt storms and severe memory leaks.
- [High] AB-BA deadlock between vdec_stop_streaming() and esparser_queue_all_src() workqueue due to cancel_work_sync().
- [High] Inverted loop logic in esparser_queue_all_src() causes infinite CPU busy-wait on hardware backpressure and premature exit on success.
- [Medium] Memory leak of sess->priv on the newly added vdec_start_streaming() error path.
--
--- Patch [8]: [PATCH v7 08/19] media: meson: vdec: Fix concurrent execution races and unsafe teardown --- commit 4587e6bbd091d6645be624dc185c0fadded8649b
Author: Anand Moon <[email protected]>
media: meson: vdec: Fix concurrent execution races and unsafe teardown
Address data races involving 'should_stop' and prevent multi-session hardware clobbering by enforcing atomic tracking and strict owner validation during device teardown.
drivers/staging/media/meson/vdec/esparser.c void esparser_queue_all_src(struct work_struct *work) · line 383
Quoted 11 lines
drivers/staging/media/meson/vdec/esparser.c +1
-1@@ -379,7 +379,7 @@ void esparser_queue_all_src(struct work_struct *work) scoped_guard(mutex, &sess->lock) {/* Safe atomic tracking check: exit loop if session is shutting down */ -if (sess->should_stop) +if (READ_ONCE (sess->should_stop))return ;
[Severity: High]
Could the workqueue get stuck in an infinite loop due to how scoped_guard expands?
Because scoped_guard(mutex, &sess->lock) expands to a for loop, if the inner code hits an empty queue or -EAGAIN and calls break, it will only exit the scoped_guard loop, leaving processed_frame as false.
The code then evaluates if (processed_frame) break; which fails, executes cond_resched(), and loops the outer while (1) infinitely.
Conversely, if a frame is successfully processed (processed_frame is true), does the loop break entirely, causing the workqueue to exit after processing just a single frame?
[ ... ]
Quoted 4 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0
[ ... ]
Quoted 6 lines
drivers/staging/media/meson/vdec/vdec.c +0
-2@@ -388,10 +392,12 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) sess->streamon_cap =0 ;mutex_lock (&core->lock); -if (core->cur_sess == sess) - core->cur_sess =NULL ;
drivers/staging/media/meson/vdec/vdec.c static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) · line 402
Quoted 10 lines
drivers/staging/media/meson/vdec/vdec.c +6
-2-if (sess->status !=STATUS_NEEDS_RESUME ) - sess->status =STATUS_STOPPED ; +if (core->cur_sess == sess) { +/* Safely clear hardware ownership since we were confirmed as the owner */ +smp_store_release (&core->cur_sess,NULL ); +if (sess->status !=STATUS_NEEDS_RESUME ) + sess->status =STATUS_STOPPED ; + }mutex_unlock (&core->lock); err_unlock_no_hw:
[Severity: Medium]
Is there a memory leak of sess->priv on the error paths in vdec_start_streaming()?
If vdec_poweron() succeeds (which dynamically allocates sess->priv), but a later step like kthread_run() fails, the code jumps to err_poweroff. While the hardware state and vififo_vaddr are rolled back, kfree(sess->priv) appears to be missing.
If VIDIOC_STREAMON repeatedly fails, could this leak kernel memory?
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static void vdec_stop_streaming(struct vb2_queue *q) · line 502
Quoted 2 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0@@ -493,26 +502,33 @@ static void vdec_stop_streaming(struct vb2_queue *q)cancel_work_sync (&sess->esparser_queue_work);
[Severity: High]
Can this cause an AB-BA deadlock between the V4L2 core and the workqueue?
When userspace calls VIDIOC_STREAMOFF, the V4L2 core acquires the queue lock (sess->lock), then calls vdec_stop_streaming(), which calls cancel_work_sync() here.
Meanwhile, the esparser_queue_all_src() workqueue loop uses scoped_guard(mutex, &sess->lock) to acquire the exact same lock. If the workqueue is active, it will block waiting for sess->lock, while vdec_stop_streaming() blocks indefinitely waiting for the workqueue to exit.
Quoted 4 lines
drivers/staging/media/meson/vdec/vdec.c +0
-2mutex_lock (&core->lock); -vdec_poweroff (sess); -vdec_free_canvas (sess);
[ ... ]
Quoted 1 line
drivers/staging/media/meson/vdec/vdec.c +0
-0@@ -1012,21 +1031,30 @@ static int vdec_close(struct file *file)
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static int vdec_close(struct file *file) · line 1057
Quoted 8 lines
drivers/staging/media/meson/vdec/vdec.c +5
-0kfree (sess->priv); sess->priv =NULL ; +/* Unconditionally set our local status to stopped */ + sess->status =STATUS_STOPPED ; +/* Safely clear hardware ownership since we were confirmed as the owner */ +smp_store_release (&core->cur_sess,NULL ); +
[Severity: Critical]
Does this unconditionally clear the hardware ownership, even if this session doesn't own it?
If session A is streaming and session B is closed, vdec_close() for session B will execute smp_store_release(&core->cur_sess, NULL) outside of the if (core->cur_sess == sess) check.
If core->cur_sess becomes NULL while session A's hardware is active, could the next hardware interrupt trigger vdec_isr() which sees !core->cur_sess and returns IRQ_HANDLED without clearing the hardware interrupt status?
Since the interrupt is level-triggered, could this result in an immediate interrupt storm that hangs the CPU, and permanently leak session A's vififo_vaddr DMA buffers when session A is eventually closed?
Quoted 3 lines
drivers/staging/media/meson/vdec/vdec.c +0
-0mutex_unlock (&core->lock);v4l2_m2m_ctx_release (sess->m2m_ctx);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
PATCH v7 9/19 media: meson: vdec: Fix vp9 header update failure on invalid payloads
In reply to Anand MoonEnsure vp9_update_header() returns an explicit error code on invalid or malformed buffer payloads instead of silently returning zero.
When v4l2-compliance injects short, empty, or uninitialized test buffers, the validation logic catches the out-of-bounds size anomalies, but returning 0 tricks the calling esparser infrastructure into treating it as a successful 0-byte header conversion. This causes the hardware decoder engine to stall out, resulting in subsequent stream-on timeouts and failure marks inside v4l2-test-buffers.cpp.
Fix this by returning -EINVAL across all validation and bounds check failures to force immediate core framework buffer drops. Additionally, tighten array pointer checks, secure the mag_ptr bounds loop, and convert the superframe parsing indexer into an explicit if/else block for readability.
--- drivers/staging/media/meson/vdec/esparser.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-)
drivers/staging/media/meson/vdec/esparser.c +13 -6
@@ -97,11 +97,15 @@ static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf)unsigned char *old_header =NULL ; dp = (uint8_t *)vb2_plane_vaddr (buf,0 ); +if (!dp) +return -EINVAL ; + dsize =vb2_get_plane_payload (buf,0 ); -if (dsize ==vb2_plane_size (buf,0 )) { -dev_warn (core->dev,"%s: unable to update header\n" , __func__); -return 0 ; +if (dsize <=0 || dsize >vb2_plane_size (buf,0 )) { +dev_warn (core->dev,"%s: invalid payload size %d\n" , + __func__, dsize); +return -EINVAL ; } marker = dp[dsize -1 ];
@@ -109,13 +113,16 @@ static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf) num_frames = (marker &0x7 ) +1 ; mag = ((marker >>3 ) &0x3 ) +1 ; mag_ptr = dsize - mag * num_frames -2 ; -if (dp[mag_ptr] != marker) -return 0 ; +if (mag_ptr <0 || dp[mag_ptr] != marker) +return -EINVAL ; mag_ptr++;for (cur_frame =0 ; cur_frame < num_frames; cur_frame++) { frame_size[cur_frame] =0 ;for (cur_mag =0 ; cur_mag < mag; cur_mag++) { +if (mag_ptr >= dsize) +return -EINVAL ; + frame_size[cur_frame] |= (dp[mag_ptr] << (cur_mag *8 )); mag_ptr++;
@@ -140,7 +147,7 @@ static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf)if (new_frame_size >=vb2_plane_size (buf,0 )) {dev_warn (core->dev,"%s: unable to update header\n" , __func__); -return 0 ; +return -ENOMEM ; }for (cur_frame = num_frames -1 ; cur_frame >=0 ; cur_frame--) {
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 09/19] media: meson: vdec: Fix vp9 header update failure on invalid payloads
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
New issues:
- [Critical] Negative error code converted to massive positive integer bypasses validation, leading to out-of-bounds DMA fetch and memory corruption.
- [High] Early return from esparser_isr bypasses hardware interrupt clearing, leading to an unrecoverable IRQ storm and system lockup.
- [High] Using break inside scoped_guard escapes only the guard mechanism, causing an infinite busy-wait loop when the worker queue is empty.
Pre-existing issues:
- [Critical] Implicit integer promotion of VP9 frame sizes allows crafting negative sizes to bypass bounds checks, causing a massive out-of-bounds heap copy.
- [High] esparser worker accesses hardware registers and waitqueues without taking the global lock, causing hardware state corruption across concurrent sessions.
- [High] VP9 frame size validation checks against maximum buffer capacity instead of actual payload size, allowing information disclosure of uninitialized kernel memory. --
--- Patch [9]: [PATCH v7 09/19] media: meson: vdec: Fix vp9 header update failure on invalid payloads --- commit e42c61ef1ea850aa0156d3301824f682854a42da
Author: Anand Moon <[email protected]>
media: meson: vdec: Fix vp9 header update failure on invalid payloads
Ensure vp9_update_header() returns an explicit error code on invalid or malformed buffer payloads instead of silently returning zero.
drivers/staging/media/meson/vdec/esparser.c static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf) · line 101
Quoted 9 lines
drivers/staging/media/meson/vdec/esparser.c +2
-0@@ -97,11 +97,15 @@ static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf)unsigned char *old_header =NULL ; dp = (uint8_t *)vb2_plane_vaddr (buf,0 ); +if (!dp) +return -EINVAL ;
[Severity: Critical]
Does returning a negative error code here cause an issue in the caller?
Looking at esparser_queue(), it assigns the return value of vp9_update_header() to payload_size and checks if (payload_size == 0). If this returns -EINVAL and the caller assigns the result to a u32 variable, could the negative error code underflow to a massive positive integer (like 4294967274)?
This might completely bypass validation and be passed to esparser_pad_start_code(), causing an out-of-bounds memset and out-of-bounds DMA commands to the hardware.
Quoted 11 lines
drivers/staging/media/meson/vdec/esparser.c +4
-3dsize =vb2_get_plane_payload (buf,0 ); -if (dsize ==vb2_plane_size (buf,0 )) { -dev_warn (core->dev,"%s: unable to update header\n" , __func__); -return 0 ; +if (dsize <=0 || dsize >vb2_plane_size (buf,0 )) { +dev_warn (core->dev,"%s: invalid payload size %d\n" , + __func__, dsize); +return -EINVAL ; }
[ ... ]
Quoted 8 lines
drivers/staging/media/meson/vdec/esparser.c +2
-2@@ -109,13 +113,16 @@ static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf) num_frames = (marker &0x7 ) +1 ; mag = ((marker >>3 ) &0x3 ) +1 ; mag_ptr = dsize - mag * num_frames -2 ; -if (dp[mag_ptr] != marker) -return 0 ; +if (mag_ptr <0 || dp[mag_ptr] != marker) +return -EINVAL ;
drivers/staging/media/meson/vdec/esparser.c static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf) · line 127
Quoted 10 lines
drivers/staging/media/meson/vdec/esparser.c +3
-0mag_ptr++;for (cur_frame =0 ; cur_frame < num_frames; cur_frame++) { frame_size[cur_frame] =0 ;for (cur_mag =0 ; cur_mag < mag; cur_mag++) { +if (mag_ptr >= dsize) +return -EINVAL ; + frame_size[cur_frame] |= (dp[mag_ptr] << (cur_mag *8 ));
[Severity: Critical]
This isn't a bug introduced by this patch, but does this shift operation cause an implicit sign extension?
Because dp is a pointer to u8, the byte at dp[mag_ptr] undergoes integer promotion to a signed int before shifting. If cur_mag is 3 and the byte is 128 or larger, shifting it by 24 bits produces a negative value due to the sign bit.
Could this allow crafting combinations of large positive and negative frame_size values so their sum wraps around to a small positive integer, bypassing the bounds check later on and causing a massive out-of-bounds memmove()?
Quoted 2 lines
drivers/staging/media/meson/vdec/esparser.c +0
-0mag_ptr++;}
[ ... ]
drivers/staging/media/meson/vdec/esparser.c static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf) · line 151
Quoted 7 lines
drivers/staging/media/meson/vdec/esparser.c +1
-1@@ -140,7 +147,7 @@ static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf)if (new_frame_size >=vb2_plane_size (buf,0 )) {dev_warn (core->dev,"%s: unable to update header\n" , __func__); -return 0 ; +return -ENOMEM ; }
[Severity: High]
This isn't a bug introduced by this patch, but does validating against the total buffer capacity instead of the actual payload size allow information disclosure?
If the check uses vb2_plane_size(buf, 0) instead of the actual received payload length in dsize, could a valid short payload with a crafted superframe index declare frame sizes that exceed the payload length but remain under the total buffer capacity?
If so, memmove() could copy trailing uninitialized or stale kernel memory from the buffer into the active payload stream.
Outside of the immediate diff, there are a few other questions about the esparser implementation that may be worth reviewing:
[Severity: High]
This isn't a bug introduced by this patch, but does esparser_queue_all_src() queue hardware operations without acquiring the global hardware lock?
The worker runs concurrently for multiple sessions because the work queue is initialized per-session. The worker locks the per-session sess->lock but does not appear to acquire the global core->lock or verify hardware ownership before calling esparser_write_data():
esparser_queue_all_src() {
...
ret = esparser_queue(sess, vbuf);
...
}Could multiple sessions invoke esparser_write_data() simultaneously, causing conflicting writes to the hardware parser registers and corruption of the global search_done waitqueue flag?
[Severity: High]
Could the early return in esparser_isr() bypass clearing the hardware interrupt?
The interrupt service routine starts with a check:
esparser_isr() {
if (!sess)
return IRQ_HANDLED;
...
}If a hardware interrupt asserts just as the session is torn down, returning early leaves the hardware interrupt un-cleared. Would this cause the hardware to hold the interrupt line asserted indefinitely, leading to an unrecoverable IRQ storm and system lockup?
[Severity: High]
Does the break statement inside esparser_queue_all_src() properly exit the worker loop?
When checking if a buffer is available inside a scoped_guard():
esparser_queue_all_src() {
...
vbuf = v4l2_m2m_next_src_buf(sess->m2m_ctx);
if (!vbuf)
break;
...
}Since scoped_guard() is implemented internally as a for loop, breaking here would only escape the guard's inner loop. If processed_frame remains false, the outer while (1) loop proceeds past its own exit condition:
if (processed_frame)
break;
/* Give other threads and IRQ routines a window ... */
cond_resched();Could this pin a CPU core at 100% in an infinite busy-wait loop when the queue is empty?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
PATCH v7 10/19 media: meson: vdec: Fix race conditions and leaks in esparser pipeline
In reply to Anand MoonFix a circular locking dependency (lockdep warning) and a potential KASAN wild-memory-access race condition within the asynchronous esparser queue by restructuring register write and context evaluation blocks.
Previously, evaluating hardware ownership checks outside of core->lock introduced a time-of-check to time-of-use vulnerability, risking register corruption during parallel stream initialization cycles. Additionally, abruptly exiting the payload queue loop without sanitizing timestamp trackers left metadata dynamically stranded in memory, triggering regular kmemleak warnings.
Fix these flaws by moving the hardware session ownership validation and atomic stop signal checks directly inside the core->lock mutex region in esparser_queue(). Ensure that presentation timestamp pointers are cleanly removed via amvdec_remove_ts() on all early-exit routes. Finally, refine the VP9 payload evaluation logic to correctly catch negative error passes from vp9_update_header() and pass the session context explicitly to the padding helper to secure memory operations.
--- drivers/staging/media/meson/vdec/esparser.c | 29 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-)
drivers/staging/media/meson/vdec/esparser.c +25 -4
@@ -199,12 +199,16 @@ static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf)* the ESPARSER interrupt. */ static u32 esparser_pad_start_code (struct amvdec_core *core, +struct amvdec_session *sess,struct vb2_buffer *vb,u32 payload_size) {u32 pad_size =0 ;u8 *vaddr =vb2_plane_vaddr (vb,0 ); +if (!sess ||READ_ONCE (sess->should_stop) || !sess->priv || !vaddr) +return 0 ; +if (payload_size <ESPARSER_MIN_PACKET_SIZE ) { pad_size =ESPARSER_MIN_PACKET_SIZE - payload_size;memset (vaddr + payload_size,0 , pad_size);
@@ -313,6 +317,9 @@ esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf)u32 offset;u32 pad_size; +if (READ_ONCE (sess->should_stop) || !sess->priv) +return -ESHUTDOWN ; +/* * When max ref frame is held by VP9, this should be -= 3 to prevent a * shortage of CAPTURE buffers on the decoder side.
@@ -349,24 +356,38 @@ esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) vbuf->sequence = sess->sequence_out++;if (sess->fmt_out->pixfmt ==V4L2_PIX_FMT_VP9 ) { - payload_size =vp9_update_header (core, vb); +int res =vp9_update_header (core, vb); -if (payload_size ==0 ) { -dev_err (core->dev,"esparser: VP9 header update failed\n" ); +if (res <=0 ) { +dev_err (core->dev, +"esparser: VP9 header update failed (%d)\n" , + res);amvdec_remove_ts (sess, vb->timestamp);return -EBADMSG ; } + payload_size = res; + } + + pad_size =esparser_pad_start_code (core, sess, vb, payload_size); + +/* Protect hardware register writes under core->lock */ +mutex_lock (&core->lock); +if (core->cur_sess != sess ||READ_ONCE (sess->should_stop)) { +mutex_unlock (&core->lock); +amvdec_remove_ts (sess, vb->timestamp); +return -ESHUTDOWN ; } - pad_size =esparser_pad_start_code (core, vb, payload_size); ret =esparser_write_data (core, phy, payload_size + pad_size);if (ret <=0 ) {dev_warn (core->dev,"esparser: input parsing error\n" );amvdec_remove_ts (sess, vb->timestamp);amvdec_write_parser (core,PARSER_FETCH_CMD ,0 ); +mutex_unlock (&core->lock);return -EIO ; } +mutex_unlock (&core->lock);atomic_inc (&sess->esparser_queued_bufs);
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 10/19] media: meson: vdec: Fix race conditions and leaks in esparser pipeline
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
New issues:
- [High] Infinite loop and CPU soft-lockup in esparser_queue_all_src due to inverted processed_frame logic.
- [High] Self-deadlock in vdec_start_streaming and vdec_stop_streaming due to recursive acquisition of core->lock.
- [High] AB-BA deadlock in vdec_stop_streaming caused by calling cancel_work_sync while holding sess->lock.
- [High] AB-BA lock inversion deadlock between core->lock and sess->lock in the esparser workqueue.
- [High] System-wide V4L2 stall due to sleeping for up to 200ms while holding the driver-wide core->lock.
- [High] Silent omission of start code padding for unmapped buffers leads to hardware out-of-bounds DMA.
Pre-existing issues:
- [Critical] Integer overflow in vp9_update_header allows massive out-of-bounds heap corruption from untrusted payload data.
- [High] esparser_pad_start_code executes an out-of-bounds memory write before evaluating bounds checks. --
--- Patch [10]: [PATCH v7 10/19] media: meson: vdec: Fix race conditions and leaks in esparser pipeline --- commit f978165e2ef967ec3f207c80310d474efee3c9e8
Author: Anand Moon <[email protected]>
Subject: media: meson: vdec: Fix race conditions and leaks in esparser pipeline
This commit fixes a circular locking dependency and potential race conditions within the asynchronous esparser queue. It moves hardware ownership validation inside the core->lock mutex region and properly cleans up timestamp trackers. The VP9 payload evaluation logic is also refined.
Quoted 7 lines
drivers/staging/media/meson/vdec/esparser.c +0
-0@@ -199,12 +199,16 @@ static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf)* the ESPARSER interrupt. */
drivers/staging/media/meson/vdec/esparser.c static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf) · line 210
Quoted 10 lines
drivers/staging/media/meson/vdec/esparser.c +3
-0static u32 esparser_pad_start_code (struct amvdec_core *core, +struct amvdec_session *sess,struct vb2_buffer *vb,u32 payload_size) {u32 pad_size =0 ;u8 *vaddr =vb2_plane_vaddr (vb,0 ); +if (!sess ||READ_ONCE (sess->should_stop) || !sess->priv || !vaddr) +return 0 ;
[Severity: High]
Does returning 0 here skip the start code injection and cause an out-of-bounds DMA scan?
By bypassing the injection of the SEARCH_PATTERN start code, the caller esparser_queue() might interpret the 0 as success and instruct the DMA engine to fetch the buffer.
Since the search pattern is missing, could the hardware parser scan indefinitely past the valid buffer into adjacent physical memory, resulting in an out-of-bounds DMA read?
drivers/staging/media/meson/vdec/esparser.c static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf)
Quoted 4 lines
drivers/staging/media/meson/vdec/esparser.c +0
-0if (payload_size <ESPARSER_MIN_PACKET_SIZE ) { pad_size =ESPARSER_MIN_PACKET_SIZE - payload_size;memset (vaddr + payload_size,0 , pad_size); }
[Severity: High]
This is a pre-existing issue, but does this memset() write out of bounds?
If a small buffer is queued, this calculation executes before the overall bounds check is evaluated.
Could this overwrite adjacent kernel memory with zeroes before validating the size?
[ ... ]
drivers/staging/media/meson/vdec/esparser.c esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) · line 359
Quoted 6 lines
drivers/staging/media/meson/vdec/esparser.c +1
-1@@ -349,24 +356,38 @@ esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) vbuf->sequence = sess->sequence_out++;if (sess->fmt_out->pixfmt ==V4L2_PIX_FMT_VP9 ) { - payload_size =vp9_update_header (core, vb); +int res =vp9_update_header (core, vb);
[Severity: Critical]
This is a pre-existing issue, but does vp9_update_header() have an integer overflow?
By extracting untrusted payload bytes and shifting them left up to 24 bits into a signed integer, can malicious payloads construct negative frame sizes?
If a highly negative signed integer is produced and passed to memmove() as an unsigned size_t, could this cause massive out-of-bounds reads and writes on the kernel heap or DMA memory?
Quoted 7 lines
drivers/staging/media/meson/vdec/esparser.c +4
-2-if (payload_size ==0 ) { -dev_err (core->dev,"esparser: VP9 header update failed\n" ); +if (res <=0 ) { +dev_err (core->dev, +"esparser: VP9 header update failed (%d)\n" , + res);
drivers/staging/media/meson/vdec/esparser.c esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) · line 374
Quoted 10 lines
drivers/staging/media/meson/vdec/esparser.c +7
-0amvdec_remove_ts (sess, vb->timestamp);return -EBADMSG ; } + payload_size = res; + } + + pad_size =esparser_pad_start_code (core, sess, vb, payload_size); + +/* Protect hardware register writes under core->lock */ +mutex_lock (&core->lock);
[Severity: High]
Does this new mutex_lock() introduce an AB-BA lock inversion?
The background workqueue thread esparser_queue_all_src() acquires sess->lock first (via scoped_guard) and then calls esparser_queue().
By adding mutex_lock(&core->lock) here, could this conflict with V4L2 ioctls that implicitly acquire core->lock before sess->lock, resulting in an AB-BA deadlock?
drivers/staging/media/meson/vdec/esparser.c esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) · line 381
Quoted 8 lines
drivers/staging/media/meson/vdec/esparser.c +4
-1+if (core->cur_sess != sess ||READ_ONCE (sess->should_stop)) { +mutex_unlock (&core->lock); +amvdec_remove_ts (sess, vb->timestamp); +return -ESHUTDOWN ; } - pad_size =esparser_pad_start_code (core, vb, payload_size); ret =esparser_write_data (core, phy, payload_size + pad_size);
[Severity: High]
Could sleeping inside esparser_write_data() block all other V4L2 ioctls?
Because core->lock is mapped to vdev->lock, sleeping via wait_event_interruptible_timeout() while waiting for the hardware interrupt blocks all other V4L2 ioctls across the entire device for the duration of the fetch.
Can this severely degrade system performance?
drivers/staging/media/meson/vdec/esparser.c esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) · line 393
Quoted 12 lines
drivers/staging/media/meson/vdec/esparser.c +2
-0if (ret <=0 ) {dev_warn (core->dev,"esparser: input parsing error\n" );amvdec_remove_ts (sess, vb->timestamp);amvdec_write_parser (core,PARSER_FETCH_CMD ,0 ); +mutex_unlock (&core->lock);return -EIO ; } +mutex_unlock (&core->lock);atomic_inc (&sess->esparser_queued_bufs);
I also have some questions about related code paths outside the diff:
[Severity: High]
Is there an infinite loop in esparser_queue_all_src()?
When looking at the worker thread:
drivers/staging/media/meson/vdec/esparser.c:esparser_queue_all_src() {
...
bool processed_frame = false;
...
if (processed_frame)
break;
...
}Because the break statements inside the scoped_guard macro only exit the internal macro loop, processed_frame remains false when the queue is empty.
Does the inverted logic cause the thread to bypass the outer break and loop infinitely, soft-locking the CPU?
[Severity: High]
Do vdec_start_streaming() and vdec_stop_streaming() self-deadlock?
In the current code:
drivers/staging/media/meson/vdec/vdec.c:vdec_start_streaming() {
...
mutex_lock(&core->lock);
...
}Because the device's vdev->lock is mapped directly to &core->lock, the V4L2 core framework automatically acquires it before dispatching standard ioctls.
Does explicitly calling mutex_lock(&core->lock) inside these callbacks result in a permanent self-deadlock?
[Severity: High]
Is there an AB-BA deadlock during stream teardown?
During streamoff:
drivers/staging/media/meson/vdec/vdec.c:vdec_stop_streaming() {
...
cancel_work_sync(&sess->esparser_queue_work);
...
}The ioctl thread acquires sess->lock before invoking this callback. Concurrently, the background workqueue thread esparser_queue_all_src() blocks waiting for sess->lock.
Does calling cancel_work_sync() here cause the ioctl thread to block forever waiting for the worker thread, resulting in an AB-BA deadlock?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
PATCH v7 11/19 media: meson: vdec: Update core m2m stream state during transitions
In reply to Anand MoonExplicitly update the V4L2 core Memory-to-Memory (m2m) framework's internal streaming states when initiating or terminating streaming on the device queues.
Without invoking these core framework helpers, the m2m engine's bookkeeping of active source and destination queues drifts out of alignment with the driver's local 'streamon_out' and 'streamon_cap' tracking variables. This misalignment causes state validation stalls and incorrect polling outcomes when the device is subjected to quick runtime cycles or strict testing setups (such as v4l2-compliance).
Fix this by integrating v4l2_m2m_update_start_streaming_state() right after a queue is marked active in vdec_start_streaming(), and pairing it cleanly with v4l2_m2m_update_stop_streaming_state() at the end of vdec_stop_streaming() before the session core mutex is released.
--- drivers/staging/media/meson/vdec/vdec.c | 4 ++++ 1 file changed, 4 insertions(+)
drivers/staging/media/meson/vdec/vdec.c +4 -0
@@ -299,6 +299,8 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count)else sess->streamon_cap =1 ; +v4l2_m2m_update_start_streaming_state (sess->m2m_ctx, q); +if (!sess->streamon_out)return 0 ;
@@ -529,6 +531,8 @@ static void vdec_stop_streaming(struct vb2_queue *q) sess->changed_format =0 ; } +v4l2_m2m_update_stop_streaming_state (sess->m2m_ctx, q); +mutex_unlock (&core->lock); }
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 11/19] media: meson: vdec: Update core m2m stream state during transitions
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] The error path in vdec_start_streaming flushes BOTH M2M source and destination buffer queues, causing buffers of an already-streaming queue to be silently stranded.
- [High] The teardown sequence in vdec_stop_streaming calls synchronize_irq() before stopping the hardware, leaving a race window for a Use-After-Free in the interrupt handler.
- [High] Active DMA buffers are returned to the videobuf2 framework before the hardware is stopped, potentially causing memory corruption or IOMMU faults. --
--- Patch [11]: [PATCH v7 11/19] media: meson: vdec: Update core m2m stream state during transitions --- commit 8309f1636cfcc48cc67621545812af232aebc2dd
Author: Anand Moon <[email protected]>
media: meson: vdec: Update core m2m stream state during transitions
Explicitly update the V4L2 core Memory-to-Memory (m2m) framework's internal streaming states when initiating or terminating streaming on the device queues.
drivers/staging/media/meson/vdec/vdec.c static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) · line 305
Quoted 11 lines
drivers/staging/media/meson/vdec/vdec.c +2
-0@@ -299,6 +299,8 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count)else sess->streamon_cap =1 ; +v4l2_m2m_update_start_streaming_state (sess->m2m_ctx, q); +if (!sess->streamon_out)return 0 ;
[Severity: High]
This isn't a bug introduced by this patch, but does the error path in vdec_start_streaming() mistakenly strand buffers if one queue fails to start?
If the CAPTURE queue starts successfully, but the OUTPUT queue fails to start due to the hardware being busy, the error path flushes both queues:
drivers/staging/media/meson/vdec/vdec.c:vdec_start_streaming() {
...
err_unlock_no_hw:
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)))
v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED);
...
}Because the CAPTURE queue is actively streaming, wouldn't returning these buffers with VB2_BUF_STATE_QUEUED cause the vb2 core to leave them in the queued list without putting them on the done list, permanently freezing the userspace application?
[ ... ]
drivers/staging/media/meson/vdec/vdec.c static void vdec_stop_streaming(struct vb2_queue *q) · line 537
Quoted 8 lines
drivers/staging/media/meson/vdec/vdec.c +2
-0@@ -529,6 +531,8 @@ static void vdec_stop_streaming(struct vb2_queue *q) sess->changed_format =0 ; } +v4l2_m2m_update_stop_streaming_state (sess->m2m_ctx, q); +mutex_unlock (&core->lock); }
[Severity: High]
This is a pre-existing issue, but does vdec_stop_streaming() return active DMA buffers to the videobuf2 framework before the hardware is stopped?
When flushing buffers, it immediately returns them to the V4L2 core:
drivers/staging/media/meson/vdec/vdec.c:vdec_stop_streaming() {
...
/* flush buffers to kill background workqueue thread */
if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx)))
v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR);
sess->streamon_out = 0;
} else {
...
}If the vb2 framework unmaps these buffers while the hardware is still running and writing to physical memory, can this cause DMA memory corruption or IOMMU faults before vdec_poweroff() is finally called later in the function?
[Severity: High]
This is a pre-existing issue, but does the teardown sequence in vdec_stop_streaming() leave a race window for a use-after-free?
The synchronization happens before the hardware is halted:
drivers/staging/media/meson/vdec/vdec.c:vdec_stop_streaming() {
...
/* Synchronize and flush pending hardware interrupt service routines */
synchronize_irq(core->vdec_irq);
/* Ensure esparser ISR finishes executing */
synchronize_irq(core->esparser_irq);
cancel_work_sync(&sess->esparser_queue_work);
mutex_lock(&core->lock);
if (core->cur_sess == sess) {
vdec_poweroff(sess);
...
kfree(sess->priv);
...
}If a new hardware interrupt fires immediately after the synchronization completes, and vdec_stop_streaming() proceeds to free sess->priv, could the concurrent ISR access the freed session data?
drivers/staging/media/meson/vdec/vdec.c:vdec_isr() {
...
/* Secure an atomic acquire snapshot to protect against concurrent teardown */
sess = smp_load_acquire(&core->cur_sess);
if (!sess)
return IRQ_HANDLED;
sess->last_irq_jiffies = get_jiffies_64();
return sess->fmt_out->codec_ops->isr(sess);
...
}Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
PATCH v7 12/19 media: meson: vdec: Coordinate m2m task execution inside async loop
In reply to Anand MoonIntroduce proper V4L2 Memory-to-Memory (m2m) scheduler pipeline tracking by handling job finalisation inside the asynchronous esparser workqueue.
Because the meson video decoder offloads raw hardware register processing and bitstream feeding to an internal workqueue engine, calling the helper v4l2_m2m_job_finish() prematurely within the primary device execution trigger context (vdec_m2m_device_run) drops the active task transaction state too early. This timing gap creates a state mismatch in user-space multimedia layers like GStreamer, resulting in a fatal "poll error 1" event abort that breaks streaming setup sequences during pipeline preroll.
Resolve this architectural loop collision by deferring the scheduling call to v4l2_m2m_job_finish() to execute exclusively within the worker routine esparser_queue_all_src() at the precise microsecond after an input buffer payload is fully validated, cleared, and returned via v4l2_m2m_buf_done(). Additionally, protect hardware session context mappings with core mutex locks and implement volatile early exit gates within vdec_m2m_device_run() to ensure stable teardowns.
--- drivers/staging/media/meson/vdec/esparser.c | 4 ++++ drivers/staging/media/meson/vdec/vdec.c | 11 +++++++++++ 2 files changed, 15 insertions(+)
drivers/staging/media/meson/vdec/esparser.c +4 -0
@@ -399,6 +399,7 @@ void esparser_queue_all_src(struct work_struct *work)struct amvdec_session *sess = container_of(work, struct amvdec_session, esparser_queue_work);struct device *dev = sess->core->dev_dec; +struct amvdec_core *core = sess->core;int ret;while (1 ) {
@@ -437,6 +438,9 @@ void esparser_queue_all_src(struct work_struct *work)else v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_DONE ); +/* Safely notify the V4L2 core sub-framework */ +v4l2_m2m_job_finish (core->m2m_dev, sess->m2m_ctx); +/* Set tracking flag indicating transaction completion */ processed_frame = true; }
drivers/staging/media/meson/vdec/vdec.c +11 -0
@@ -149,6 +149,17 @@ vdec_queue_recycle(struct amvdec_session *sess, struct vb2_buffer *vb)static void vdec_m2m_device_run (void *priv) {struct amvdec_session *sess = priv; +struct amvdec_core *core = sess->core; + +if (READ_ONCE (sess->should_stop)) { +v4l2_m2m_job_finish (core->m2m_dev, sess->m2m_ctx); +return ; + } + +mutex_lock (&core->lock); +if (!core->cur_sess) + core->cur_sess = sess; +mutex_unlock (&core->lock);schedule_work (&sess->esparser_queue_work); }
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 12/19] media: meson: vdec: Coordinate m2m task execution inside async loop
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
New issues:
- [Critical] An AB-BA deadlock occurs between the V4L2 VIDIOC_STREAMOFF ioctl and the workqueue worker esparser_queue_all_src over sess->lock.
- [Critical] A recursive mutex self-deadlock occurs because core->lock is manually acquired inside functions already protected by the V4L2 core.
- [High] The esparser_queue_all_src workqueue thread enters a 100% CPU infinite busy-loop on empty queues, end-of-stream buffers, or hardware FIFO full conditions.
- [High] The V4L2 M2M framework permanently stalls due to a leaked job context when esparser_queue_all_src exits early or hits loop break conditions without calling v4l2_m2m_job_finish().
- [High] Closing any secondary file descriptor unconditionally clears core->cur_sess, stripping hardware ownership from the active session and causing permanent DMA memory leaks.
Pre-existing issues:
- [Critical] An unconditional out-of-bounds heap write occurs when padding artificially small, user-controlled buffers in esparser_pad_start_code().
- [Critical] An integer overflow during VP9 sub-frame length parsing allows attackers to bypass size boundaries and trigger an out-of-bounds memmove copy. --
--- Patch [12]: [PATCH v7 12/19] media: meson: vdec: Coordinate m2m task execution inside async loop --- commit 8f8ffb4492573d6d82ee209ecb382a9f5fbed9e4
Author: Anand Moon <[email protected]>
media: meson: vdec: Coordinate m2m task execution inside async loop
This commit introduces proper V4L2 Memory-to-Memory (m2m) scheduler pipeline tracking by handling job finalisation inside the asynchronous esparser workqueue. It defers the scheduling call to v4l2_m2m_job_finish() to execute within the worker routine.
Quoted 4 lines
drivers/staging/media/meson/vdec/esparser.c +0
-0
[ ... ]
drivers/staging/media/meson/vdec/esparser.c void esparser_queue_all_src(struct work_struct *work) · line 446
Quoted 10 lines
drivers/staging/media/meson/vdec/esparser.c +3
-0@@ -437,6 +438,9 @@ void esparser_queue_all_src(struct work_struct *work)else v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_DONE ); +/* Safely notify the V4L2 core sub-framework */ +v4l2_m2m_job_finish (core->m2m_dev, sess->m2m_ctx); +/* Set tracking flag indicating transaction completion */ processed_frame = true; }
[Severity: High]
Does this worker thread run into an infinite busy-loop on empty queues or end-of-stream buffers?
In esparser_queue_all_src(), the code uses a scoped_guard for the lock:
while (1) {
...
scoped_guard(mutex, &sess->lock) {
if (!vbuf)
break;
}
}Since scoped_guard expands into a hidden for-loop, the break statement only exits the scoped_guard's loop, not the outer while (1) loop. This could trap execution indefinitely, consuming 100% CPU when the queue is empty, hits an EOS buffer, or the hardware FIFO is full.
[Severity: High]
Could the V4L2 M2M job context permanently leak if the session shuts down?
Looking at esparser_queue_all_src():
scoped_guard(mutex, &sess->lock) {
/* Safe atomic tracking check: exit loop if session is shutting down */
if (READ_ONCE(sess->should_stop))
return;Returning early directly bypasses the newly added v4l2_m2m_job_finish() call. This could permanently hang the V4L2 memory-to-memory framework because it considers the device busy forever, preventing further decoding jobs.
[Severity: Critical]
Is there a risk of an AB-BA deadlock between the V4L2 VIDIOC_STREAMOFF ioctl and the workqueue worker?
The V4L2 core framework acquires sess->lock during VIDIOC_STREAMOFF and then calls vdec_stop_streaming(), which waits for the active worker thread:
drivers/staging/media/meson/vdec/vdec.c:vdec_stop_streaming() {
...
cancel_work_sync(&sess->esparser_queue_work);
}Concurrently, the worker thread esparser_queue_all_src spins up and blocks attempting to acquire the identical sess->lock:
drivers/staging/media/meson/vdec/esparser.c:esparser_queue_all_src() {
...
scoped_guard(mutex, &sess->lock) {
}This sequence would entirely deadlock the system when stopping the video stream.
Quoted 12 lines
drivers/staging/media/meson/vdec/vdec.c +4
-0@@ -149,6 +149,17 @@ vdec_queue_recycle(struct amvdec_session *sess, struct vb2_buffer *vb)static void vdec_m2m_device_run (void *priv) {struct amvdec_session *sess = priv; +struct amvdec_core *core = sess->core; + +if (READ_ONCE (sess->should_stop)) { +v4l2_m2m_job_finish (core->m2m_dev, sess->m2m_ctx);
drivers/staging/media/meson/vdec/vdec.c vdec_queue_recycle(struct amvdec_session *sess, struct vb2_buffer *vb) · line 165
Quoted 10 lines
drivers/staging/media/meson/vdec/vdec.c +7
-0+return ; + } + +mutex_lock (&core->lock); +if (!core->cur_sess) + core->cur_sess = sess; +mutex_unlock (&core->lock);schedule_work (&sess->esparser_queue_work); }
[Severity: Critical]
Will acquiring core->lock manually here and in vdec_start_streaming result in a recursive mutex self-deadlock?
Because vdev->queue is intentionally left NULL for M2M devices, the V4L2 core ioctl wrapper unconditionally acquires vdev->lock (which is mapped to &core->lock) before delegating execution to the ioctl implementations.
The underlying driver callback vdec_start_streaming then attempts to manually acquire it again:
drivers/staging/media/meson/vdec/vdec.c:vdec_start_streaming() {
...
mutex_lock(&core->lock);
if (core->cur_sess && core->cur_sess != sess) {
}This would immediately self-deadlock the kernel thread processing VIDIOC_STREAMON or VIDIOC_STREAMOFF.
[Severity: High]
Does closing a secondary file descriptor unintentionally strip hardware ownership from the active session?
In vdec_close(), the state modification is placed outside the active session check block:
drivers/staging/media/meson/vdec/vdec.c:vdec_close() {
...
/* Safely clear hardware ownership since we were confirmed as the owner */
smp_store_release(&core->cur_sess, NULL);
}If a user-space process opens the video node twice, streams on the first session, and then closes the second session, this clears core->cur_sess unconditionally. This destroys the ownership state of the active session, bypassing its cleanup sequence and permanently leaking DMA buffers and canvases.
[Severity: Critical]
This is a pre-existing issue, but can esparser_pad_start_code() cause an out-of-bounds heap write when handling artificially small buffers?
If userspace negotiates a small sizeimage and queues a tiny buffer, the padding function computes pad_size based on ESPARSER_MIN_PACKET_SIZE and executes a memset before verifying the vb2_plane_size:
drivers/staging/media/meson/vdec/esparser.c:esparser_pad_start_code() {
...
pad_size = ESPARSER_MIN_PACKET_SIZE - payload_size;
memset(vaddr + payload_size, 0, pad_size);
}
if ((payload_size + pad_size + SEARCH_PATTERN_LEN) >
vb2_plane_size(vb, 0)) {
}This could allow a local user to overwrite memory with zeroes by passing a payload_size of 0, resulting in a 4096-byte memset.
[Severity: Critical]
This is a pre-existing issue, but might an integer overflow in vp9_update_header() allow attackers to trigger an out-of-bounds memmove?
The parser reconstructs frame lengths by bit-shifting byte payloads:
drivers/staging/media/meson/vdec/esparser.c:vp9_update_header() {
...
frame_size[cur_frame] |=
(dp[mag_ptr] << (cur_mag * 8));
}If dp[mag_ptr] is >= 128 and cur_mag is 3, the unsigned 8-bit value promotes to an int and shifts into the sign bit, resulting in a large negative integer.
Later, this negative frame_size is passed to memmove():
drivers/staging/media/meson/vdec/esparser.c:vp9_update_header() {
...
memmove(fdata + VP9_HEADER_SIZE, old_framedata, framesize);
}The negative framesize implicitly converts to a massive size_t, which could corrupt arbitrary kernel memory.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
PATCH v7 13/19 media: meson: vdec: Fix race conditions in job abort sequence
In reply to Anand MoonImplement proper cleanup inside vdec_m2m_job_abort to safely stop the hardware job sequence during a streaming abort or teardown.
Without this, if a job is aborted right after being triggered, the deferred work item (esparser_queue_work) scheduled by device_run could continue running concurrently. This leads to unexpected behavior and potential use-after-free bugs if session structures are cleared.
Fix this by flagging the session to stop via WRITE_ONCE, synchronously canceling any pending parser work, and safely clearing the active core session pointer under the core lock protection.
--- drivers/staging/media/meson/vdec/vdec.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
drivers/staging/media/meson/vdec/vdec.c +11 -0
@@ -167,6 +167,17 @@ static void vdec_m2m_device_run(void *priv)static void vdec_m2m_job_abort (void *priv) {struct amvdec_session *sess = priv; +struct amvdec_core *core = sess->core; + +WRITE_ONCE (sess->should_stop,1 ); + +cancel_work_sync (&sess->esparser_queue_work); + +mutex_lock (&core->lock); +if (core->cur_sess == sess) +/* Safely clear hardware ownership since we were confirmed as the owner */ +smp_store_release (&core->cur_sess,NULL ); +mutex_unlock (&core->lock);v4l2_m2m_job_finish (sess->core->m2m_dev, sess->m2m_ctx); }
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 13/19] media: meson: vdec: Fix race conditions in job abort sequence
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: - [Critical] Clearing the active session pointer while the hardware is still running causes unacknowledged…
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] Clearing the active session pointer while the hardware is still running causes unacknowledged interrupts and a CPU-locking interrupt storm.
- [High] Premature clearing of the active session pointer in job_abort bypasses hardware teardown and resource cleanup, causing severe memory and resource leaks.
- [High] AB-BA lock inversion deadlock between VIDIOC_STREAMOFF and the esparser_queue_work worker thread.
- [High] Releasing session ownership without stopping the hardware allows concurrent DMA overwrites and hardware state corruption.
- [High] Infinite loop inside esparser_queue_all_src() causing 100% CPU lockup when the queue is empty.
--
--- Patch [13]: [PATCH v7 13/19] media: meson: vdec: Fix race conditions in job abort sequence --- commit bc32f101c548ab648eb44ae46f9cc2ea72d4c339
Author: Anand Moon <[email protected]>
media: meson: vdec: Fix race conditions in job abort sequence
Implement proper cleanup inside vdec_m2m_job_abort to safely stop the hardware job sequence during a streaming abort or teardown.
drivers/staging/media/meson/vdec/vdec.c static void vdec_m2m_device_run(void *priv) · line 174
Quoted 13 lines
drivers/staging/media/meson/vdec/vdec.c +5
-0@@ -167,6 +167,17 @@ static void vdec_m2m_device_run(void *priv)static void vdec_m2m_job_abort (void *priv) {struct amvdec_session *sess = priv; +struct amvdec_core *core = sess->core; + +WRITE_ONCE (sess->should_stop,1 ); + +cancel_work_sync (&sess->esparser_queue_work);
[Severity: High]
Does this introduce an AB-BA deadlock with VIDIOC_STREAMOFF?
The V4L2 core acquires the queue lock (sess->lock) during VIDIOC_STREAMOFF and calls vdec_m2m_job_abort(), which blocks here indefinitely waiting for the worker.
Meanwhile, the worker thread unconditionally attempts to acquire sess->lock in esparser_queue_all_src():
drivers/staging/media/meson/vdec/esparser.c:esparser_queue_all_src() {
...
scoped_guard(mutex, &sess->lock) {
...
}drivers/staging/media/meson/vdec/vdec.c static void vdec_m2m_device_run(void *priv) · line 179
Quoted 5 lines
drivers/staging/media/meson/vdec/vdec.c +5
-0+ +mutex_lock (&core->lock); +if (core->cur_sess == sess) +/* Safely clear hardware ownership since we were confirmed as the owner */ +smp_store_release (&core->cur_sess,NULL );
[Severity: High]
Does clearing cur_sess here prematurely bypass hardware teardown?
During a normal VIDIOC_STREAMOFF, vdec_m2m_job_abort() is called before the stop_streaming hook. If cur_sess is cleared here, the later teardown in vdec_stop_streaming() will skip powering off the hardware and freeing the DMA buffer (vififo_size) because core->cur_sess == sess evaluates to false:
drivers/staging/media/meson/vdec/vdec.c:vdec_stop_streaming() {
...
if (core->cur_sess == sess) {
vdec_poweroff(sess);
vdec_free_canvas(sess);
...
}[Severity: Critical]
Can this cause an interrupt storm and CPU lockup?
Since the physical hardware isn't stopped, it may complete its active task and assert a level-triggered interrupt.
The ISR checks cur_sess, reads NULL, and immediately returns IRQ_HANDLED without acknowledging or clearing the hardware register:
drivers/staging/media/meson/vdec/vdec.c:vdec_isr() {
...
if (!sess)
return IRQ_HANDLED;
...
}Will this cause the unacknowledged interrupt to immediately fire again, locking up the CPU?
[Severity: High]
Could this allow concurrent DMA overwrites and hardware state corruption?
By clearing core->cur_sess here, the V4L2 framework considers the hardware idle and can schedule a new session.
Since the hardware is actually still running, a newly scheduled session might blindly overwrite the active hardware's registers in esparser_queue(), corrupting the ongoing DMA operations:
drivers/staging/media/meson/vdec/esparser.c:esparser_queue() {
...
amvdec_write_parser(core, PARSER_VIDEO_START_PTR, payload_paddr);
...
}drivers/staging/media/meson/vdec/vdec.c static void vdec_m2m_device_run(void *priv) · line 183
Quoted 4 lines
drivers/staging/media/meson/vdec/vdec.c +1
-0+mutex_unlock (&core->lock);v4l2_m2m_job_finish (sess->core->m2m_dev, sess->m2m_ctx); }
[Severity: High]
Does the esparser_queue_all_src() worker have an infinite loop?
When looking into the interaction with esparser_queue_work, the outer loop in esparser_queue_all_src() appears to never terminate when the queue is empty:
drivers/staging/media/meson/vdec/esparser.c:esparser_queue_all_src() {
while (1) {
struct vb2_v4l2_buffer *vbuf = NULL;
bool processed_frame = false;
...
scoped_guard(mutex, &sess->lock) {
...
vbuf = v4l2_m2m_next_src_buf(sess->m2m_ctx);
if (!vbuf)
break;
...
}
...
}
}Since scoped_guard is implemented as a hidden for-loop, the break statement only exits the scoped_guard's internal loop, not the outer while(1) loop.
Because processed_frame remains false, the code calls cond_resched() and spins indefinitely. Could this cause a 100% CPU lockup?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
PATCH v7 14/19 media: meson: vdec: Correct atomic counter placement in dst_buf_done
In reply to Anand MoonFix an end-of-stream (EOS) boundary condition logic flaw in dst_buf_done() by relocating the atomic decrement helper to execute after state evaluation.
Previously, decrementing sess->esparser_queued_bufs at the entry point of the function corrupted the conditional checks for the final video frame. This premature decrement tricked the driver into flagging the second-to-last buffer as the final frame (V4L2_BUF_FLAG_LAST) during teardown sequences, which cut off the true final frame and broke compliance validation.
Resolve this by moving atomic_dec() to execute immediately after the EOS conditional tracking block finishes evaluating, right before invoking v4l2_m2m_buf_done(). This ensures accurate stream termination signaling.
--- drivers/staging/media/meson/vdec/vdec_helpers.c | 3 +++ 1 file changed, 3 insertions(+)
drivers/staging/media/meson/vdec/vdec_helpers.c +3 -0
@@ -314,6 +314,9 @@ static void dst_buf_done(struct amvdec_session *sess,dev_dbg (dev,"Buffer %u done, ts = %llu, flags = %08X\n" , vbuf->vb2_buf.index, timestamp, flags); vbuf->field = field; + +atomic_dec (&sess->esparser_queued_bufs); +v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_DONE );/* Buffer done probably means the vififo got freed */
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 14/19] media: meson: vdec: Correct atomic counter placement in dst_buf_done
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
New issues:
- [Critical] The patch introduces an unhandled double decrement of sess->esparser_queued_bufs, causing the atomic counter to underflow, corrupting stream state evaluation, and deadlocking the driver.
- [High] Using break inside scoped_guard fails to exit the worker loop, resulting in a 100% CPU infinite loop deadlock.
- [High] Returning 0 on !vaddr in esparser_pad_start_code() skips writing the mandatory start code pattern, leading to hardware out-of-bounds reads.
- [Medium] The commit message erroneously claims to relocate a decrement from the 'entry point' of the function, presenting a false premise for the change.
Pre-existing issues:
- [High] Missing decrement of esparser_queued_bufs on error paths permanently leaks logical buffer slots. --
--- Patch [14]: [PATCH v7 14/19] media: meson: vdec: Correct atomic counter placement in dst_buf_done --- commit 3bacd92d37472439ff6f7550b68eb503f9d760f0
Author: Anand Moon <[email protected]>
media: meson: vdec: Correct atomic counter placement in dst_buf_done
This commit fixes an end-of-stream boundary condition by relocating the atomic decrement helper to execute after state evaluation. It aims to ensure accurate stream termination signaling.
Quoted 3 lines
Previously, decrementing
sess->esparser_queued_bufsat the entry point of the function corrupted the conditional checks for the final video frame.
[Severity: Medium]
Is this description accurate? Looking at the baseline code, dst_buf_done() never contained an atomic decrement at its entry point or anywhere else inside the function. The decrement only existed in the caller functions.
drivers/staging/media/meson/vdec/vdec_helpers.c static void dst_buf_done(struct amvdec_session *sess, · line 319
Quoted 11 lines
drivers/staging/media/meson/vdec/vdec_helpers.c +3
-0@@ -314,6 +314,9 @@ static void dst_buf_done(struct amvdec_session *sess,dev_dbg (dev,"Buffer %u done, ts = %llu, flags = %08X\n" , vbuf->vb2_buf.index, timestamp, flags); vbuf->field = field; + +atomic_dec (&sess->esparser_queued_bufs); +
[Severity: Critical]
Does this introduce a double decrement? Since the callers like amvdec_dst_buf_done() and amvdec_dst_buf_done_offset() still contain their original atomic_dec() calls, this new decrement causes the counter to underflow.
When the counter becomes negative, the EOS condition atomic_read(&sess->esparser_queued_bufs) <= 1 evaluates to true, which can falsely trigger V4L2_EVENT_EOS on the wrong buffer and cause a permanent -EAGAIN deadlock.
drivers/staging/media/meson/vdec/vdec_helpers.c static void dst_buf_done(struct amvdec_session *sess, · line 322
Quoted 3 lines
drivers/staging/media/meson/vdec/vdec_helpers.c +0
-0v4l2_m2m_buf_done (vbuf,VB2_BUF_STATE_DONE );/* Buffer done probably means the vififo got freed */
[Severity: High]
This is a pre-existing issue, but does the error path in amvdec_dst_buf_done() permanently leak logical buffer slots?
drivers/staging/media/meson/vdec/vdec_helpers.c:amvdec_dst_buf_done() {
...
if (list_empty(&sess->timestamps)) {
v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
spin_unlock_irqrestore(&sess->ts_spinlock, flags);
return;
}
...
}When list_empty() is true, the function returns early without decrementing sess->esparser_queued_bufs. If this triggers enough times, the counter artificially hits the max limit, preventing new buffers from being queued.
[Severity: High]
Does the break statement in esparser_queue_all_src() inadvertently cause an infinite loop?
drivers/staging/media/meson/vdec/esparser.c:esparser_queue_all_src() {
while (1) {
scoped_guard(mutex, &sess->lock) {
...
if (!vbuf)
break;
...
}
...
}
}Because scoped_guard() is implemented using an internal for-loop, calling break here only exits the scoped_guard's internal scope, not the outer while (1) loop. This causes the worker thread to permanently hang in an infinite busy-wait loop.
[Severity: High]
Can the failure to propagate an error in esparser_pad_start_code() cause hardware out-of-bounds reads?
drivers/staging/media/meson/vdec/esparser.c:esparser_pad_start_code() {
...
if (!sess || READ_ONCE(sess->should_stop) || !sess->priv || !vaddr)
return 0;
...
}If a client provides an unmapped buffer, !vaddr is true and this returns 0, skipping the mandatory 512-byte start code padding.
Its caller esparser_queue() ignores this and passes the physical address to esparser_write_data(), which unconditionally adds 512 to the payload size. Since the padding wasn't written, the hardware DMA engine reads past the valid buffer boundary.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
PATCH v7 15/19 media: meson: vdec: Fix concurrent firmware loading race and hardware timeout
In reply to Anand MoonThe vdec_1 firmware loader used static variables for DMA buffer addresses (mc_addr, mc_addr_map). This made allocations global across sessions, corrupting state when multiple decoders loaded firmware concurrently. Under stress this triggered KASAN reports, lost references, and memory leaks. Making mc_addr and mc_addr_map local to each load call, eliminating cross‑session races.
The firmware DMA completion check also relied on a raw decrement loop of 1000 iterations. On modern CPUs this loop completed far too quickly, often before hardware signaled ready, producing false "DMA hang" errors. Replacing the busy‑wait loop with readl_poll_timeout_atomic(), providing a bounded, microsecond scale poll with proper timeout semantics.
--- drivers/staging/media/meson/vdec/vdec_1.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-)
drivers/staging/media/meson/vdec/vdec_1.c +8 -8
@@ -29,14 +29,14 @@ vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname)struct amvdec_core *core = sess->core;struct device *dev = core->dev_dec;struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; -static void *mc_addr; -static dma_addr_t mc_addr_map; +void *mc_addr; +dma_addr_t mc_addr_map;int ret; -u32 i =1000 ; +u32 val; ret =request_firmware (&fw, fwname, dev);if (ret <0 ) -return -EINVAL ; +return ret;if (fw->size <MC_SIZE ) { dev_err(dev, "Firmware size %zu is too small. Expected %u.\n",
@@ -63,11 +63,11 @@ vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname)amvdec_write_dos (core,IMEM_DMA_COUNT ,MC_SIZE /4 );amvdec_write_dos (core,IMEM_DMA_CTRL , (0x8000 | (7 <<16 ))); -while (--i &&amvdec_read_dos (core,IMEM_DMA_CTRL ) &0x8000 ); - -if (i ==0 ) { + ret =readl_poll_timeout_atomic (core->dos_base +IMEM_DMA_CTRL , val, + !(val &0x8000 ),10 ,10000 ); +if (ret) {dev_err (dev,"Firmware load fail (DMA hang?)\n" ); - ret = -EINVAL ; + ret = -ETIMEDOUT ; goto free_mc; }
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 15/19] media: meson: vdec: Fix concurrent firmware loading race and hardware timeout
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [Low] Suboptimal use of atomic polling in a sleepable context.
Pre-existing issues:
- [High] Missing memory barrier between firmware memcpy and DMA start.
- [High] Incomplete fix: identical bugs (static variables and raw decrement loop) remain in HEVC firmware loader.
- [High] Potential DMA Use-After-Free if firmware DMA transfer times out.
--
--- Patch [15]: [PATCH v7 15/19] media: meson: vdec: Fix concurrent firmware loading race and hardware timeout --- commit 7acf244d598b1d1a15fbe219b6ceaead31b2210b
Author: Anand Moon <[email protected]>
media: meson: vdec: Fix concurrent firmware loading race and hardware timeout
This commit fixes a concurrent firmware loading race condition by replacing static variables with local ones in vdec_1_load_firmware(). It also addresses hardware DMA hang timeouts by replacing a fast decrement loop with readl_poll_timeout_atomic().
Quoted 3 lines
drivers/staging/media/meson/vdec/vdec_1.c +0
-0
[ ... ]
drivers/staging/media/meson/vdec/vdec_1.c vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname) · line 64
Quoted 3 lines
drivers/staging/media/meson/vdec/vdec_1.c +0
-0@@ -63,11 +63,11 @@ vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname)amvdec_write_dos (core,IMEM_DMA_COUNT ,MC_SIZE /4 );amvdec_write_dos (core,IMEM_DMA_CTRL , (0x8000 | (7 <<16 )));
[Severity: High]
This is a pre-existing issue, but is there a missing memory barrier between the firmware memcpy and the DMA start?
Earlier in vdec_1_load_firmware(), the firmware is copied to a DMA-coherent buffer:
memcpy(mc_addr, fw->data, MC_SIZE);
Since amvdec_write_dos() maps to writel_relaxed(), there is no memory-to-I/O ordering guarantee. Could the CPU issue the MMIO write to start the DMA before the firmware data is flushed to main memory, causing the hardware to read stale data?
drivers/staging/media/meson/vdec/vdec_1.c vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname) · line 67
Quoted 5 lines
drivers/staging/media/meson/vdec/vdec_1.c +2
-3-while (--i &&amvdec_read_dos (core,IMEM_DMA_CTRL ) &0x8000 ); - -if (i ==0 ) { + ret =readl_poll_timeout_atomic (core->dos_base +IMEM_DMA_CTRL , val, + !(val &0x8000 ),10 ,10000 );
[Severity: Low]
Does this need to use the atomic polling variant?
Since vdec_1_load_firmware() runs in a sleepable context (it successfully calls request_firmware() and allocates memory with GFP_KERNEL just prior), could this use the non-atomic readl_poll_timeout() macro instead?
Using the atomic variant forces the CPU to busy-wait in a udelay() loop for up to 10ms, which might be inefficient.
drivers/staging/media/meson/vdec/vdec_1.c vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname) · line 71
Quoted 5 lines
drivers/staging/media/meson/vdec/vdec_1.c +2
-1+if (ret) {dev_err (dev,"Firmware load fail (DMA hang?)\n" ); - ret = -EINVAL ; + ret = -ETIMEDOUT ; goto free_mc;
[Severity: High]
This is a pre-existing issue, but does this create a DMA use-after-free if the firmware DMA transfer times out?
When a timeout occurs, the code jumps to free_mc:
free_mc:
dma_free_coherent(core->dev, MC_SIZE, mc_addr, mc_addr_map);
If the hardware is merely slow and still executing the DMA transfer, will it continue to read from these freed memory pages? Should the DMA controller be explicitly halted (for example, by clearing the IMEM_DMA_CTRL register) before releasing the buffer?
drivers/staging/media/meson/vdec/vdec_1.c vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname) · line 71
Quoted 1 line
drivers/staging/media/meson/vdec/vdec_1.c +0
-0}
[Severity: High]
This is a pre-existing issue, but does the same problem still exist in the HEVC firmware loader?
Looking at vdec_hevc_load_firmware() in vdec_hevc.c, it uses the exact same static variables and fast decrement loop that this patch fixes in vdec_1.c:
static int vdec_hevc_load_firmware(...)
{
...
static void *mc_addr;
static dma_addr_t mc_addr_map;
...
while (i && (readl(core->dos_base + HEVC_IMEM_DMA_CTRL) & 0x8000))
i--;
...
}Will HEVC decoding also fail consistently on modern CPUs due to false DMA timeouts? And could multiple HEVC decoding sessions corrupt state by sharing these static variables?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
PATCH v7 16/19 media: meson: vdec: Configure DMA mask and segment size in probe
In reply to Anand MoonThe vdec probe routine does not set explicit DMA constraints, leaving the driver completely dependent on platform bus default values. This lack of explicit layout initialization can lead to allocation failures, restricted address space mappings, or broken contiguous buffer handling on architectures with restrictive DMA layers.
Address these platform constraints with the following changes during driver initialization:
1. Enforce a 32-bit coherent DMA allocation window by invoking dma_set_mask_and_coherent() with a DMA_BIT_MASK(32) argument.
2. Maximize the contiguous allocation segment boundary constraint to UINT_MAX using the vb2_dma_contig_set_max_seg_size() configuration helper.
This guarantees that large, contiguous video frame allocation requests work reliably and explicitly aligns the memory management paths with standard Linux kernel DMA management paradigms.
--- drivers/staging/media/meson/vdec/vdec.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
drivers/staging/media/meson/vdec/vdec.c +10 -0
@@ -1186,6 +1186,16 @@ static int vdec_probe(struct platform_device *pdev)if (IS_ERR (core->canvas))return PTR_ERR (core->canvas); +/* Enforce strict 32-bit DMA limit to match hardware capabilities */ + ret =dma_set_mask_and_coherent (dev,DMA_BIT_MASK (32 )); +if (ret) +return dev_err_probe (dev, ret,"Failed to set 32-bit DMA mask\n" ); + + ret =vb2_dma_contig_set_max_seg_size (dev,UINT_MAX ); +if (ret) +return dev_err_probe (dev, ret, +"Failed to set DMA max segment size\n" ); + of_id =of_match_node (vdec_dt_match, dev->of_node); core->platform = of_id->data;
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 16/19] media: meson: vdec: Configure DMA mask and segment size in probe
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
New issues:
- [High] Hardware interrupts are not masked prior to calling synchronize_irq(), leading to a race condition that causes a Use-After-Free of sess->priv.
- [High] Misuse of scoped_guard inside a while (1) loop combined with inverted logic causes the workqueue to busy-wait infinitely when no data is available.
- [Medium] Missing release barriers when publishing core->cur_sess allows the lockless ISR to observe an uninitialized or stale session state.
Pre-existing issues:
- [Critical] vp9_update_header trusts user-provided frame sizes to calculate offsets, leading to integer overflows and massive out-of-bounds memory accesses via memmove.
- [High] meson_canvas_get retrieves a pointer to the provider's private data but immediately drops its reference to the provider device, leading to a Use-After-Free if the provider driver is unbound.
- [Medium] Potential NULL pointer dereference if the driver is probed without a device tree node.
--
--- Patch [16]: [PATCH v7 16/19] media: meson: vdec: Configure DMA mask and segment size in probe --- commit 8dd369d994b7e2a6a1fb0db5dcba10a32f46beaa
Author: Anand Moon <[email protected]>
media: meson: vdec: Configure DMA mask and segment size in probe
This commit addresses missing explicit DMA constraints in the vdec probe routine. It enforces a 32-bit coherent DMA allocation window and maximizes the contiguous allocation segment boundary constraint to UINT_MAX.
Quoted 9 lines
drivers/staging/media/meson/vdec/vdec.c +2
-0@@ -1186,6 +1186,16 @@ static int vdec_probe(struct platform_device *pdev)if (IS_ERR (core->canvas))return PTR_ERR (core->canvas); +/* Enforce strict 32-bit DMA limit to match hardware capabilities */ + ret =dma_set_mask_and_coherent (dev,DMA_BIT_MASK (32 ));
drivers/staging/media/meson/vdec/vdec.c static int vdec_probe(struct platform_device *pdev) · line 1200
Quoted 10 lines
drivers/staging/media/meson/vdec/vdec.c +8
-0+if (ret) +return dev_err_probe (dev, ret,"Failed to set 32-bit DMA mask\n" ); + + ret =vb2_dma_contig_set_max_seg_size (dev,UINT_MAX ); +if (ret) +return dev_err_probe (dev, ret, +"Failed to set DMA max segment size\n" ); + of_id =of_match_node (vdec_dt_match, dev->of_node); core->platform = of_id->data;
[Severity: Medium]
This isn't a bug introduced by this patch, but can this lead to a NULL pointer dereference if the driver is probed without a device tree node? If a user with privileges binds the driver manually via sysfs without an associated device tree node, dev->of_node is NULL. The of_match_node() call will return NULL, causing of_id->data to be dereferenced blindly.
[Severity: High]
Can this regression cause a use-after-free of sess->priv in the interrupt handler?
drivers/staging/media/meson/vdec/vdec.c:vdec_stop_streaming() {
...
/* Synchronize and flush pending hardware interrupt service routines */
synchronize_irq(core->vdec_irq);
/* Ensure esparser ISR finishes executing */
synchronize_irq(core->esparser_irq);
cancel_work_sync(&sess->esparser_queue_work);
mutex_lock(&core->lock);
if (core->cur_sess == sess) {
vdec_poweroff(sess);...
}
It appears hardware interrupts are not masked prior to calling synchronize_irq(), which could allow an interrupt to fire immediately after synchronize_irq() returns. Because vdec_poweroff() is responsible for masking the hardware interrupts, could the main thread then free sess->priv while the concurrently executing ISR dereferences it?
[Severity: High]
Does this loop correctly process the buffer queue or does it spin infinitely?
drivers/staging/media/meson/vdec/esparser.c:esparser_queue_all_src() {
...
while (1) {
struct vb2_v4l2_buffer *vbuf = NULL;
bool processed_frame = false;
scoped_guard(mutex, &sess->lock) {
/* Safe atomic tracking check: exit loop if session is shutting down */
if (READ_ONCE(sess->should_stop))
return;
/* Queue completely empty: exit work loop cleanly */
vbuf = v4l2_m2m_next_src_buf(sess->m2m_ctx);
if (!vbuf)
break;...
}
When v4l2_m2m_next_src_buf() returns NULL, the break statement seems to only exit the inner for loop created by the scoped_guard macro. This bypasses the subsequent loop exit conditions and repeats the while (1) loop endlessly, potentially causing a CPU lockup on the workqueue thread. Additionally, if a buffer is present, could this break out of the loop completely instead of processing the queue?
[Severity: Medium]
Can the lockless ISR observe an uninitialized or stale session state due to missing release barriers?
drivers/staging/media/meson/vdec/vdec.c:vdec_start_streaming() {
...
/* 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);...
}
Because core->cur_sess is assigned using a plain store, could the compiler or CPU reorder this store before earlier initialization steps like sess->status = STATUS_INIT? If the lockless ISR uses smp_load_acquire(), it might observe the new session pointer but the old memory state.
[Severity: High]
This is a pre-existing issue, but does this code cause a use-after-free if the provider driver is unbound?
drivers/soc/amlogic/meson-canvas.c:meson_canvas_get() {
...
canvas_pdev = of_find_device_by_node(canvas_node); of_node_put(canvas_node); if (!canvas_pdev) return ERR_PTR(-EPROBE_DEFER); ... canvas = dev_get_drvdata(&canvas_pdev->dev); put_device(&canvas_pdev->dev); if (!canvas) return ERR_PTR(-EINVAL); return canvas; }
The put_device() call immediately drops the device reference before returning the pointer. Since no device link is maintained, could an admin or unprivileged user with sysfs access unbind the amlogic-canvas driver, freeing the memory while the vdec driver still holds and uses the dangling pointer?
[Severity: Critical]
This is a pre-existing issue, but does this size calculation prevent integer overflows and out-of-bounds memory accesses?
drivers/staging/media/meson/vdec/esparser.c:vp9_update_header() {
...
for (cur_frame = 0; cur_frame < num_frames; cur_frame++) {
frame_size[cur_frame] = 0;
for (cur_mag = 0; cur_mag < mag; cur_mag++) {
if (mag_ptr >= dsize)
return -EINVAL;
frame_size[cur_frame] |=
(dp[mag_ptr] << (cur_mag * 8));
mag_ptr++;
}
if (cur_frame == 0)
tot_frame_size[cur_frame] =
frame_size[cur_frame];
else
tot_frame_size[cur_frame] =
tot_frame_size[cur_frame - 1] +
frame_size[cur_frame];
total_datasize += frame_size[cur_frame];
}...
}
It appears user-provided frame sizes are added blindly without overflow protection. If a user crafts a V4L2 VP9 buffer payload with two frames specifying sizes that sum to 0 (e.g., 0x40000000 and 0xC0000000), could this bypass the new_frame_size bounds check? The driver might then use one of the huge frame sizes in a memmove operation, leading to out-of-bounds reads and writes on the kernel heap.
Signature · 6 lines
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16 _______________________________________________ linux-amlogic mailing list [email protected] http://lists.infradead.org/mailman/listinfo/linux-amlogic
PATCH v7 17/19 media: meson: canvas: Fix Use-After-Free by linking canvas provider device
In reply to Anand MoonThe vdec driver retrieves a canvas instance using meson_canvas_get() and stores the raw pointer in core->canvas. However, the helper drops the provider device reference via put_device() right before returning. Because no formal device relationship is established, an unbind of the canvas provider driver triggers a Use-After-Free (UAF) bug when the video decoder subsequently attempts to access that memory block.
Fix this lifecycle hazard by enhancing meson_canvas_get() to establish a formal managed device link between the consumer device and the underlying canvas platform device. Using DL_FLAG_AUTOREMOVE_CONSUMER ensures the dependency link is automatically torn down when the consumer driver unbinds.
Additionally, handle missing link conditions using dev_err_probe() to clean up upstream error diagnostics, and ensure the canvas platform device reference count is dropped safely along the uninitialized driver data path.
--- drivers/soc/amlogic/meson-canvas.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-)
drivers/soc/amlogic/meson-canvas.c +12 -2
@@ -54,6 +54,7 @@ struct meson_canvas *meson_canvas_get(struct device *dev)struct device_node *canvas_node;struct platform_device *canvas_pdev;struct meson_canvas *canvas; +struct device_link *link; canvas_node =of_parse_phandle (dev->of_node,"amlogic,canvas" ,0 );if (!canvas_node)
@@ -70,9 +71,18 @@ struct meson_canvas *meson_canvas_get(struct device *dev)* current state, this driver probe cannot return -EPROBE_DEFER */ canvas =dev_get_drvdata (&canvas_pdev->dev); -put_device (&canvas_pdev->dev); -if (!canvas) +if (!canvas) { +put_device (&canvas_pdev->dev);return ERR_PTR (-EINVAL ); + } + +/* Establish device link to prevent Use-After-Free */ + link =device_link_add (dev, &canvas_pdev->dev, +DL_FLAG_AUTOREMOVE_CONSUMER ); +put_device (&canvas_pdev->dev); +if (!link) +return ERR_PTR (dev_err_probe (dev, -EINVAL , +"Failed to create device link canvas\n" ));return canvas; }
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 17/19] media: meson: canvas: Fix Use-After-Free by linking canvas provider device
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] A Time-of-Check to Time-of-Use (TOCTOU) race condition exists between retrieving the supplier's driver data and establishing the device link, which allows the supplier to unbind and free the data before the link can protect it.
Pre-existing issues:
- [High] The device link fails to prevent the Use-After-Free in the vdec driver because V4L2 file descriptors outlive the consumer's driver unbind sequence. --
--- Patch [17]: [PATCH v7 17/19] media: meson: canvas: Fix Use-After-Free by linking canvas provider device --- commit bf6a22d368b30413921052e4cb659c5a279efaea
Author: Anand Moon <[email protected]>
media: meson: canvas: Fix Use-After-Free by linking canvas provider device
This commit attempts to fix a Use-After-Free by establishing a device link between the vdec consumer and the canvas provider. The link aims to automatically unbind the consumer driver when the provider unbinds.
Quoted 4 lines
drivers/soc/amlogic/meson-canvas.c +0
-0
[ ... ]
drivers/soc/amlogic/meson-canvas.c struct meson_canvas *meson_canvas_get(struct device *dev) · line 73
Quoted 4 lines
drivers/soc/amlogic/meson-canvas.c +0
-0@@ -70,9 +71,18 @@ struct meson_canvas *meson_canvas_get(struct device *dev)* current state, this driver probe cannot return -EPROBE_DEFER */ canvas =dev_get_drvdata (&canvas_pdev->dev);
[Severity: High]
Is there a race condition between retrieving the driver data and creating the device link?
If the canvas provider unbinds immediately after dev_get_drvdata() but before device_link_add(), devres_release_all() will free the canvas data.
Because the driver core allows links to unbound suppliers, device_link_add() would still succeed and create a dormant link, and then meson_canvas_get() would return a freed pointer to the caller.
drivers/soc/amlogic/meson-canvas.c struct meson_canvas *meson_canvas_get(struct device *dev) · line 81
Quoted 10 lines
drivers/soc/amlogic/meson-canvas.c +7
-2-put_device (&canvas_pdev->dev); -if (!canvas) +if (!canvas) { +put_device (&canvas_pdev->dev);return ERR_PTR (-EINVAL ); + } + +/* Establish device link to prevent Use-After-Free */ + link =device_link_add (dev, &canvas_pdev->dev, +DL_FLAG_AUTOREMOVE_CONSUMER );
[Severity: High]
This is a pre-existing issue, but does this device link fully resolve the use-after-free for the vdec driver?
Because vdec is a V4L2 driver, userspace file descriptors can outlive the consumer driver's unbind sequence. If a user holds the video device open:
1. The canvas provider unbinds.
2. The device link forces the vdec driver to unbind.
3. devres_release_all() frees the devm-managed canvas and vdec core contexts.
4. Userspace later closes the file descriptor.
When the file descriptor is closed, vdec_close() calls vdec_free_canvas(), which attempts to dereference the already freed sess->core->canvas pointer.
Quoted 7 lines
drivers/soc/amlogic/meson-canvas.c +4
-0+put_device (&canvas_pdev->dev); +if (!link) +return ERR_PTR (dev_err_probe (dev, -EINVAL , +"Failed to create device link canvas\n" ));return canvas; }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
PATCH v7 18/19 media: meson: vdec: Increase VIFIFO buffer size to 32 MiB
In reply to Anand MoonDuring 4K video playback, the hardware Elementary Stream Parser (esparser) can rapidly push massive burst data payloads into the Video Input FIFO faster than the VPU decoding block can consume it. This causes a buffer overflow when stream usage breaks past the allocated hardware boundary:
[ 1852.587956] meson-vdec ff620000.video-decoder: VIFIFO usage (16779003) > VIFIFO size (16777216)
When this overflow happens on Amlogic SoCs, it triggers an invalid memory state, leading to stream corruption and fatal kernel panic lockups within videobuf2 error rollback handling routines.
Double the parsed bitstream swap exchange memory pool (SIZE_VIFIFO) from 16 MiB (SZ_16M) to 32 MiB (SZ_32M). This provides a larger canvas window to cushion high-frequency frame reordering spikes and prevents the VPU from crashing on demanding modern streams.
--- drivers/staging/media/meson/vdec/vdec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/staging/media/meson/vdec/vdec.c +2 -2
@@ -29,8 +29,8 @@ struct dummy_buf {struct list_head list; }; -/* 16 MiB for parsed bitstream swap exchange */ -#define SIZE_VIFIFO SZ_16M +/* 32 MiB for parsed bitstream swap exchange */ +#define SIZE_VIFIFO SZ_32Mstatic void vdec_free_canvas (struct amvdec_session *sess);static void vdec_reset_timestamps (struct amvdec_session *sess);
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 18/19] media: meson: vdec: Increase VIFIFO buffer size to 32 MiB
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
New issues:
- [High] The esparser_queue_all_src workqueue handler contains a fatal infinite loop due to incorrect usage of the scoped_guard macro in combination with break statements, monopolizing 100% of a CPU core.
- [High] Double decrement of the esparser_queued_bufs atomic counter per video frame leads to counter underflow and buffer overruns.
- [High] The 32 MiB VIFIFO buffer increase causes the bitstream size to overflow the 32-bit hardware PARSER_CONTROL register calculation, crashing the esparser DMA engine.
Pre-existing issues:
- [Critical] In vp9_update_header, an integer overflow when decoding maliciously crafted frame magnitudes allows frame_size to become negative, bypassing size validations and triggering a massive out-of-bounds memmove that panics the kernel.
- [High] Physical address truncation for the hardware ring buffer end pointer causes immediate DMA corruption when memory is allocated near the top of the 32-bit address space.
- [High] Use-After-Free (UAF) vulnerabilities in vdec_close during device unbind due to devm_kzalloc and v4l2_m2m_release lifecycle mismatches.
- [Medium] Timestamp tracking and matching breaks silently when the 32-bit hardware offset wraps around at the 4 GB bitstream boundary, due to an improper naive unsigned sequence comparison.
- [Medium] Race condition in esparser_write_data overwrites the completion flag, causing false input parsing timeouts.
--
--- Patch [18]: [PATCH v7 18/19] media: meson: vdec: Increase VIFIFO buffer size to 32 MiB --- commit 2addc7a4961dc5fcc69dfbb2e96d57eb89853de9
Author: Anand Moon <[email protected]>
media: meson: vdec: Increase VIFIFO buffer size to 32 MiB
During 4K video playback, the hardware Elementary Stream Parser (esparser) can rapidly push massive burst data payloads into the Video Input FIFO faster than the VPU decoding block can consume it. This causes a buffer overflow when stream usage breaks past the allocated hardware boundary. This patch doubles the parsed bitstream swap exchange memory pool to 32 MiB.
drivers/staging/media/meson/vdec/vdec.c struct dummy_buf { · line 33
Quoted 12 lines
drivers/staging/media/meson/vdec/vdec.c +2
-2@@ -29,8 +29,8 @@ struct dummy_buf {struct list_head list; }; -/* 16 MiB for parsed bitstream swap exchange */ -#define SIZE_VIFIFO SZ_16M +/* 32 MiB for parsed bitstream swap exchange */ +#define SIZE_VIFIFO SZ_32M
[Severity: High]
Does increasing the buffer to 32 MiB cause the bitstream size to overflow the 32-bit hardware PARSER_CONTROL register calculation?
In drivers/staging/media/meson/vdec/esparser.c:esparser_write_data():
amvdec_write_parser(core, PARSER_CONTROL, ES_WRITE | ES_PARSER_START | ES_SEARCH | (size << ES_PACK_SIZE_BIT));
With ES_PACK_SIZE_BIT being 8, for a size >= 16 MiB this left shift will overflow a 32-bit unsigned integer. This can result in the DMA engine receiving a truncated payload size and crashing.
[Severity: High]
This is a pre-existing issue, but does increasing the buffer size make it highly probable that the hardware ring buffer end pointer truncates, causing immediate DMA corruption?
In drivers/staging/media/meson/vdec/vdec_hevc.c:vdec_hevc_stbuf_init():
amvdec_write_dos(core, HEVC_STREAM_END_ADDR, sess->vififo_paddr + sess->vififo_size);
If the VIFIFO buffer is allocated near the end of the 32-bit address space, the calculation sess->vififo_paddr + sess->vififo_size can exceed 32 bits, resulting in a value like 0x100000000. This is silently truncated to 0 when passed to amvdec_write_dos() which accepts a 32-bit u32.
[Severity: Medium]
This is a pre-existing issue, but how does the driver handle timestamps when the 32-bit hardware offset wraps around at the 4 GB bitstream boundary?
In drivers/staging/media/meson/vdec/vdec_helpers.c:amvdec_dst_buf_done_offset():
list_for_each_entry_safe(tmp, n, &sess->timestamps, list) {
if (tmp->offset > offset) {
...
break;
}The stream offset accumulates into a 32-bit integer in esparser_get_offset() that naturally wraps at 4 GB. The list_for_each_entry_safe loop uses a non-wrap-safe raw unsigned comparison. When the offset wraps around to 0, a large tmp->offset immediately evaluates as greater and incorrectly breaks the loop, stranding or dropping timestamps.
[Severity: High]
Are there Use-After-Free vulnerabilities in vdec_close() during device unbind due to devm_kzalloc() and v4l2_m2m_release() lifecycle mismatches? This isn't a bug introduced by this patch, but...
In drivers/staging/media/meson/vdec/vdec.c:vdec_remove():
video_unregister_device(core->vdev_dec); v4l2_m2m_release(core->m2m_dev);
The unbind path frees the core structure while V4L2 file descriptors may still be actively held by userspace.
Later, if an application closes the file descriptor, vdec_close() is invoked:
synchronize_irq(core->vdec_irq);
This could dereference the freed core pointer and the freed m2m_dev.
[Severity: High]
Does the esparser_queue_all_src() workqueue handler contain an infinite loop that could monopolize a CPU core?
In drivers/staging/media/meson/vdec/esparser.c:esparser_queue_all_src():
while (1) {
struct vb2_v4l2_buffer *vbuf = NULL;
bool processed_frame = false;
scoped_guard(mutex, &sess->lock) {
...
vbuf = v4l2_m2m_next_src_buf(sess->m2m_ctx);
if (!vbuf)
break;
...
}
if (processed_frame)
break;
...
}The break statements are intended to exit the function when no buffers are available. However, they only escape the scoped_guard's internal for loop. This leaves the outer while (1) loop intact since processed_frame is false, which could result in a lockup of a kernel worker thread.
[Severity: High]
Does this code double decrement the esparser_queued_bufs atomic counter?
In drivers/staging/media/meson/vdec/vdec_helpers.c, dst_buf_done() calls atomic_dec(&sess->esparser_queued_bufs). However, its callers also execute their own atomic_dec():
amvdec_dst_buf_done():
dst_buf_done(sess, vbuf, field, timestamp, timecode, vbuf_flags); atomic_dec(&sess->esparser_queued_bufs);
amvdec_dst_buf_done_offset():
dst_buf_done(sess, vbuf, field, timestamp, timecode, vbuf_flags); if (match) atomic_dec(&sess->esparser_queued_bufs);
This could lead to counter underflow, bypassing buffer limit checks and causing hardware queue overruns.
[Severity: Medium]
This is a pre-existing issue, but does esparser_write_data() contain a race condition that overwrites the completion flag?
In drivers/staging/media/meson/vdec/esparser.c:esparser_write_data():
amvdec_write_parser(core, PARSER_FETCH_CMD, (7 << FETCH_ENDIAN_BIT) | (size + SEARCH_PATTERN_LEN)); search_done = 0; return wait_event_interruptible_timeout(wq, search_done, (HZ / 5));
The CPU initiates hardware processing by writing to PARSER_FETCH_CMD, which can instantly trigger esparser_isr(). If the hardware interrupt fires immediately, the ISR sets search_done = 1. The CPU sequentially executes search_done = 0 afterwards, which could erase the completion signal before waiting on it.
[Severity: Critical]
This is a pre-existing issue, but does vp9_update_header() have an integer overflow when decoding frame magnitudes that can bypass size validations?
In drivers/staging/media/meson/vdec/esparser.c:vp9_update_header():
frame_size[cur_frame] |= (dp[mag_ptr] << (cur_mag * 8));
The magnitude byte is promoted to a signed 32-bit integer. When shifted by 24 bits, it can become a large negative number if the maliciously crafted payload has dp[mag_ptr] >= 0x80.
Later, this negative frame size bypasses total_datasize validations and triggers an out-of-bounds copy in memmove:
memmove(fdata + VP9_HEADER_SIZE, old_framedata, framesize);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
PATCH v7 19/19 gpu: drm: meson: Fix DMA segment size limits and maximize allocation boundaries
In reply to Anand MoonWhen importing DMABUFs exported by the Amlogic video decoder driver (meson_vdec) for hardware-accelerated rendering paths, the DMA core subsystem throws constraint validation warnings. This occurs because the display controller master device lacks explicit DMA layout configuration, causing it to fall back to a default 64KB maximum segment size limit.
Address these architectural constraints during the master bind sequence:
1. Initialize and validate a 32-bit coherent DMA allocation window by invoking dma_set_mask_and_coherent() with a DMA_BIT_MASK(32) argument.
2. Maximize the contiguous scatter-gather allocation segment boundary check constraint to UINT_MAX using the dma_set_max_seg_size() helper.
This guarantees that large video bitstream frame buffers can be imported and scanned out across sub-driver domains without triggering allocation warnings or page boundary splits.
--- drivers/gpu/drm/meson/meson_drv.c | 6 ++++++ 1 file changed, 6 insertions(+)
drivers/gpu/drm/meson/meson_drv.c +6 -0
@@ -202,6 +202,12 @@ static int meson_drv_bind_master(struct device *dev, bool has_components)if (IS_ERR (drm))return PTR_ERR (drm); + ret =dma_set_mask_and_coherent (dev,DMA_BIT_MASK (32 )); +if (ret) + goto free_drm; + +dma_set_max_seg_size (dev,UINT_MAX ); + priv =devm_kzalloc (dev,sizeof (*priv),GFP_KERNEL );if (!priv) { ret = -ENOMEM ;
Signature · 1 line
2.50.1
[email protected]
AI reviewRe: [PATCH v7 19/19] gpu: drm: meson: Fix DMA segment size limits and maximize allocation boundaries
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Uninitialized pointer dereference in the error handling path of meson_drv_bind_master.
Pre-existing issues:
- [High] NULL pointer dereference when devm_kzalloc fails to allocate the priv structure. --
--- Patch [19]: [PATCH v7 19/19] gpu: drm: meson: Fix DMA segment size limits and maximize allocation boundaries --- commit 5509f37f742bf971664f2f0e723c6187dedff8d4
Author: Anand Moon <[email protected]>
gpu: drm: meson: Fix DMA segment size limits and maximize allocation boundaries
This commit addresses DMA layout configuration constraints by setting a 32-bit coherent DMA allocation window and maximizing the scatter-gather allocation segment boundary check constraint.
drivers/gpu/drm/meson/meson_drv.c static int meson_drv_bind_master(struct device *dev, bool has_components) · line 207
Quoted 11 lines
drivers/gpu/drm/meson/meson_drv.c +3
-0@@ -202,6 +202,12 @@ static int meson_drv_bind_master(struct device *dev, bool has_components)if (IS_ERR (drm))return PTR_ERR (drm); + ret =dma_set_mask_and_coherent (dev,DMA_BIT_MASK (32 )); +if (ret) + goto free_drm;
[Severity: High]
If dma_set_mask_and_coherent() fails in meson_drv_bind_master(), the code jumps to the free_drm error path before the priv pointer is allocated and initialized.
Because the free_drm label unconditionally passes priv to encoder removal functions:
drivers/gpu/drm/meson/meson_drv.c:meson_drv_bind_master() {
...free_drm:
drm_dev_put(drm); meson_encoder_dsi_remove(priv);
...
}
And meson_encoder_dsi_remove() dereferences priv immediately:
drivers/gpu/drm/meson/meson_encoder_dsi.c:meson_encoder_dsi_remove() {
...
if (priv->encoders[MESON_ENC_DSI]) {...
}
Will this result in dereferencing an uninitialized stack pointer?
Quoted 7 lines
drivers/gpu/drm/meson/meson_drv.c +3
-0+ +dma_set_max_seg_size (dev,UINT_MAX ); + priv =devm_kzalloc (dev,sizeof (*priv),GFP_KERNEL );if (!priv) { ret = -ENOMEM ;goto
free_drm;
[Severity: High]
This is a pre-existing issue, but if devm_kzalloc() in meson_drv_bind_master() fails to allocate priv, it will be set to NULL and the code jumps to free_drm.
Similar to the above issue, this will pass a NULL priv pointer to meson_encoder_dsi_remove(), which does not check for NULL before dereferencing it:
drivers/gpu/drm/meson/meson_encoder_dsi.c:meson_encoder_dsi_remove() {
...
if (priv->encoders[MESON_ENC_DSI]) {...
}
Will this cause a NULL pointer dereference during an out-of-memory condition?
Quoted 1 line
}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19
Re: [PATCH v7 19/19] gpu: drm: meson: Fix DMA segment size limits and maximize allocation boundaries
In reply to Anand MoonLe lundi 13 juillet 2026 à 17:37 +0530, Anand Moon a écrit :
Quoted 13 lines
When importing DMABUFs exported by the Amlogic video decoder driver (
meson_vdec) for hardware-accelerated rendering paths, the DMA core subsystem throws constraint validation warnings. This occurs because the display controller master device lacks explicit DMA layout configuration, causing it to fall back to a default 64KB maximum segment size limit.Address these architectural constraints during the master bind sequence:
1. Initialize and validate a 32-bit coherent DMA allocation window by invoking
dma_set_mask_and_coherent()with aDMA_BIT_MASK(32)argument.
2. Maximize the contiguous scatter-gather allocation segment boundary check constraint toUINT_MAXusing thedma_set_max_seg_size()helper.
Quoted 6 lines
This guarantees that large video bitstream frame buffers can be imported and scanned out across sub-driver domains without triggering allocation warnings or page boundary splits.
Cc Nicolas Dufresne <[email protected]>Signed-off-by Anand Moon <[email protected]>
Quoted 21 lines
---
drivers/gpu/drm/meson/meson_drv.c | 6 ++++++1 file changed, 6 insertions(+)drivers/gpu/drm/meson/meson_drv.c +0
-0@@ -202,6 +202,12 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) if (IS_ERR(drm)) return PTR_ERR(drm);drivers/gpu/drm/meson/meson_drv.c +6
-0+ ret =dma_set_mask_and_coherent (dev,DMA_BIT_MASK (32 )); +if (ret) + goto free_drm; + +dma_set_max_seg_size (dev,UINT_MAX ); +priv =
devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) {
ret =-ENOMEM;
Hi All,
Quoted 51 lines · Anand Moon
This patch series addresses a collection of critical race conditions, memory leaks, use-after-free bugs, and resource management issues within the Amlogic Meson video decoder (meson-vdec) driver and associated components.
The primary focus is tightening session lifecycles, safely synchronising the V4L2 m2m framework callbacks, hardening work queue operations (
esparser_queue_work), and correcting buffer validations to prevent stability issues like KASAN out-of-bounds errors or stream-on timeouts.Summary of Changes:
* Race Conditions & Synchronisation: Fixes multi-threading and execution races across stream-on, stream-off, job abort, and teardown pathways.
* Memory Lifecycle: Eliminates memory leaks and use-after-free vulnerabilities by safely pinning, canceling, and sequencing device removals.
* VP9 & Buffer Hardening: Addresses payload handling bugs in the ES parser, properly tracks ongoing buffers via atomic counters, and prevents invalid memory writes.
* Platform/DMA Adjustments: Adjusts DMA segment configurations, canvas handling, and buffer sizes to match modern hardware demands.Note on an outstanding issue:
An execution deadlock occurs in the driver workqueue path during high stress or long playback sessions. This stall is triggered when the Amlogic hardware internal video FIFO buffer becomes completely full. Whenesparser_queue_all_srcruns, the capacity validation check is tripped due to a design limitation in how buffer exhaustion is handled. This blocks progress and leaves the workqueue worker waiting indefinitely on a core mutex lock.The resulting hung task call trace shows the worker thread blocking on the internal lock:
Workqueue: events esparser_queue_all_src [meson_vdec]...
__mutex_lockmutex_lock_nestedesparser_queue_all_srcI have tried to address this issue, but the underlying deadlock persists under extreme conditions.
Any feedback on this new appock is welcome.
Testing was done using v4l2-compliance and active decoding pipelines on Meson platforms (like Hardkernel ODROID-N2+ and ODROID-C4) SBCs.
The v4l2-compliance tests passed successfully via:
Quoted 9 lines
$ v4l2-compliance -s -v -d /dev/video0Pipelines were validated using the following GStreamer script.
[1] https://gist.github.com/moonlinux/08295ba1f17ce7155550773f2d2cb6e5
Previous changes:
v6: https://lore.kernel.org/all/[email protected]/
I will review Sashiko's feedback. I will look into these suggestions and work on incorporating them to improve the code quality
Quoted 3 lines
Thanks
-Annad
Thanks
-Anand
Quoted 60 lines · Anand Moon
Hi All,
Quoted 55 lines · Anand Moon
This patch series addresses a collection of critical race conditions, memory leaks, use-after-free bugs, and resource management issues within the Amlogic Meson video decoder (meson-vdec) driver and associated components.
The primary focus is tightening session lifecycles, safely synchronising the V4L2 m2m framework callbacks, hardening work queue operations (
esparser_queue_work), and correcting buffer validations to prevent stability issues like KASAN out-of-bounds errors or stream-on timeouts.Summary of Changes:
* Race Conditions & Synchronisation: Fixes multi-threading and execution races across stream-on, stream-off, job abort, and teardown pathways.
* Memory Lifecycle: Eliminates memory leaks and use-after-free vulnerabilities by safely pinning, canceling, and sequencing device removals.
* VP9 & Buffer Hardening: Addresses payload handling bugs in the ES parser, properly tracks ongoing buffers via atomic counters, and prevents invalid memory writes.
* Platform/DMA Adjustments: Adjusts DMA segment configurations, canvas handling, and buffer sizes to match modern hardware demands.Note on an outstanding issue:
An execution deadlock occurs in the driver workqueue path during high stress or long playback sessions. This stall is triggered when the Amlogic hardware internal video FIFO buffer becomes completely full. Whenesparser_queue_all_srcruns, the capacity validation check is tripped due to a design limitation in how buffer exhaustion is handled. This blocks progress and leaves the workqueue worker waiting indefinitely on a core mutex lock.The resulting hung task call trace shows the worker thread blocking on the internal lock:
Workqueue: events esparser_queue_all_src [meson_vdec]...
__mutex_lockmutex_lock_nestedesparser_queue_all_srcI have tried to address this issue, but the underlying deadlock persists under extreme conditions.
Any feedback on this new appock is welcome.
Testing was done using v4l2-compliance and active decoding pipelines on Meson platforms (like Hardkernel ODROID-N2+ and ODROID-C4) SBCs.
The v4l2-compliance tests passed successfully via:
$ v4l2-compliance -s -v -d /dev/video0Pipelines were validated using the following GStreamer script.
Quoted 8 lines
Quoted 5 lines
[1] https://gist.github.com/moonlinux/08295ba1f17ce7155550773f2d2cb6e5
Previous changes:
v6: https://lore.kernel.org/all/[email protected]/I will review Sashiko's feedback. I will look into these suggestions and work on incorporating them to improve the code quality
Hello Anand,
I’ve picked this entire series into a LibreELEC image using Kodi 22 and ffmpeg 8.1 (using the RPi source that improves v4l2_m2m behaviour) on a 7.2-rc3 kernel and run some tests on a GXBB/S905 board.
Without this series H264 plays and seeks well and the unmerged H265 codec also plays well and seeks well but with minor artefacts (something that I need to look into). The experience is not perfect (this driver has a few flaws) but it’s consistent with the ‘usable’ behaviour of the driver in the last 1-2 years.
With this series H264 plays but seek seems to hang everything with no logged errors anywhere. H265 results in a black screen and again everything’s hung.
It’s possible that kernel changes need to be balanced with userspace? Can you please share details of how you have been testing things (and on what hardware)? - I will attempt to replicate and poke smaller portions of the series to see if I can isolate which bits are problematic to the otherwise usable current experience.
Christian