diff mbox series

[v1,2/3] cpufreq: mediatek: fix KP caused by handler usage after regulator_put/clk_put

Message ID 20230310051750.4745-3-jia-wei.chang@mediatek.com (mailing list archive)
State New, archived
Headers show
Series Enhance mediatek-cpufreq robustness | expand

Commit Message

Jia-wei Chang (張佳偉) March 10, 2023, 5:17 a.m. UTC
From: "Jia-Wei Chang" <jia-wei.chang@mediatek.com>

Any kind of failure in mtk_cpu_dvfs_info_init() will lead to calling
regulator_put() or clk_put() and the KP will occur since the regulator/clk
handlers are used after released in mtk_cpu_dvfs_info_release().

To prevent the usage after regulator_put()/clk_put(), the regulator/clk
handlers are reassigned to NULL value for validation check afterwards.

Signed-off-by: Jia-Wei Chang <jia-wei.chang@mediatek.com>
Fixes: 4b9ceb757bbb ("cpufreq: mediatek: Enable clocks and regulators")
Reported-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Link: https://lore.kernel.org/linux-arm-kernel/20220921071913.p7kwsjnnuad2jgvk@vireshk-i7/T/
---
 drivers/cpufreq/mediatek-cpufreq.c | 28 ++++++++++++++++++++--------
 1 file changed, 20 insertions(+), 8 deletions(-)

Comments

Dan Carpenter March 10, 2023, 9:11 a.m. UTC | #1
On Fri, Mar 10, 2023 at 01:17:49PM +0800, jia-wei.chang wrote:
> From: "Jia-Wei Chang" <jia-wei.chang@mediatek.com>
> 
> Any kind of failure in mtk_cpu_dvfs_info_init() will lead to calling
> regulator_put() or clk_put() and the KP will occur since the regulator/clk
> handlers are used after released in mtk_cpu_dvfs_info_release().
> 

This patch is harmless but it's not required.

If the mtk_cpu_dvfs_info_init() function is not able to complete
successfully then it cleans up all the partial allocations.  If it is
able to allocate everything successfully, then the caller, which is
mtk_cpufreq_probe(), adds it to the &dvfs_info_list.  If the
probe() function is not able to complete successfully it releases
everything in the &dvfs_info_list.

In this way, mtk_cpu_dvfs_info_init() never frees anything which has
not been allocated and it never frees anything which has already been
freed.  All the IS_ERR() checks in mtk_cpu_dvfs_info_release() can be
removed.

In reviewing this code, I can't help but notice that mtk_cpu_dvfs_info_init()
is buggy.  Please read my blog on error handling:
https://staticthinking.wordpress.com/2022/04/28/free-the-last-thing-style/

drivers/cpufreq/mediatek-cpufreq.c
   411          info->cpu_clk = clk_get(cpu_dev, "cpu");
   412          if (IS_ERR(info->cpu_clk)) {
   413                  ret = PTR_ERR(info->cpu_clk);
   414                  return dev_err_probe(cpu_dev, ret,
                        ^^^^^^^^^^^^^^^^^^^^
Here we have a direct return which means there is nothing to free.
Okay.

   415                                       "cpu%d: failed to get cpu clk\n", cpu);
   416          }
   417  
   418          info->inter_clk = clk_get(cpu_dev, "intermediate");
   419          if (IS_ERR(info->inter_clk)) {
   420                  ret = PTR_ERR(info->inter_clk);
   421                  dev_err_probe(cpu_dev, ret,
   422                                "cpu%d: failed to get intermediate clk\n", cpu);
   423                  goto out_free_resources;
                        ^^^^^^^^^^^^^^^^^^^^^^^
The last thing we successfully allocated was ->cpu_clk so ideally the
goto would be something like "goto put_cpu_clk;".  Let's assume this
clk_get() fails.


   424          }
   425  
   426          info->proc_reg = regulator_get_optional(cpu_dev, "proc");
                ^^^^^^^^^^^^^^
Note that this is where ->proc_reg is allocated.  Prior to that point
it is NULL because it's allocated with kzalloc().

   427          if (IS_ERR(info->proc_reg)) {
   428                  ret = PTR_ERR(info->proc_reg);
   429                  dev_err_probe(cpu_dev, ret,
   430                                "cpu%d: failed to get proc regulator\n", cpu);
   431                  goto out_free_resources;
   432          }

[ snip ]

   536  out_free_resources:
   537          if (regulator_is_enabled(info->proc_reg))
                                         ^^^^^^^^^^^^^^
