diff mbox series

[v3,36/36] mmc: meson-mx-sdio: Use devm_mmc_alloc_host() helper

Message ID 68e752931552e95667191c2641076a1bfaea3dc6.1748933789.git.zhoubinbin@loongson.cn
State New
Headers show
Series [v3,01/36] mmc: alcor: Use devm_mmc_alloc_host() helper | expand

Commit Message

Binbin Zhou June 3, 2025, 12:28 p.m. UTC
Use new function devm_mmc_alloc_host() to simplify the code.

Cc: Neil Armstrong <neil.armstrong@linaro.org>
Cc: Kevin Hilman <khilman@baylibre.com>
Cc: Jerome Brunet <jbrunet@baylibre.com>
Cc: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Cc: linux-amlogic@lists.infradead.org
Reviewed-by: Huacai Chen <chenhuacai@loongson.cn>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 drivers/mmc/host/meson-mx-sdio.c | 20 ++++++++------------
 1 file changed, 8 insertions(+), 12 deletions(-)

Comments

Martin Blumenstingl June 3, 2025, 8:49 p.m. UTC | #1
Hello,

On Tue, Jun 3, 2025 at 2:28 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
>
> Use new function devm_mmc_alloc_host() to simplify the code.
>
> Cc: Neil Armstrong <neil.armstrong@linaro.org>
> Cc: Kevin Hilman <khilman@baylibre.com>
> Cc: Jerome Brunet <jbrunet@baylibre.com>
> Cc: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
> Cc: linux-amlogic@lists.infradead.org
> Reviewed-by: Huacai Chen <chenhuacai@loongson.cn>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
>  drivers/mmc/host/meson-mx-sdio.c | 20 ++++++++------------
>  1 file changed, 8 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/mmc/host/meson-mx-sdio.c b/drivers/mmc/host/meson-mx-sdio.c
> index e0ae5a0c9670..b6cb475f1a5f 100644
> --- a/drivers/mmc/host/meson-mx-sdio.c
> +++ b/drivers/mmc/host/meson-mx-sdio.c
> @@ -640,7 +640,7 @@ static int meson_mx_mmc_probe(struct platform_device *pdev)
>         else if (IS_ERR(slot_pdev))
>                 return PTR_ERR(slot_pdev);
>
> -       mmc = mmc_alloc_host(sizeof(*host), &slot_pdev->dev);
> +       mmc = devm_mmc_alloc_host(&slot_pdev->dev, sizeof(*host));
This change is fine at runtime (on my Odroid-C1 board) but it can lead
to a use-after-free issue.
meson_mx_mmc_register_clks() devm_ registers two clocks and uses
host->controller_dev as device.
This leads to the fact that during driver removal struct
meson_mx_mmc_host is free'd before host->controller_dev, which means
the clocks are also free'd after struct meson_mx_mmc_host is already
gone. The problem here: the clocks need the struct clk_divider and
struct clk_fixed_factor from struct meson_mx_mmc_host.

I don't understand why I'm not seeing any problems with this patch at
runtime - maybe what I'm describing is just a theoretical issue
(because it would only it if something would access the clocks between
freeing struct meson_mx_mmc_host and removal of the clocks a few
milliseconds later).

What are your thoughts on this?
If we're concerned about the potential UAF then I can refactor
meson_mx_mmc_register_clks() first and then apply this patch
afterwards.


Best regards,
Martin
Binbin Zhou June 4, 2025, 3:02 a.m. UTC | #2
Hi Martin:

Thanks for your testing and reply.

On Wed, Jun 4, 2025 at 4:49 AM Martin Blumenstingl
<martin.blumenstingl@googlemail.com> wrote:
>
> Hello,
>
> On Tue, Jun 3, 2025 at 2:28 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
> >
> > Use new function devm_mmc_alloc_host() to simplify the code.
> >
> > Cc: Neil Armstrong <neil.armstrong@linaro.org>
> > Cc: Kevin Hilman <khilman@baylibre.com>
> > Cc: Jerome Brunet <jbrunet@baylibre.com>
> > Cc: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
> > Cc: linux-amlogic@lists.infradead.org
> > Reviewed-by: Huacai Chen <chenhuacai@loongson.cn>
> > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > ---
> >  drivers/mmc/host/meson-mx-sdio.c | 20 ++++++++------------
> >  1 file changed, 8 insertions(+), 12 deletions(-)
> >
> > diff --git a/drivers/mmc/host/meson-mx-sdio.c b/drivers/mmc/host/meson-mx-sdio.c
> > index e0ae5a0c9670..b6cb475f1a5f 100644
> > --- a/drivers/mmc/host/meson-mx-sdio.c
> > +++ b/drivers/mmc/host/meson-mx-sdio.c
> > @@ -640,7 +640,7 @@ static int meson_mx_mmc_probe(struct platform_device *pdev)
> >         else if (IS_ERR(slot_pdev))
> >                 return PTR_ERR(slot_pdev);
> >
> > -       mmc = mmc_alloc_host(sizeof(*host), &slot_pdev->dev);
> > +       mmc = devm_mmc_alloc_host(&slot_pdev->dev, sizeof(*host));
> This change is fine at runtime (on my Odroid-C1 board) but it can lead
> to a use-after-free issue.
> meson_mx_mmc_register_clks() devm_ registers two clocks and uses
> host->controller_dev as device.
> This leads to the fact that during driver removal struct
> meson_mx_mmc_host is free'd before host->controller_dev, which means
> the clocks are also free'd after struct meson_mx_mmc_host is already
> gone. The problem here: the clocks need the struct clk_divider and
> struct clk_fixed_factor from struct meson_mx_mmc_host.

