mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git
synced 2026-10-28 12:53:14 +01:00
rtc: ac100: Fix clock provider use-after-free on probe failure
ac100_rtc_register_clks() registers the RTC-32k clock with
clk_hw_register_fixed_rate() and the clock provider with
of_clk_add_hw_provider(). Neither is device managed, and both are
released only from ac100_rtc_remove(). The driver core does not call
remove() when probe() fails: really_probe() reaches probe_failed below
the device_remove() call and goes straight to releasing the device's
managed resources.
ac100_rtc_probe() ends with
return devm_rtc_register_device(chip->rtc);
so if that fails after the clocks have been registered, the provider is
left on the global of_clk_providers list holding chip->clk_data, which
was allocated with devm_kzalloc() and is freed while probe() unwinds. A
later lookup on this device tree node then reads freed memory in
of_clk_hw_onecell_get(). Consumers that do exactly that exist in tree:
the wifi power sequence nodes on sun8i-a83t-bananapi-m3 and
sun8i-a83t-cubietruck-plus take <&ac100_rtc 1>, and the sun9i-a80 boards
route osc32k through <&ac100_rtc 0>. The window is narrow, as
devm_rtc_register_device() can only fail with -ENOMEM here, but the
provider is left dangling whenever it does.
The RTC-32k clock is leaked on the same path. Since clk_core_lookup()
searches a global list that is not scoped per device, __clk_register()
rejects the duplicate name with -EEXIST, so the leak also makes a later
probe of the same device fail.
The error path that returns -EINVAL when the ADDA 4M parent clock cannot
be found leaks the RTC-32k clock in the same way. No provider has been
registered at that point, so that one is a leak rather than a
use-after-free.
Register both with devm_clk_hw_register_fixed_rate() and
devm_of_clk_add_hw_provider() so that they are released whenever the
device goes away, on a failed probe as well as on unbind. Devres
releases in reverse order of acquisition, so the provider is removed
before chip->clk_data is freed, and the clkout children are now
unregistered before their parent rather than after it.
ac100_rtc_unregister_clks() and the remove callback then have nothing
left to do and are removed.
Fixes: d00a18a42c ("rtc: ac100: Add RTC driver for X-Powers AC100")
Reported-by: Sashiko AI <[email protected]>
Closes: https://lore.kernel.org/linux-rtc/[email protected]/
Assisted-by: LLM
Signed-off-by: Aamir Ahmed <[email protected]>
Link: https://patch.msgid.link/AS8P251MB0001A489CC45A591ABBB9BDCC8B42@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM
Signed-off-by: Alexandre Belloni <[email protected]>
This commit is contained in:
1 parent
6a4117eee8
commit
fcf0b58e97
1 file changed
+7
-29
+7
-29
@@ -319,10 +319,10 @@ static int ac100_rtc_register_clks(struct ac100_rtc_dev *chip)
|
||||
|
||||
chip->clk_data->num = AC100_CLKOUT_NUM;
|
||||
|
||||
chip->rtc_32k_clk = clk_hw_register_fixed_rate(chip->dev,
|
||||
AC100_RTC_32K_NAME,
|
||||
NULL, 0,
|
||||
AC100_RTC_32K_RATE);
|
||||
chip->rtc_32k_clk = devm_clk_hw_register_fixed_rate(chip->dev,
|
||||
AC100_RTC_32K_NAME,
|
||||
NULL, 0,
|
||||
AC100_RTC_32K_RATE);
|
||||
if (IS_ERR(chip->rtc_32k_clk)) {
|
||||
ret = PTR_ERR(chip->rtc_32k_clk);
|
||||
dev_err(chip->dev, "Failed to register RTC-32k clock: %d\n",
|
||||
@@ -356,28 +356,14 @@ static int ac100_rtc_register_clks(struct ac100_rtc_dev *chip)
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "Failed to register clk '%s': %d\n",
|
||||
init.name, ret);
|
||||
goto err_unregister_rtc_32k;
|
||||
return ret;
|
||||
}
|
||||
|
||||
chip->clk_data->hws[i] = &clk->hw;
|
||||
}
|
||||
|
||||
ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, chip->clk_data);
|
||||
if (ret)
|
||||
goto err_unregister_rtc_32k;
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_rtc_32k:
|
||||
clk_unregister_fixed_rate(chip->rtc_32k_clk->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ac100_rtc_unregister_clks(struct ac100_rtc_dev *chip)
|
||||
{
|
||||
of_clk_del_provider(chip->dev->of_node);
|
||||
clk_unregister_fixed_rate(chip->rtc_32k_clk->clk);
|
||||
return devm_of_clk_add_hw_provider(chip->dev, of_clk_hw_onecell_get,
|
||||
chip->clk_data);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -615,13 +601,6 @@ static int ac100_rtc_probe(struct platform_device *pdev)
|
||||
return devm_rtc_register_device(chip->rtc);
|
||||
}
|
||||
|
||||
static void ac100_rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ac100_rtc_dev *chip = platform_get_drvdata(pdev);
|
||||
|
||||
ac100_rtc_unregister_clks(chip);
|
||||
}
|
||||
|
||||
static const struct of_device_id ac100_rtc_match[] = {
|
||||
{ .compatible = "x-powers,ac100-rtc" },
|
||||
{ },
|
||||
@@ -630,7 +609,6 @@ MODULE_DEVICE_TABLE(of, ac100_rtc_match);
|
||||
|
||||
static struct platform_driver ac100_rtc_driver = {
|
||||
.probe = ac100_rtc_probe,
|
||||
.remove = ac100_rtc_remove,
|
||||
.driver = {
|
||||
.name = "ac100-rtc",
|
||||
.of_match_table = of_match_ptr(ac100_rtc_match),
|
||||
|
||||
Reference in new issue
Block a user