Oops!  This is a NULL dereference because ->proc_reg wasn't allocated
yet.

   538                  regulator_disable(info->proc_reg);
   539          if (info->sram_reg && regulator_is_enabled(info->sram_reg))
   540                  regulator_disable(info->sram_reg);
   541  
   542          if (!IS_ERR(info->proc_reg))
   543                  regulator_put(info->proc_reg);
   544          if (!IS_ERR(info->sram_reg))
   545                  regulator_put(info->sram_reg);
   546          if (!IS_ERR(info->cpu_clk))
   547                  clk_put(info->cpu_clk);
   548          if (!IS_ERR(info->inter_clk))
   549                  clk_put(info->inter_clk);
   550  
   551          return ret;
   552  }

It would be safer and more readable to have:

err_put_inter_clk:
	clk_put(info->inter_clk);
err_put_cpu_clk:
	clk_put(info->cpu_clk);

etc.

regards,
dan carpenter
AngeloGioacchino Del Regno March 10, 2023, 9:30 a.m. UTC | #2
Il 10/03/23 06:17, jia-wei.chang ha scritto:
> From: "Jia-Wei Chang" <jia-wei.chang@mediatek.com>
> 
> Any kind of failure in mtk_cpu_dvfs_info_init() will lead to calling
> regulator_put() or clk_put() and the KP will occur since the regulator/clk
> handlers are used after released in mtk_cpu_dvfs_info_release().
> 
> To prevent the usage after regulator_put()/clk_put(), the regulator/clk
> handlers are reassigned to NULL value for validation check afterwards.
> 
> Signed-off-by: Jia-Wei Chang <jia-wei.chang@mediatek.com>
> Fixes: 4b9ceb757bbb ("cpufreq: mediatek: Enable clocks and regulators")
> Reported-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
> Link: https://lore.kernel.org/linux-arm-kernel/20220921071913.p7kwsjnnuad2jgvk@vireshk-i7/T/

Actually, it's not just Reported-by, but also please add:
Suggested-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>


Anyway, apart from that:
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
diff mbox series

Patch

diff --git a/drivers/cpufreq/mediatek-cpufreq.c b/drivers/cpufreq/mediatek-cpufreq.c
index 01d949707c37..cb8b76f9c2c3 100644
--- a/drivers/cpufreq/mediatek-cpufreq.c
+++ b/drivers/cpufreq/mediatek-cpufreq.c
@@ -539,35 +539,47 @@  static int mtk_cpu_dvfs_info_init(struct mtk_cpu_dvfs_info *info, int cpu)
 	if (info->sram_reg && regulator_is_enabled(info->sram_reg))
 		regulator_disable(info->sram_reg);
 
-	if (!IS_ERR(info->proc_reg))
+	if (!IS_ERR(info->proc_reg)) {
 		regulator_put(info->proc_reg);
-	if (!IS_ERR(info->sram_reg))
+		info->proc_reg = NULL;
+	}
+	if (!IS_ERR(info->sram_reg)) {
 		regulator_put(info->sram_reg);
-	if (!IS_ERR(info->cpu_clk))
+		info->sram_reg = NULL;
+	}
+	if (!IS_ERR(info->cpu_clk)) {
 		clk_put(info->cpu_clk);
-	if (!IS_ERR(info->inter_clk))
+		info->cpu_clk = NULL;
+	}
+	if (!IS_ERR(info->inter_clk)) {
 		clk_put(info->inter_clk);
+		info->inter_clk = NULL;
+	}
 
 	return ret;
 }
 
 static void mtk_cpu_dvfs_info_release(struct mtk_cpu_dvfs_info *info)
 {
-	if (!IS_ERR(info->proc_reg)) {
+	if (!IS_ERR_OR_NULL(info->proc_reg)) {
 		regulator_disable(info->proc_reg);
 		regulator_put(info->proc_reg);
+		info->proc_reg = NULL;
 	}
-	if (!IS_ERR(info->sram_reg)) {
+	if (!IS_ERR_OR_NULL(info->sram_reg)) {
 		regulator_disable(info->sram_reg);
 		regulator_put(info->sram_reg);
+		info->sram_reg = NULL;
 	}
-	if (!IS_ERR(info->cpu_clk)) {
+	if (!IS_ERR_OR_NULL(info->cpu_clk)) {
 		clk_disable_unprepare(info->cpu_clk);
 		clk_put(info->cpu_clk);
+		info->cpu_clk = NULL;
 	}
-	if (!IS_ERR(info->inter_clk)) {
+	if (!IS_ERR_OR_NULL(info->inter_clk)) {
 		clk_disable_unprepare(info->inter_clk);
 		clk_put(info->inter_clk);
+		info->inter_clk = NULL;
 	}
 
 	dev_pm_opp_of_cpumask_remove_table(&info->cpus);