[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <6e6c3034-221c-4e79-8971-7bfbe26f91a6@oss.qualcomm.com>
Date: Tue, 21 Oct 2025 09:56:43 +0800
From: Jie Gan <jie.gan@....qualcomm.com>
To: Leo Yan <leo.yan@....com>, Xiaoqi Zhuang <xiaoqi.zhuang@....qualcomm.com>
Cc: Suzuki K Poulose <suzuki.poulose@....com>,
Mike Leach <mike.leach@...aro.org>,
James Clark <james.clark@...aro.org>,
Alexander Shishkin <alexander.shishkin@...ux.intel.com>,
coresight@...ts.linaro.org, linux-arm-kernel@...ts.infradead.org,
linux-kernel@...r.kernel.org, linux-arm-msm@...r.kernel.org
Subject: Re: [PATCH] coresight: ETR: Fix ETR buffer use-after-free issue
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;
> }
>
Powered by blists - more mailing lists