media: iris: Fix issues in the driver unbind path
Subsystems (5)
v2 cover media: iris: Fix issues in the driver unbind path
The driver unbind can be triggered through sysfs while a client has a video session running. The devm resources are freed when the unbind completes. That leads to use after free for the running sessions.
The core and its device are now kept allocated until the last file descriptor is closed. New sessions are stopped as soon as the removal starts. The interrupt is freed before the core, so the interrupt thread cannot run on freed memory.
The system error work was only cancelled during the removal. An interrupt could still arrive afterwards and queue the work again. The work is now disabled.
The runtime PM usage count was dropped twice when a resume failed. This happens for a command written after runtime PM is disabled. The count underflowed. The reference is no longer dropped a second time.
---
Changes in v2:
- Updated the commit description of patch 3 (Bryan)
- Split the interrupt freeing into a separate patch (Bryan)
- Added a new patch to wake up clients blocked on open sessions at removal.
- Link to v1: https://lore.kernel.org/r/[email protected]
---
Vishnu Reddy (5):
media: iris: Fix runtime PM reference imbalance on failed resume
media: iris: Disable the system error work on removal
media: iris: Fix use-after-free on unbind with open sessions
media: iris: Wake up blocked clients when the driver is removed
media: iris: Free the interrupt in iris_remove()
drivers/media/platform/qcom/iris/iris_core.c | 10 +++- drivers/media/platform/qcom/iris/iris_core.h | 2 + drivers/media/platform/qcom/iris/iris_hfi_queue.c | 17 +++++- drivers/media/platform/qcom/iris/iris_instance.h | 2 + drivers/media/platform/qcom/iris/iris_power.c | 13 +++++ drivers/media/platform/qcom/iris/iris_probe.c | 64 +++++++++++++++++++---- drivers/media/platform/qcom/iris/iris_vidc.c | 8 +++ 7 files changed, 103 insertions(+), 13 deletions(-)
---
base-commit: 630059a5a632377b9b7b487baccde92b7ae1dd47
change-id: 20261013-iris-handle-unbind-fe09689e6696
Best regards,
Signature · 1 line
Vishnu Reddy <[email protected]>
PATCH v2 1/5 media: iris: Fix runtime PM reference imbalance on failed resume
pm_runtime_resume_and_get() drops its usage count when the resume fails. iris_hfi_queue_cmd_write() dropped it a second time on that path, resulting in a runtime PM usage count underflow warning:
qcom-iris aa00000.video-codec: Runtime PM usage count underflow!
--- drivers/media/platform/qcom/iris/iris_hfi_queue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/media/platform/qcom/iris/iris_hfi_queue.c +1 -1
@@ -132,7 +132,7 @@ int iris_hfi_queue_cmd_write(struct iris_core *core, void *pkt, u32 pkt_size) ret =pm_runtime_resume_and_get (core->dev);if (ret <0 ) - goto exit; +return ret;mutex_lock (&core->lock); ret =iris_hfi_queue_cmd_write_locked (core, pkt, pkt_size);
Signature · 1 line
2.34.1
PATCH v2 2/5 media: iris: Disable the system error work on removal
In reply to Vishnu Reddyiris_remove() calls cancel_delayed_work_sync() to stop the system error work. This cancels a pending work and waits for a running one. It does not stop the work from being queued again afterwards.
A system error interrupt can still arrive after that call. The interrupt handler queues the work again. The work then runs after the removal and uses resources that are already freed.
Use disable_delayed_work_sync() instead. Once it returns no new work can be queued. The work cannot run again during or after the removal.
--- drivers/media/platform/qcom/iris/iris_probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/media/platform/qcom/iris/iris_probe.c +1 -1
@@ -251,7 +251,7 @@ static void iris_remove(struct platform_device *pdev)if (!core)return ; -cancel_delayed_work_sync (&core->sys_error_handler); +disable_delayed_work_sync (&core->sys_error_handler);iris_core_deinit (core);
Signature · 1 line
2.34.1
PATCH v2 3/5 media: iris: Fix use-after-free on unbind with open sessions
In reply to Vishnu ReddyThe driver can be unbound through sysfs while a client has a video session open. The core is devm allocated and freed when remove() returns, so the open sessions then use freed memory.
Only the core structure needs to outlive remove(), because it is the only thing the file operations of an open session still use. Allocate it with kzalloc and tie it to the reference count of its v4l2_device, which every registered video device holds. It is freed when the last file descriptor is closed. The other devm resources (register mapping, response packet, clocks, power domains, interconnects) are only used by the power on/off sequence, the clock and bandwidth votes and the interrupt handler. All of them are released with the unbind, after the core is shut down.
To make sure nothing uses them afterwards, set a removing flag at the start of iris_remove(). It makes core init, new sessions, command writes and the clock and interconnect votes fail with -ENODEV, so a client still holding a file descriptor cannot power the hardware on or vote for resources that are gone. A client waiting for the firmware only times out and returns an error without touching them. Runtime PM is also disabled explicitly in iris_remove() instead of with devm, because its callbacks use the core.
--- drivers/media/platform/qcom/iris/iris_core.c | 10 +++++- drivers/media/platform/qcom/iris/iris_core.h | 2 ++ drivers/media/platform/qcom/iris/iris_hfi_queue.c | 15 +++++++- drivers/media/platform/qcom/iris/iris_power.c | 13 +++++++ drivers/media/platform/qcom/iris/iris_probe.c | 44 ++++++++++++++++++----- drivers/media/platform/qcom/iris/iris_vidc.c | 3 ++ 6 files changed, 76 insertions(+), 11 deletions(-)
drivers/media/platform/qcom/iris/iris_core.c +9 -1
@@ -43,7 +43,10 @@ static int iris_wait_for_system_response(struct iris_core *core) ret =wait_for_completion_timeout (&core->core_init_done,msecs_to_jiffies (HW_RESPONSE_TIMEOUT_VALUE ));if (!ret) { - core->state =IRIS_CORE_ERROR ; +mutex_lock (&core->lock); +if (!core->removing) + core->state =IRIS_CORE_ERROR ; +mutex_unlock (&core->lock);return -ETIMEDOUT ; }
@@ -56,6 +59,11 @@ int iris_core_init(struct iris_core *core)int ret;mutex_lock (&core->lock); +if (core->removing) { + ret = -ENODEV ; + goto exit; + } +if (core->state ==IRIS_CORE_INIT ) { ret =0 ; goto exit;
drivers/media/platform/qcom/iris/iris_core.h +2 -0
@@ -62,6 +62,7 @@ struct qcom_ubwc_cfg_data;* @iris_firmware_desc: a pointer to the firmware-specific descriptive data * @ubwc_cfg: UBWC configuration for the platform * @state: current state of core +* @removing: set when driver removal starts, blocks core init and resource votes * @iface_q_table_daddr: device address for interface queue table memory * @sfr_daddr: device address for SFR (Sub System Failure Reason) register memory * @iface_q_table_vaddr: virtual address for interface queue table memory
@@ -108,6 +109,7 @@ struct iris_core {const struct iris_firmware_desc *iris_firmware_desc;const struct qcom_ubwc_cfg_data *ubwc_cfg;enum iris_core_state state; +bool removing;dma_addr_t iface_q_table_daddr;dma_addr_t sfr_daddr;void *iface_q_table_vaddr;
drivers/media/platform/qcom/iris/iris_hfi_queue.c +14 -1
@@ -128,13 +128,26 @@ int iris_hfi_queue_cmd_write_locked(struct iris_core *core, void *pkt, u32 pkt_sint iris_hfi_queue_cmd_write (struct iris_core *core,void *pkt,u32 pkt_size) { -int ret; +int ret =0 ; + +mutex_lock (&core->lock); +if (core->removing) + ret = -ENODEV ; +mutex_unlock (&core->lock); +if (ret) +return ret; ret =pm_runtime_resume_and_get (core->dev);if (ret <0 )return ret;mutex_lock (&core->lock); +if (core->removing) { +mutex_unlock (&core->lock); + ret = -ENODEV ; + goto exit; + } + ret =iris_hfi_queue_cmd_write_locked (core, pkt, pkt_size);if (ret) {mutex_unlock (&core->lock);
drivers/media/platform/qcom/iris/iris_power.c +13 -0
@@ -47,6 +47,11 @@ static int iris_set_interconnects(struct iris_inst *inst)int ret;mutex_lock (&core->lock); +if (core->removing) { + ret = -ENODEV ; + goto exit; + } +list_for_each_entry (instance, &core->instances, list) {if (!instance->max_input_data_size)continue ;
@@ -56,6 +61,7 @@ static int iris_set_interconnects(struct iris_inst *inst) ret =iris_set_icc_bw (core, total_bw_ddr); +exit:mutex_unlock (&core->lock);return ret;
@@ -83,6 +89,11 @@ static int iris_set_clocks(struct iris_inst *inst)int ret;mutex_lock (&core->lock); +if (core->removing) { + ret = -ENODEV ; + goto exit; + } +list_for_each_entry (instance, &core->instances, list) {if (!instance->max_input_data_size)continue ;
@@ -92,6 +103,8 @@ static int iris_set_clocks(struct iris_inst *inst) core->power.clk_freq = freq; ret =iris_opp_set_rate (core->dev, freq); + +exit:mutex_unlock (&core->lock);return ret;
drivers/media/platform/qcom/iris/iris_probe.c +35 -9
83 lines not on this page · raw message
drivers/media/platform/qcom/iris/iris_vidc.c +3 -0
@@ -64,6 +64,9 @@ static int iris_add_session(struct iris_inst *inst)guard (mutex)(&core->lock); +if (core->removing) +return -ENODEV ; +list_for_each_entry (iter, &core->instances, list) count++;
Signature · 1 line
2.34.1
Re: [PATCH v2 3/5] media: iris: Fix use-after-free on unbind with open sessions
Quoted 104 lines · Vishnu Reddy
The driver can be unbound through sysfs while a client has a video session open. The core is devm allocated and freed when
remove()returns, so the open sessions then use freed memory.Only the core structure needs to outlive
remove(), because it is the only thing the file operations of an open session still use. Allocate it with kzalloc and tie it to the reference count of itsv4l2_device, which every registered video device holds. It is freed when the last file descriptor is closed. The other devm resources (register mapping, response packet, clocks, power domains, interconnects) are only used by the power on/off sequence, the clock and bandwidth votes and the interrupt handler. All of them are released with the unbind, after the core is shut down.To make sure nothing uses them afterwards, set a removing flag at the start of
iris_remove(). It makes core init, new sessions, command writes and the clock and interconnect votes fail with-ENODEV, so a client still holding a file descriptor cannot power the hardware on or vote for resources that are gone. A client waiting for the firmware only times out and returns an error without touching them. Runtime PM is also disabled explicitly iniris_remove()instead of with devm, because its callbacks use the core.Fixes 38506cb7e8d2 ("media: iris: add platform driver for iris video device")Reported-by sashiko-bot <[email protected]>Signed-off-by Vishnu Reddy <[email protected]>--- drivers/media/platform/qcom/iris/iris_core.c | 10 +++++- drivers/media/platform/qcom/iris/iris_core.h | 2 ++ drivers/media/platform/qcom/iris/iris_hfi_queue.c | 15 +++++++- drivers/media/platform/qcom/iris/iris_power.c | 13 +++++++ drivers/media/platform/qcom/iris/iris_probe.c | 44 ++++++++++++++++++----- drivers/media/platform/qcom/iris/iris_vidc.c | 3 ++ 6 files changed, 76 insertions(+), 11 deletions(-)drivers/media/platform/qcom/iris/iris_core.c +9
-1@@ -43,7 +43,10 @@ static int iris_wait_for_system_response(struct iris_core *core) ret =wait_for_completion_timeout (&core->core_init_done,msecs_to_jiffies (HW_RESPONSE_TIMEOUT_VALUE ));if (!ret) { - core->state =IRIS_CORE_ERROR ; +mutex_lock (&core->lock); +if (!core->removing) + core->state =IRIS_CORE_ERROR ; +mutex_unlock (&core->lock);return -ETIMEDOUT ; }@@ -56,6 +59,11 @@ int iris_core_init(struct iris_core *core)int ret;mutex_lock (&core->lock); +if (core->removing) { + ret = -ENODEV ; + goto exit; + } +if (core->state ==IRIS_CORE_INIT ) { ret =0 ; goto exit;drivers/media/platform/qcom/iris/iris_core.h +2
-0@@ -62,6 +62,7 @@ struct qcom_ubwc_cfg_data;* @iris_firmware_desc: a pointer to the firmware-specific descriptive data * @ubwc_cfg: UBWC configuration for the platform * @state: current state of core +* @removing: set when driver removal starts, blocks core init and resource votes * @iface_q_table_daddr: device address for interface queue table memory * @sfr_daddr: device address for SFR (Sub System Failure Reason) register memory * @iface_q_table_vaddr: virtual address for interface queue table memory @@ -108,6 +109,7 @@ struct iris_core {const struct iris_firmware_desc *iris_firmware_desc;const struct qcom_ubwc_cfg_data *ubwc_cfg;enum iris_core_state state; +bool removing;dma_addr_t iface_q_table_daddr;dma_addr_t sfr_daddr;void *iface_q_table_vaddr;drivers/media/platform/qcom/iris/iris_hfi_queue.c +8
-1@@ -128,13 +128,26 @@ int iris_hfi_queue_cmd_write_locked(struct iris_core *core, void *pkt, u32 pkt_sint iris_hfi_queue_cmd_write (struct iris_core *core,void *pkt,u32 pkt_size) { -int ret; +int ret =0 ; + +mutex_lock (&core->lock); +if (core->removing) + ret = -ENODEV ; +mutex_unlock (&core->lock); +if (ret) +return ret; ret =pm_runtime_resume_and_get (core->dev);
drivers/media/platform/qcom/iris/iris_hfi_queue.c int iris_hfi_queue_cmd_write_locked(struct iris_core *core, void *pkt, u32 pkt_s · line 150
Quoted 10 lines
drivers/media/platform/qcom/iris/iris_hfi_queue.c +6
-0if (ret <0 )return ret;mutex_lock (&core->lock); +if (core->removing) { +mutex_unlock (&core->lock); + ret = -ENODEV ; + goto exit; + } +
This locking scheme is looks racy, what's the point of this granularity ?
---
bod
Re: [PATCH v2 3/5] media: iris: Fix use-after-free on unbind with open sessions
Quoted 112 lines · Bryan O'Donoghue
Quoted 111 lines · Vishnu Reddy
The driver can be unbound through sysfs while a client has a video session open. The core is devm allocated and freed when
remove()returns, so the open sessions then use freed memory.Only the core structure needs to outlive
remove(), because it is the only thing the file operations of an open session still use. Allocate it with kzalloc and tie it to the reference count of itsv4l2_device, which every registered video device holds. It is freed when the last file descriptor is closed. The other devm resources (register mapping, response packet, clocks, power domains, interconnects) are only used by the power on/off sequence, the clock and bandwidth votes and the interrupt handler. All of them are released with the unbind, after the core is shut down.To make sure nothing uses them afterwards, set a removing flag at the start of
iris_remove(). It makes core init, new sessions, command writes and the clock and interconnect votes fail with-ENODEV, so a client still holding a file descriptor cannot power the hardware on or vote for resources that are gone. A client waiting for the firmware only times out and returns an error without touching them. Runtime PM is also disabled explicitly iniris_remove()instead of with devm, because its callbacks use the core.Fixes 38506cb7e8d2 ("media: iris: add platform driver for iris video device")Reported-by sashiko-bot <[email protected]>Signed-off-by Vishnu Reddy <[email protected]>---
drivers/media/platform/qcom/iris/iris_core.c | 10 +++++- drivers/media/platform/qcom/iris/iris_core.h | 2 ++drivers/media/platform/qcom/iris/iris_hfi_queue.c| 15 +++++++- drivers/media/platform/qcom/iris/iris_power.c | 13 +++++++ drivers/media/platform/qcom/iris/iris_probe.c | 44 ++++++++++++++++++----- drivers/media/platform/qcom/iris/iris_vidc.c | 3 ++6 files changed, 76 insertions(+), 11 deletions(-)drivers/media/platform/qcom/iris/iris_core.c +4
-1@@ -43,7 +43,10 @@ static int iris_wait_for_system_response(struct iris_core *core) ret = wait_for_completion_timeout(&core->core_init_done, msecs_to_jiffies(HW_RESPONSE_TIMEOUT_VALUE)); if (!ret) { - core->state = IRIS_CORE_ERROR; + mutex_lock(&core->lock); + if (!core->removing) + core->state = IRIS_CORE_ERROR; + mutex_unlock(&core->lock); return -ETIMEDOUT; }@@ -56,6 +59,11 @@ int
iris_core_init(struct iris_core *core)int ret;
mutex_lock(&core->lock);
+ if (core->removing) {
+ ret =-ENODEV;
+ goto exit;
+ }
+
if (core->state==IRIS_CORE_INIT) {
ret = 0;
goto exit;drivers/media/platform/qcom/iris/iris_core.h +1
-0@@ -62,6 +62,7 @@ struct qcom_ubwc_cfg_data; * @iris_firmware_desc: a pointer to the firmware-specific descriptive data * @ubwc_cfg: UBWC configuration for the platform * @state: current state of core + * @removing: set when driver removal starts, blocks core init and resourcevotes
* @iface_q_table_daddr: device address for interface queue table memory
* @sfr_daddr: device address for SFR (Sub System Failure Reason) register memory
* @iface_q_table_vaddr: virtual address for interface queue table memorydrivers/media/platform/qcom/iris/iris_core.h +1
-0@@ -108,6 +109,7 @@ struct iris_core { const struct iris_firmware_desc *iris_firmware_desc; const struct qcom_ubwc_cfg_data *ubwc_cfg; enum iris_core_state state; + bool removing; dma_addr_t iface_q_table_daddr; dma_addr_t sfr_daddr; void *iface_q_table_vaddr;drivers/media/platform/qcom/iris/iris_hfi_queue.c +0
-0@@ -128,13 +128,26 @@ int iris_hfi_queue_cmd_write_locked(struct iris_core *core, void *pkt, u32 pkt_sint
iris_hfi_queue_cmd_write(struct iris_core *core, void *pkt, u32 pkt_size)
{
- int ret;
+ int ret = 0;
+
+mutex_lock(&core->lock);
+ if (core->removing)
+ ret =-ENODEV;
+mutex_unlock(&core->lock);
+ if (ret)
+ return ret;
ret =pm_runtime_resume_and_get(core->dev);
if (ret < 0)
return ret;
mutex_lock(&core->lock);
Quoted 8 lines
Quoted 6 lines
+ if (
core->removing) {
+mutex_unlock(&core->lock);
+ ret =-ENODEV;
+ goto exit;
+ }
+This locking scheme is looks racy, what's the point of this granularity ?
The first check is an early reject when removal is already in progress. The second re-validates the state after resume, since iris_remove() can start while pm_runtime_resume_and_get() is in progress. Without the second check, a command could be issued after teardown has started. Also, core->lock cannot be held across the entire function because iris_resume() also acquires core->lock.
Quoted 3 lines
---
bod
PATCH v2 4/5 media: iris: Wake up blocked clients when the driver is removed
In reply to Vishnu Reddyiris_remove() does not wake up a client that is blocked in VIDIOC_DQBUF or poll() on an open session. The client stays blocked and keeps its file descriptor open.
Flag the vb2 queues of all open instances as errored after the core is shut down. This wakes up the blocked clients, which fail with an error and can close the file descriptor. Skip the instances that are already closing, as iris_close() releases their queues.
--- drivers/media/platform/qcom/iris/iris_instance.h | 2 ++ drivers/media/platform/qcom/iris/iris_probe.c | 16 ++++++++++++++++ drivers/media/platform/qcom/iris/iris_vidc.c | 5 +++++ 3 files changed, 23 insertions(+)
drivers/media/platform/qcom/iris/iris_instance.h +2 -0
@@ -50,6 +50,7 @@ struct iris_hfi_session_ops;* @icc_data: structure of interconnect data * @m2m_dev: a reference to m2m device structure * @m2m_ctx: a reference to m2m context structure +* @closing: set on close, skips the queue error during removal * @sequence_cap: a sequence counter for capture queue * @sequence_out: a sequence counter for output queue * @tss: timestamp metadata
@@ -98,6 +99,7 @@ struct iris_inst {struct icc_vote_data icc_data;struct v4l2_m2m_dev *m2m_dev;struct v4l2_m2m_ctx *m2m_ctx; +bool closing;u32 sequence_cap;u32 sequence_out;struct iris_ts_metadata tss[VIDEO_MAX_FRAME ];
drivers/media/platform/qcom/iris/iris_probe.c +16 -0
@@ -12,8 +12,10 @@#include <linux/reset.h> #include <linux/soc/qcom/ubwc.h> +#include "iris_buffer.h" #include "iris_core.h" #include "iris_ctrls.h" +#include "iris_instance.h" #include "iris_vidc.h" static int iris_init_icc (struct iris_core *core)
@@ -259,6 +261,18 @@ static void iris_core_put(void *data)v4l2_device_put (&core->v4l2_dev); } +static void iris_instances_vb2_queue_error (struct iris_core *core) +{ +struct iris_inst *inst; + +mutex_lock (&core->lock); +list_for_each_entry (inst, &core->instances, list) { +if (!inst->closing) +iris_vb2_queue_error (inst); + } +mutex_unlock (&core->lock); +} +static void iris_remove (struct platform_device *pdev) {struct iris_core *core;
@@ -278,6 +292,8 @@ static void iris_remove(struct platform_device *pdev)iris_core_deinit (core); +iris_instances_vb2_queue_error (core); +pm_runtime_dont_use_autosuspend (core->dev);pm_runtime_disable (core->dev);
drivers/media/platform/qcom/iris/iris_vidc.c +5 -0
@@ -312,6 +312,11 @@ static void iris_check_num_queued_internal_buffers(struct iris_inst *inst, u32 pint iris_close (struct file *filp) {struct iris_inst *inst =iris_get_inst (filp); +struct iris_core *core = inst->core; + +mutex_lock (&core->lock); + inst->closing = true; +mutex_unlock (&core->lock);v4l2_ctrl_handler_free (&inst->ctrl_handler);v4l2_m2m_ctx_release (inst->m2m_ctx);
Signature · 1 line
2.34.1
PATCH v2 5/5 media: iris: Free the interrupt in iris_remove()
In reply to Vishnu ReddyThe interrupt is requested with devm_request_threaded_irq(), so it is only freed after iris_remove() returns. Until then the threaded handler can still run while the rest of the removal is in progress.
Free the interrupt explicitly in iris_remove(), right after the core is shut down.
--- drivers/media/platform/qcom/iris/iris_probe.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/media/platform/qcom/iris/iris_probe.c +2 -0
@@ -292,6 +292,8 @@ static void iris_remove(struct platform_device *pdev)iris_core_deinit (core); +devm_free_irq (core->dev, core->irq, core); +iris_instances_vb2_queue_error (core);pm_runtime_dont_use_autosuspend (core->dev);
Signature · 1 line
2.34.1