Sorry, I have a slightly different opinion, which may not necessarily
be correct.

meson_mx_mmc_host is kzalloc as `extra` in mmc_alloc_host()[1], but in
fact, the entire host is released in mmc_host_classdev_release()[2],
so I don't think it will affect the use of host->controller_dev.

[1]: https://elixir.bootlin.com/linux/v6.15-rc1/source/drivers/mmc/core/host.c#L523
[2]: https://elixir.bootlin.com/linux/v6.15-rc1/source/drivers/mmc/core/host.c#L78
>
> I don't understand why I'm not seeing any problems with this patch at
> runtime - maybe what I'm describing is just a theoretical issue
> (because it would only it if something would access the clocks between
> freeing struct meson_mx_mmc_host and removal of the clocks a few
> milliseconds later).
>
> What are your thoughts on this?
> If we're concerned about the potential UAF then I can refactor
> meson_mx_mmc_register_clks() first and then apply this patch
> afterwards.
>
>
> Best regards,
> Martin
Martin Blumenstingl June 4, 2025, 7:37 p.m. UTC | #3
Hi Binbin,

On Wed, Jun 4, 2025 at 5:02 AM Binbin Zhou <zhoubb.aaron@gmail.com> wrote:
[...]
> > > -       mmc = mmc_alloc_host(sizeof(*host), &slot_pdev->dev);
> > > +       mmc = devm_mmc_alloc_host(&slot_pdev->dev, sizeof(*host));
> > This change is fine at runtime (on my Odroid-C1 board) but it can lead
> > to a use-after-free issue.
> > meson_mx_mmc_register_clks() devm_ registers two clocks and uses
> > host->controller_dev as device.
> > This leads to the fact that during driver removal struct
> > meson_mx_mmc_host is free'd before host->controller_dev, which means
> > the clocks are also free'd after struct meson_mx_mmc_host is already
> > gone. The problem here: the clocks need the struct clk_divider and
> > struct clk_fixed_factor from struct meson_mx_mmc_host.
>
> Sorry, I have a slightly different opinion, which may not necessarily
> be correct.
>
> meson_mx_mmc_host is kzalloc as `extra` in mmc_alloc_host()[1], but in
> fact, the entire host is released in mmc_host_classdev_release()[2],
> so I don't think it will affect the use of host->controller_dev.
It's well hidden, so it's tricky to spot: we have two "struct device"
in the meson-mx-sdio driver:
1. the "controller_dev" which is belongs to the struct platform_device
of the driver/device-tree node [0]
2. a second struct platform_device and thus a second struct device
whose of_node belongs to the "slot" (which is a child node of the
controller) [1]

Clock controller initialization is the same for all slots, so I chose
to link it with the controller_device.
We want to parse the mmc slot properties (bus-width, cd-gpios, cap-*,
...) from the slot node in device-tree, so we create a second
platform_device for the slot (child) node. This design was chosen
because the controller supports multiple (up to 3) slots while the mmc
core in Linux doesn't. However, it was important for me to at least be
prepared in terms of device-tree bindings in case Linux would ever
support controllers with multiple slots.

So the lifecycle during probe is:
a) controller_device is instantiated by device-tree node with
compatible = "amlogic,meson8-sdio"
b) slot device is instantiated using the "mmc-slot" compatible and
using controller_device as parent device
c) devm_mmc_alloc_host() is called
d) clock providers are devm_* registered and attached to controller_device
e) the meson-mx-sdio driver acts as clock consumer for the clock it's providing

