drivers/hwtracing/coresight/coresight-tmc-etr.c | 7 +++++++ 1 file changed, 7 insertions(+)
When ETR is enabled as CS_MODE_SYSFS, if the buffer size is changed
and enabled again, currently sysfs_buf will point to the newly
allocated memory(buf_new) and free the old memory(buf_old). But the
etr_buf that is being used by the ETR remains pointed to buf_old, not
updated to buf_new. In this case, it will result in a memory
use-after-free issue.
Fix this by checking ETR's mode before updating and releasing buf_old,
if the mode is CS_MODE_SYSFS, then skip updating and releasing it.
Signed-off-by: Xiaoqi Zhuang <xiaoqi.zhuang@oss.qualcomm.com>
---
drivers/hwtracing/coresight/coresight-tmc-etr.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
index b07fcdb3fe1a..3e73cf2c38a3 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
@@ -1268,6 +1268,13 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
goto out;
}
+ /*
+ * Since this sink is already enabled, the existing buffer should not
+ * be released even if the buffer size has changed.
+ */
+ if (coresight_get_mode(csdev) == CS_MODE_SYSFS)
+ goto out;
+
/*
* If we don't have a buffer or it doesn't match the requested size,
* use the buffer allocated above. Otherwise reuse the existing buffer.
---
base-commit: 98ac9cc4b4452ed7e714eddc8c90ac4ae5da1a09
change-id: 20251020-fix_etr_issue-02c706dbc899
Best regards,
--
Xiaoqi Zhuang <xiaoqi.zhuang@oss.qualcomm.com>
On 10/20/2025 5:06 PM, Xiaoqi Zhuang wrote:
> When ETR is enabled as CS_MODE_SYSFS, if the buffer size is changed
> and enabled again, currently sysfs_buf will point to the newly
> allocated memory(buf_new) and free the old memory(buf_old). But the
> etr_buf that is being used by the ETR remains pointed to buf_old, not
> updated to buf_new. In this case, it will result in a memory
> use-after-free issue.
>
> Fix this by checking ETR's mode before updating and releasing buf_old,
> if the mode is CS_MODE_SYSFS, then skip updating and releasing it.
>
missed fix tag:
Fixes: de5461970b3e ("coresight: tmc: allocating memory when needed")
Thanks,
Jie
> Signed-off-by: Xiaoqi Zhuang <xiaoqi.zhuang@oss.qualcomm.com>
> ---
> drivers/hwtracing/coresight/coresight-tmc-etr.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> index b07fcdb3fe1a..3e73cf2c38a3 100644
> --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
> +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> @@ -1268,6 +1268,13 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
> goto out;
> }
>
> + /*
> + * Since this sink is already enabled, the existing buffer should not
> + * be released even if the buffer size has changed.
> + */
> + if (coresight_get_mode(csdev) == CS_MODE_SYSFS)
> + goto out;
> +
> /*
> * If we don't have a buffer or it doesn't match the requested size,
> * use the buffer allocated above. Otherwise reuse the existing buffer.
>
> ---
> base-commit: 98ac9cc4b4452ed7e714eddc8c90ac4ae5da1a09
> change-id: 20251020-fix_etr_issue-02c706dbc899
>
> Best regards,
On Mon, Oct 20, 2025 at 05:06:46PM +0800, Xiaoqi Zhuang wrote:
> When ETR is enabled as CS_MODE_SYSFS, if the buffer size is changed
> and enabled again, currently sysfs_buf will point to the newly
> allocated memory(buf_new) and free the old memory(buf_old). But the
> etr_buf that is being used by the ETR remains pointed to buf_old, not
> updated to buf_new. In this case, it will result in a memory
> use-after-free issue.
I struggled to understand how to reproduce the issue under the condition
"if the buffer size is changed and enabled again."
I don't think the flow below where the trace is re-enabled would cause
an issue:
- Step 1: Enable trace path between ETM0 -> ETR0;
- Step 2: Change the buffer size for ETR0;
- Step 3: Disable trace path between ETM0 -> ETR0;
- Step 4: Enable again trace path between ETM0 -> ETR0.
In this case, step3 releases the buffer and update "drvdata->etr_buf" to
NULL, and step 4 allocates a new buffer and assign it to
"drvdata->etr_buf".
The problem should occur when operating on two trace paths, E.g.,
- Step 1: Enable trace path between ETM0 -> ETR0;
- Step 2: Change the buffer size for ETR0;
- Step 3: Enable trace path between ETM1 -> ETR0;
In step3, the driver releases the existed buffer and allocate a new one.
At the meantime, "drvdata->etr_buf" still holds the buffer allocated in
step 1.
> Fix this by checking ETR's mode before updating and releasing buf_old,
> if the mode is CS_MODE_SYSFS, then skip updating and releasing it.
Given that we now have a couple of reported issues related to ETR mode,
I'd like to refactor the ETR mode handling and its reference counting
thoroughly. I've drafted a large change (it's quite big, but we can
split it into small patches if we agree to proceed).
Thanks for reporting the issue!
Leo
---8<---
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
index b07fcdb3fe1a..d0fac958c614 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
@@ -1241,6 +1241,8 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
struct etr_buf *sysfs_buf = NULL, *new_buf = NULL, *free_buf = NULL;
+ WARN_ON(coresight_get_mode(csdev) != CS_MODE_SYSFS);
+
/*
* If we are enabling the ETR from disabled state, we need to make
* sure we have a buffer with the right size. The etr_buf is not reset
@@ -1263,7 +1265,7 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
raw_spin_lock_irqsave(&drvdata->spinlock, flags);
}
- if (drvdata->reading || coresight_get_mode(csdev) == CS_MODE_PERF) {
+ if (drvdata->reading) {
ret = -EBUSY;
goto out;
}
@@ -1292,30 +1294,14 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
int ret = 0;
unsigned long flags;
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
- struct etr_buf *sysfs_buf = tmc_etr_get_sysfs_buffer(csdev);
+ struct etr_buf *sysfs_buf;
+ sysfs_buf = tmc_etr_get_sysfs_buffer(csdev);
if (IS_ERR(sysfs_buf))
return PTR_ERR(sysfs_buf);
raw_spin_lock_irqsave(&drvdata->spinlock, flags);
-
- /*
- * In sysFS mode we can have multiple writers per sink. Since this
- * sink is already enabled no memory is needed and the HW need not be
- * touched, even if the buffer size has changed.
- */
- if (coresight_get_mode(csdev) == CS_MODE_SYSFS) {
- csdev->refcnt++;
- goto out;
- }
-
ret = tmc_etr_enable_hw(drvdata, sysfs_buf);
- if (!ret) {
- coresight_set_mode(csdev, CS_MODE_SYSFS);
- csdev->refcnt++;
- }
-
-out:
raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
if (!ret)
@@ -1735,11 +1721,6 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
struct etr_perf_buffer *etr_perf = etm_perf_sink_config(handle);
raw_spin_lock_irqsave(&drvdata->spinlock, flags);
- /* Don't use this sink if it is already claimed by sysFS */
- if (coresight_get_mode(csdev) == CS_MODE_SYSFS) {
- rc = -EBUSY;
- goto unlock_out;
- }
if (WARN_ON(!etr_perf || !etr_perf->etr_buf)) {
rc = -EINVAL;
@@ -1759,18 +1740,14 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
* No HW configuration is needed if the sink is already in
* use for this session.
*/
- if (drvdata->pid == pid) {
- csdev->refcnt++;
+ if (drvdata->pid == pid)
goto unlock_out;
- }
rc = tmc_etr_enable_hw(drvdata, etr_perf->etr_buf);
if (!rc) {
/* Associate with monitored process. */
drvdata->pid = pid;
- coresight_set_mode(csdev, CS_MODE_PERF);
drvdata->perf_buf = etr_perf->etr_buf;
- csdev->refcnt++;
}
unlock_out:
@@ -1778,17 +1755,76 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
return rc;
}
+static int tmc_acquire_mode(struct coresight_device *csdev, enum cs_mode mode)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+
+ if (mode != CS_MODE_SYSFS && mode != CS_MODE_PERF)
+ return -EINVAL;
+
+ scoped_guard(raw_spinlock_irqsave, &drvdata->spinlock);
+
+ if (coresight_get_mode(csdev) == CS_MODE_DISABLED) {
+ if (!csdev->refcnt)
+ coresight_set_mode(csdev, mode);
+ csdev->refcnt++;
+ } else if (coresight_get_mode(csdev) != mode) {
+ ret = -EBUSY;
+ }
+
+ return csdev->refcnt;
+}
+
+static void tmc_release_mode(struct coresight_device *csdev, enum cs_mode mode)
+{
+ struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+
+ scoped_guard(raw_spinlock_irqsave, &drvdata->spinlock);
+
+ if (WARN_ON(coresight_get_mode(csdev) != mode))
+ return;
+
+ csdev->refcnt--;
+ if (!csdev->refcnt)
+ coresight_set_mode(csdev, CS_MODE_DISABLED);
+}
+
static int tmc_enable_etr_sink(struct coresight_device *csdev,
enum cs_mode mode, void *data)
{
+ unsigned long flags;
+ struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+ int ret;
+
+ ret = tmc_acquire_mode(csdev, mode);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * For sysfs mode, the higher level mutex ensures exclusively
+ * enabling sink, it is safe to bail out if this is not the
+ * first time to enable sink.
+ *
+ * A perf session can enable the same sink simultaneously, fall
+ * through to call tmc_enable_etr_sink_perf() to ensure the sink
+ * has been enabled.
+ */
+ if (mode == CS_MODE_SYSFS && ret > 1)
+ return 0;
+
switch (mode) {
case CS_MODE_SYSFS:
- return tmc_enable_etr_sink_sysfs(csdev);
+ ret = tmc_enable_etr_sink_sysfs(csdev);
case CS_MODE_PERF:
- return tmc_enable_etr_sink_perf(csdev, data);
+ ret = tmc_enable_etr_sink_perf(csdev, data);
default:
- return -EINVAL;
+ ret = -EINVAL;
}
+
+ if (ret)
+ tmc_release_mode(csdev, mode);
+
+ return ret;
}
static int tmc_disable_etr_sink(struct coresight_device *csdev)
@@ -1796,30 +1832,20 @@ static int tmc_disable_etr_sink(struct coresight_device *csdev)
unsigned long flags;
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
- raw_spin_lock_irqsave(&drvdata->spinlock, flags);
+ tmc_release_mode(csdev, mode);
- if (drvdata->reading) {
- raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
- return -EBUSY;
- }
+ scoped_guard(raw_spinlock_irqsave, &drvdata->spinlock);
- csdev->refcnt--;
- if (csdev->refcnt) {
- raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
+ if (csdev->refcnt || drvdata->reading)
return -EBUSY;
- }
- /* Complain if we (somehow) got out of sync */
- WARN_ON_ONCE(coresight_get_mode(csdev) == CS_MODE_DISABLED);
+ if (drvdata->pid == -1)
+ return 0;
+
tmc_etr_disable_hw(drvdata);
- /* Dissociate from monitored process. */
- drvdata->pid = -1;
- coresight_set_mode(csdev, CS_MODE_DISABLED);
/* Reset perf specific data */
drvdata->perf_buf = NULL;
- raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
-
dev_dbg(&csdev->dev, "TMC-ETR disabled\n");
return 0;
}
On 2025/10/20 22:37, Leo Yan via CoreSight wrote:
> On Mon, Oct 20, 2025 at 05:06:46PM +0800, Xiaoqi Zhuang wrote:
>> When ETR is enabled as CS_MODE_SYSFS, if the buffer size is changed
>> and enabled again, currently sysfs_buf will point to the newly
>> allocated memory(buf_new) and free the old memory(buf_old). But the
>> etr_buf that is being used by the ETR remains pointed to buf_old, not
>> updated to buf_new. In this case, it will result in a memory
>> use-after-free issue.
> I struggled to understand how to reproduce the issue under the condition
> "if the buffer size is changed and enabled again."
>
> I don't think the flow below where the trace is re-enabled would cause
> an issue:
>
> - Step 1: Enable trace path between ETM0 -> ETR0;
> - Step 2: Change the buffer size for ETR0;
> - Step 3: Disable trace path between ETM0 -> ETR0;
> - Step 4: Enable again trace path between ETM0 -> ETR0.
>
> In this case, step3 releases the buffer and update "drvdata->etr_buf" to
> NULL, and step 4 allocates a new buffer and assign it to
> "drvdata->etr_buf".
>
> The problem should occur when operating on two trace paths, E.g.,
>
> - Step 1: Enable trace path between ETM0 -> ETR0;
> - Step 2: Change the buffer size for ETR0;
> - Step 3: Enable trace path between ETM1 -> ETR0;
>
> In step3, the driver releases the existed buffer and allocate a new one.
> At the meantime, "drvdata->etr_buf" still holds the buffer allocated in
> step 1.
>
>> Fix this by checking ETR's mode before updating and releasing buf_old,
>> if the mode is CS_MODE_SYSFS, then skip updating and releasing it.
> Given that we now have a couple of reported issues related to ETR mode,
> I'd like to refactor the ETR mode handling and its reference counting
> thoroughly. I've drafted a large change (it's quite big, but we can
> split it into small patches if we agree to proceed).
>
> Thanks for reporting the issue!
>
> Leo
>
> ---8<---
>
> diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> index b07fcdb3fe1a..d0fac958c614 100644
> --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
> +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> @@ -1241,6 +1241,8 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
> struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> struct etr_buf *sysfs_buf = NULL, *new_buf = NULL, *free_buf = NULL;
>
> + WARN_ON(coresight_get_mode(csdev) != CS_MODE_SYSFS);
> +
> /*
> * If we are enabling the ETR from disabled state, we need to make
> * sure we have a buffer with the right size. The etr_buf is not reset
> @@ -1263,7 +1265,7 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
> raw_spin_lock_irqsave(&drvdata->spinlock, flags);
> }
>
> - if (drvdata->reading || coresight_get_mode(csdev) == CS_MODE_PERF) {
> + if (drvdata->reading) {
> ret = -EBUSY;
> goto out;
> }
> @@ -1292,30 +1294,14 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
> int ret = 0;
> unsigned long flags;
> struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> - struct etr_buf *sysfs_buf = tmc_etr_get_sysfs_buffer(csdev);
> + struct etr_buf *sysfs_buf;
>
> + sysfs_buf = tmc_etr_get_sysfs_buffer(csdev);
> if (IS_ERR(sysfs_buf))
> return PTR_ERR(sysfs_buf);
>
> raw_spin_lock_irqsave(&drvdata->spinlock, flags);
> -
> - /*
> - * In sysFS mode we can have multiple writers per sink. Since this
> - * sink is already enabled no memory is needed and the HW need not be
> - * touched, even if the buffer size has changed.
> - */
> - if (coresight_get_mode(csdev) == CS_MODE_SYSFS) {
> - csdev->refcnt++;
> - goto out;
> - }
> -
> ret = tmc_etr_enable_hw(drvdata, sysfs_buf);
> - if (!ret) {
> - coresight_set_mode(csdev, CS_MODE_SYSFS);
> - csdev->refcnt++;
> - }
> -
> -out:
> raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
>
> if (!ret)
> @@ -1735,11 +1721,6 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
> struct etr_perf_buffer *etr_perf = etm_perf_sink_config(handle);
>
> raw_spin_lock_irqsave(&drvdata->spinlock, flags);
> - /* Don't use this sink if it is already claimed by sysFS */
> - if (coresight_get_mode(csdev) == CS_MODE_SYSFS) {
> - rc = -EBUSY;
> - goto unlock_out;
> - }
>
> if (WARN_ON(!etr_perf || !etr_perf->etr_buf)) {
> rc = -EINVAL;
> @@ -1759,18 +1740,14 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
> * No HW configuration is needed if the sink is already in
> * use for this session.
> */
> - if (drvdata->pid == pid) {
> - csdev->refcnt++;
> + if (drvdata->pid == pid)
> goto unlock_out;
> - }
>
> rc = tmc_etr_enable_hw(drvdata, etr_perf->etr_buf);
> if (!rc) {
> /* Associate with monitored process. */
> drvdata->pid = pid;
> - coresight_set_mode(csdev, CS_MODE_PERF);
> drvdata->perf_buf = etr_perf->etr_buf;
> - csdev->refcnt++;
> }
>
> unlock_out:
> @@ -1778,17 +1755,76 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
> return rc;
> }
>
> +static int tmc_acquire_mode(struct coresight_device *csdev, enum cs_mode mode)
> +{
> + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> +
> + if (mode != CS_MODE_SYSFS && mode != CS_MODE_PERF)
> + return -EINVAL;
> +
> + scoped_guard(raw_spinlock_irqsave, &drvdata->spinlock);
> +
> + if (coresight_get_mode(csdev) == CS_MODE_DISABLED) {
> + if (!csdev->refcnt)
> + coresight_set_mode(csdev, mode);
> + csdev->refcnt++;
Hi,Leo
Reference counting only increases above, and after ETR has been enabled,
no other
place was found where the reference count is incremented.
According to the previous review comment [1], the reference count should
only
be increased after ETR is enabled.
I've send the v3 [2] of the ETR mode refactoring, maybe could also take
a look at this, and provide some review~~
[1]:
https://lore.kernel.org/linux-arm-kernel/20250702164729.GA1039028@e132581.arm.com/
[2]:
https://lore.kernel.org/linux-arm-kernel/20250818080600.418425-3-hejunhao3@huawei.com/
Best regards,
Junhao.
> + } else if (coresight_get_mode(csdev) != mode) {
> + ret = -EBUSY;
> + }
> +
> + return csdev->refcnt;
> +}
> +
> +static void tmc_release_mode(struct coresight_device *csdev, enum cs_mode mode)
> +{
> + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> +
> + scoped_guard(raw_spinlock_irqsave, &drvdata->spinlock);
> +
> + if (WARN_ON(coresight_get_mode(csdev) != mode))
> + return;
> +
> + csdev->refcnt--;
> + if (!csdev->refcnt)
> + coresight_set_mode(csdev, CS_MODE_DISABLED);
> +}
> +
> static int tmc_enable_etr_sink(struct coresight_device *csdev,
> enum cs_mode mode, void *data)
> {
> + unsigned long flags;
> + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> + int ret;
> +
> + ret = tmc_acquire_mode(csdev, mode);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * For sysfs mode, the higher level mutex ensures exclusively
> + * enabling sink, it is safe to bail out if this is not the
> + * first time to enable sink.
> + *
> + * A perf session can enable the same sink simultaneously, fall
> + * through to call tmc_enable_etr_sink_perf() to ensure the sink
> + * has been enabled.
> + */
> + if (mode == CS_MODE_SYSFS && ret > 1)
> + return 0;
> +
> switch (mode) {
> case CS_MODE_SYSFS:
> - return tmc_enable_etr_sink_sysfs(csdev);
> + ret = tmc_enable_etr_sink_sysfs(csdev);
> case CS_MODE_PERF:
> - return tmc_enable_etr_sink_perf(csdev, data);
> + ret = tmc_enable_etr_sink_perf(csdev, data);
> default:
> - return -EINVAL;
> + ret = -EINVAL;
> }
> +
> + if (ret)
> + tmc_release_mode(csdev, mode);
> +
> + return ret;
> }
>
> static int tmc_disable_etr_sink(struct coresight_device *csdev)
> @@ -1796,30 +1832,20 @@ static int tmc_disable_etr_sink(struct coresight_device *csdev)
> unsigned long flags;
> struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
>
> - raw_spin_lock_irqsave(&drvdata->spinlock, flags);
> + tmc_release_mode(csdev, mode);
>
> - if (drvdata->reading) {
> - raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
> - return -EBUSY;
> - }
> + scoped_guard(raw_spinlock_irqsave, &drvdata->spinlock);
>
> - csdev->refcnt--;
> - if (csdev->refcnt) {
> - raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
> + if (csdev->refcnt || drvdata->reading)
> return -EBUSY;
> - }
>
> - /* Complain if we (somehow) got out of sync */
> - WARN_ON_ONCE(coresight_get_mode(csdev) == CS_MODE_DISABLED);
> + if (drvdata->pid == -1)
> + return 0;
> +
> tmc_etr_disable_hw(drvdata);
> - /* Dissociate from monitored process. */
> - drvdata->pid = -1;
> - coresight_set_mode(csdev, CS_MODE_DISABLED);
> /* Reset perf specific data */
> drvdata->perf_buf = NULL;
>
> - raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
> -
> dev_dbg(&csdev->dev, "TMC-ETR disabled\n");
> return 0;
> }
> _______________________________________________
> CoreSight mailing list -- coresight@lists.linaro.org
> To unsubscribe send an email to coresight-leave@lists.linaro.org
> .
>
On 10/20/2025 10:37 PM, Leo Yan wrote:
> On Mon, Oct 20, 2025 at 05:06:46PM +0800, Xiaoqi Zhuang wrote:
>> When ETR is enabled as CS_MODE_SYSFS, if the buffer size is changed
>> and enabled again, currently sysfs_buf will point to the newly
>> allocated memory(buf_new) and free the old memory(buf_old). But the
>> etr_buf that is being used by the ETR remains pointed to buf_old, not
>> updated to buf_new. In this case, it will result in a memory
>> use-after-free issue.
>
> I struggled to understand how to reproduce the issue under the condition
> "if the buffer size is changed and enabled again."
>
> I don't think the flow below where the trace is re-enabled would cause
> an issue:
>
> - Step 1: Enable trace path between ETM0 -> ETR0;
> - Step 2: Change the buffer size for ETR0;
> - Step 3: Disable trace path between ETM0 -> ETR0;
> - Step 4: Enable again trace path between ETM0 -> ETR0.
>
> In this case, step3 releases the buffer and update "drvdata->etr_buf" to
> NULL, and step 4 allocates a new buffer and assign it to
> "drvdata->etr_buf".
>
> The problem should occur when operating on two trace paths, E.g.,
>
> - Step 1: Enable trace path between ETM0 -> ETR0;
> - Step 2: Change the buffer size for ETR0;
> - Step 3: Enable trace path between ETM1 -> ETR0;
>
> In step3, the driver releases the existed buffer and allocate a new one.
> At the meantime, "drvdata->etr_buf" still holds the buffer allocated in
> step 1.
That's the scenario of the issue. The system will report a segmentation
error when the driver try to sync the freed etr_buf.
>
>> Fix this by checking ETR's mode before updating and releasing buf_old,
>> if the mode is CS_MODE_SYSFS, then skip updating and releasing it.
I agree. We should bail out as earlier as possible to gain some
performance efficiency.
>
> Given that we now have a couple of reported issues related to ETR mode,
> I'd like to refactor the ETR mode handling and its reference counting
> thoroughly. I've drafted a large change (it's quite big, but we can
> split it into small patches if we agree to proceed).
>
> Thanks for reporting the issue!
>
> Leo
>
> ---8<---
>
> diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> index b07fcdb3fe1a..d0fac958c614 100644
> --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
> +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> @@ -1241,6 +1241,8 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
> struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> struct etr_buf *sysfs_buf = NULL, *new_buf = NULL, *free_buf = NULL;
>
> + WARN_ON(coresight_get_mode(csdev) != CS_MODE_SYSFS);
I think we should check the WARN_ON result and exit if there is an error?
> +
> /*
> * If we are enabling the ETR from disabled state, we need to make
> * sure we have a buffer with the right size. The etr_buf is not reset
> @@ -1263,7 +1265,7 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
> raw_spin_lock_irqsave(&drvdata->spinlock, flags);
> }
>
> - if (drvdata->reading || coresight_get_mode(csdev) == CS_MODE_PERF) {
> + if (drvdata->reading) {
> ret = -EBUSY;
> goto out;
> }
> @@ -1292,30 +1294,14 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
> int ret = 0;
> unsigned long flags;
> struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> - struct etr_buf *sysfs_buf = tmc_etr_get_sysfs_buffer(csdev);
> + struct etr_buf *sysfs_buf;
>
> + sysfs_buf = tmc_etr_get_sysfs_buffer(csdev);
> if (IS_ERR(sysfs_buf))
> return PTR_ERR(sysfs_buf);
>
> raw_spin_lock_irqsave(&drvdata->spinlock, flags);
> -
> - /*
> - * In sysFS mode we can have multiple writers per sink. Since this
> - * sink is already enabled no memory is needed and the HW need not be
> - * touched, even if the buffer size has changed.
> - */
> - if (coresight_get_mode(csdev) == CS_MODE_SYSFS) {
> - csdev->refcnt++;
> - goto out;
> - }
> -
> ret = tmc_etr_enable_hw(drvdata, sysfs_buf);
> - if (!ret) {
> - coresight_set_mode(csdev, CS_MODE_SYSFS);
> - csdev->refcnt++;
> - }
> -
> -out:
> raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
>
> if (!ret)
> @@ -1735,11 +1721,6 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
> struct etr_perf_buffer *etr_perf = etm_perf_sink_config(handle);
>
> raw_spin_lock_irqsave(&drvdata->spinlock, flags);
> - /* Don't use this sink if it is already claimed by sysFS */
> - if (coresight_get_mode(csdev) == CS_MODE_SYSFS) {
> - rc = -EBUSY;
> - goto unlock_out;
> - }
>
> if (WARN_ON(!etr_perf || !etr_perf->etr_buf)) {
> rc = -EINVAL;
> @@ -1759,18 +1740,14 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
> * No HW configuration is needed if the sink is already in
> * use for this session.
> */
> - if (drvdata->pid == pid) {
> - csdev->refcnt++;
> + if (drvdata->pid == pid)
> goto unlock_out;
> - }
>
> rc = tmc_etr_enable_hw(drvdata, etr_perf->etr_buf);
> if (!rc) {
> /* Associate with monitored process. */
> drvdata->pid = pid;
> - coresight_set_mode(csdev, CS_MODE_PERF);
> drvdata->perf_buf = etr_perf->etr_buf;
> - csdev->refcnt++;
> }
>
> unlock_out:
> @@ -1778,17 +1755,76 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
> return rc;
> }
>
> +static int tmc_acquire_mode(struct coresight_device *csdev, enum cs_mode mode)
> +{
> + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> +
> + if (mode != CS_MODE_SYSFS && mode != CS_MODE_PERF)
> + return -EINVAL;
> +
> + scoped_guard(raw_spinlock_irqsave, &drvdata->spinlock);
> +
> + if (coresight_get_mode(csdev) == CS_MODE_DISABLED) {
> + if (!csdev->refcnt)
> + coresight_set_mode(csdev, mode);
> + csdev->refcnt++;
> + } else if (coresight_get_mode(csdev) != mode) {
> + ret = -EBUSY;
> + }
> +
> + return csdev->refcnt;
> +}
> +
> +static void tmc_release_mode(struct coresight_device *csdev, enum cs_mode mode)
> +{
> + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> +
> + scoped_guard(raw_spinlock_irqsave, &drvdata->spinlock);
> +
> + if (WARN_ON(coresight_get_mode(csdev) != mode))
> + return;
the mode here could be set to any CS_MODE, so I think it's possible to
encounter the secenario below:
coresight_get_mode(csdev) == CS_MODE_DISABLED, mode == CS_MODE_DISABLED,
With the condition, the csdev->refcnt will go to negative number?
> +
> + csdev->refcnt--;
> + if (!csdev->refcnt)
> + coresight_set_mode(csdev, CS_MODE_DISABLED);
> +}
> +
> static int tmc_enable_etr_sink(struct coresight_device *csdev,
> enum cs_mode mode, void *data)
> {
> + unsigned long flags;
> + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> + int ret;
> +
> + ret = tmc_acquire_mode(csdev, mode);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * For sysfs mode, the higher level mutex ensures exclusively
> + * enabling sink, it is safe to bail out if this is not the
> + * first time to enable sink.
> + *
> + * A perf session can enable the same sink simultaneously, fall
> + * through to call tmc_enable_etr_sink_perf() to ensure the sink
> + * has been enabled.
> + */
> + if (mode == CS_MODE_SYSFS && ret > 1)
> + return 0;
> +
> switch (mode) {
> case CS_MODE_SYSFS:
> - return tmc_enable_etr_sink_sysfs(csdev);
> + ret = tmc_enable_etr_sink_sysfs(csdev);
> case CS_MODE_PERF:
> - return tmc_enable_etr_sink_perf(csdev, data);
> + ret = tmc_enable_etr_sink_perf(csdev, data);
> default:
> - return -EINVAL;
> + ret = -EINVAL;
> }
> +
> + if (ret)
> + tmc_release_mode(csdev, mode);
> +
> + return ret;
> }
>
> static int tmc_disable_etr_sink(struct coresight_device *csdev)
> @@ -1796,30 +1832,20 @@ static int tmc_disable_etr_sink(struct coresight_device *csdev)
> unsigned long flags;
> struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
>
> - raw_spin_lock_irqsave(&drvdata->spinlock, flags);
> + tmc_release_mode(csdev, mode);
mode here is undefined?
Thanks,
Jie
>
> - if (drvdata->reading) {
> - raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
> - return -EBUSY;
> - }
> + scoped_guard(raw_spinlock_irqsave, &drvdata->spinlock);
>
> - csdev->refcnt--;
> - if (csdev->refcnt) {
> - raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
> + if (csdev->refcnt || drvdata->reading)
> return -EBUSY;
> - }
>
> - /* Complain if we (somehow) got out of sync */
> - WARN_ON_ONCE(coresight_get_mode(csdev) == CS_MODE_DISABLED);
> + if (drvdata->pid == -1)
> + return 0;
> +
> tmc_etr_disable_hw(drvdata);
> - /* Dissociate from monitored process. */
> - drvdata->pid = -1;
> - coresight_set_mode(csdev, CS_MODE_DISABLED);
> /* Reset perf specific data */
> drvdata->perf_buf = NULL;
>
> - raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
> -
> dev_dbg(&csdev->dev, "TMC-ETR disabled\n");
> return 0;
> }
>
On Tue, Oct 21, 2025 at 09:56:43AM +0800, Jie Gan wrote:
[...]
> > diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> > index b07fcdb3fe1a..d0fac958c614 100644
> > --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
> > +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> > @@ -1241,6 +1241,8 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
> > struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> > struct etr_buf *sysfs_buf = NULL, *new_buf = NULL, *free_buf = NULL;
> > + WARN_ON(coresight_get_mode(csdev) != CS_MODE_SYSFS);
>
> I think we should check the WARN_ON result and exit if there is an error?
When run at here, it should be in Sysfs mode. Here the check is for
debugging purpose in case any mismatch.
[...]
> > +static void tmc_release_mode(struct coresight_device *csdev, enum cs_mode mode)
> > +{
> > + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
> > +
> > + scoped_guard(raw_spinlock_irqsave, &drvdata->spinlock);
> > +
> > + if (WARN_ON(coresight_get_mode(csdev) != mode))
> > + return;
>
> the mode here could be set to any CS_MODE, so I think it's possible to
> encounter the secenario below:
>
> coresight_get_mode(csdev) == CS_MODE_DISABLED, mode == CS_MODE_DISABLED,
>
> With the condition, the csdev->refcnt will go to negative number?
The parameter "mode" might cause complexity, will drop it. The
correctness will be ensured by the callers.
Thanks for review!
Leo
On 10/20/2025 5:06 PM, Xiaoqi Zhuang wrote:
> When ETR is enabled as CS_MODE_SYSFS, if the buffer size is changed
> and enabled again, currently sysfs_buf will point to the newly
> allocated memory(buf_new) and free the old memory(buf_old). But the
> etr_buf that is being used by the ETR remains pointed to buf_old, not
> updated to buf_new. In this case, it will result in a memory
> use-after-free issue.
>
> Fix this by checking ETR's mode before updating and releasing buf_old,
> if the mode is CS_MODE_SYSFS, then skip updating and releasing it.
>
I think we need a fix tag for the fix patch:
Fixes: de5461970b3e ("coresight: tmc: allocating memory when needed")
minor comment:
Since ETR is enabled, we can't switch the sysfs buffer, can we exit
earlier by checking the CS_MODE to avoid allocating memory unnecessarily?
Besides,
Looks good to me.
Thanks,
Jie
> Signed-off-by: Xiaoqi Zhuang <xiaoqi.zhuang@oss.qualcomm.com>
> ---
> drivers/hwtracing/coresight/coresight-tmc-etr.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> index b07fcdb3fe1a..3e73cf2c38a3 100644
> --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
> +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> @@ -1268,6 +1268,13 @@ static struct etr_buf *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
> goto out;
> }
>
> + /*
> + * Since this sink is already enabled, the existing buffer should not
> + * be released even if the buffer size has changed.
> + */
> + if (coresight_get_mode(csdev) == CS_MODE_SYSFS)
> + goto out;
> +
> /*
> * If we don't have a buffer or it doesn't match the requested size,
> * use the buffer allocated above. Otherwise reuse the existing buffer.
>
> ---
> base-commit: 98ac9cc4b4452ed7e714eddc8c90ac4ae5da1a09
> change-id: 20251020-fix_etr_issue-02c706dbc899
>
> Best regards,
On 20/10/2025 11:18, Jie Gan wrote:
>
>
> On 10/20/2025 5:06 PM, Xiaoqi Zhuang wrote:
>> When ETR is enabled as CS_MODE_SYSFS, if the buffer size is changed
>> and enabled again, currently sysfs_buf will point to the newly
>> allocated memory(buf_new) and free the old memory(buf_old). But the
>> etr_buf that is being used by the ETR remains pointed to buf_old, not
>> updated to buf_new. In this case, it will result in a memory
>> use-after-free issue.
>>
>> Fix this by checking ETR's mode before updating and releasing buf_old,
>> if the mode is CS_MODE_SYSFS, then skip updating and releasing it.
>>
>
> I think we need a fix tag for the fix patch:
> Fixes: de5461970b3e ("coresight: tmc: allocating memory when needed")
>
> minor comment:
> Since ETR is enabled, we can't switch the sysfs buffer, can we exit
> earlier by checking the CS_MODE to avoid allocating memory unnecessarily?
+1
Something like this:
diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c
b/drivers/hwtracing/coresight/coresight-tmc-etr.c
index b07fcdb3fe1a..a9ddb05c10d9 100644
--- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
+++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
@@ -1252,6 +1252,12 @@ static struct etr_buf
*tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
raw_spin_lock_irqsave(&drvdata->spinlock, flags);
sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
if (!sysfs_buf || (sysfs_buf->size != drvdata->size)) {
+ /*
+ * If the ETR is already enabled, continue with the existing
+ * buffer.
+ */
+ if (coresight_get_mode(csdev) == CS_MODE_SYSFS)
+ goto out;
raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);
/* Allocate memory with the locks released */
Suzuki>
> Besides,
> Looks good to me.
>
> Thanks,
> Jie
>
>> Signed-off-by: Xiaoqi Zhuang <xiaoqi.zhuang@oss.qualcomm.com>
>> ---
>> drivers/hwtracing/coresight/coresight-tmc-etr.c | 7 +++++++
>> 1 file changed, 7 insertions(+)
>>
>> diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/
>> drivers/hwtracing/coresight/coresight-tmc-etr.c
>> index b07fcdb3fe1a..3e73cf2c38a3 100644
>> --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
>> +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
>> @@ -1268,6 +1268,13 @@ static struct etr_buf
>> *tmc_etr_get_sysfs_buffer(struct coresight_device *csdev)
>> goto out;
>> }
>> + /*
>> + * Since this sink is already enabled, the existing buffer should
>> not
>> + * be released even if the buffer size has changed.
>> + */
>> + if (coresight_get_mode(csdev) == CS_MODE_SYSFS)
>> + goto out;
>> +
>> /*
>> * If we don't have a buffer or it doesn't match the requested
>> size,
>> * use the buffer allocated above. Otherwise reuse the existing
>> buffer.
>>
>> ---
>> base-commit: 98ac9cc4b4452ed7e714eddc8c90ac4ae5da1a09
>> change-id: 20251020-fix_etr_issue-02c706dbc899
>>
>> Best regards,
>
© 2016 - 2026 Red Hat, Inc.