However, if we want to remove the driver the order is:
- remove the device attached to the "mmc-slot" compatible - before
that happens struct mmc is freed (as it's parent is being removed)
which also frees struct meson_mx_mmc_host
- (struct clk_divider and struct clk_fixed_factor are linked with the
clocks which are still registered <- here we have a problem because
those two structs are part of struct meson_mx_mmc_host which has
already been freed)
- controller_device is freed - before that happens the provided clocks
are freed (as their parent is being removed)

I take your questions as feedback on how to make the code easier to
read/understand.
This is very welcome, because it's been 8 years since I introduced the
driver and I also have to think twice about some of its details.

Let me know if things are more clear now - or if you still have any
doubts/questions.


Best regards,
Martin


[0] https://elixir.bootlin.com/linux/v6.15-rc1/source/Documentation/devicetree/bindings/mmc/amlogic,meson-mx-sdio.yaml#L83
[1] https://elixir.bootlin.com/linux/v6.15-rc1/source/Documentation/devicetree/bindings/mmc/amlogic,meson-mx-sdio.yaml#L92
diff mbox series

Patch

diff --git a/drivers/mmc/host/meson-mx-sdio.c b/drivers/mmc/host/meson-mx-sdio.c
index e0ae5a0c9670..b6cb475f1a5f 100644
--- a/drivers/mmc/host/meson-mx-sdio.c
+++ b/drivers/mmc/host/meson-mx-sdio.c
@@ -640,7 +640,7 @@  static int meson_mx_mmc_probe(struct platform_device *pdev)
 	else if (IS_ERR(slot_pdev))
 		return PTR_ERR(slot_pdev);
 
-	mmc = mmc_alloc_host(sizeof(*host), &slot_pdev->dev);
+	mmc = devm_mmc_alloc_host(&slot_pdev->dev, sizeof(*host));
 	if (!mmc) {
 		ret = -ENOMEM;
 		goto error_unregister_slot_pdev;
@@ -658,13 +658,13 @@  static int meson_mx_mmc_probe(struct platform_device *pdev)
 	host->base = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(host->base)) {
 		ret = PTR_ERR(host->base);
-		goto error_free_mmc;
+		goto error_unregister_slot_pdev;
 	}
 
 	irq = platform_get_irq(pdev, 0);
 	if (irq < 0) {
 		ret = irq;
-		goto error_free_mmc;
+		goto error_unregister_slot_pdev;
 	}
 
 	ret = devm_request_threaded_irq(host->controller_dev, irq,
@@ -672,28 +672,28 @@  static int meson_mx_mmc_probe(struct platform_device *pdev)
 					meson_mx_mmc_irq_thread, IRQF_ONESHOT,
 					NULL, host);
 	if (ret)
-		goto error_free_mmc;
+		goto error_unregister_slot_pdev;
 
 	host->core_clk = devm_clk_get(host->controller_dev, "core");
 	if (IS_ERR(host->core_clk)) {
 		ret = PTR_ERR(host->core_clk);
-		goto error_free_mmc;
+		goto error_unregister_slot_pdev;
 	}
 
 	host->parent_clk = devm_clk_get(host->controller_dev, "clkin");
 	if (IS_ERR(host->parent_clk)) {
 		ret = PTR_ERR(host->parent_clk);
-		goto error_free_mmc;
+		goto error_unregister_slot_pdev;
 	}
 
 	ret = meson_mx_mmc_register_clks(host);
 	if (ret)
-		goto error_free_mmc;
+		goto error_unregister_slot_pdev;
 
 	ret = clk_prepare_enable(host->core_clk);
 	if (ret) {
 		dev_err(host->controller_dev, "Failed to enable core clock\n");
-		goto error_free_mmc;
+		goto error_unregister_slot_pdev;
 	}
 
 	ret = clk_prepare_enable(host->cfg_div_clk);
@@ -721,8 +721,6 @@  static int meson_mx_mmc_probe(struct platform_device *pdev)
 	clk_disable_unprepare(host->cfg_div_clk);
 error_disable_core_clk:
 	clk_disable_unprepare(host->core_clk);
-error_free_mmc:
-	mmc_free_host(mmc);
 error_unregister_slot_pdev:
 	of_platform_device_destroy(&slot_pdev->dev, NULL);
 	return ret;
@@ -741,8 +739,6 @@  static void meson_mx_mmc_remove(struct platform_device *pdev)
 
 	clk_disable_unprepare(host->cfg_div_clk);
 	clk_disable_unprepare(host->core_clk);
-
-	mmc_free_host(host->mmc);
 }
 
 static const struct of_device_id meson_mx_mmc_of_match[] = {