[PATCH] rtc: ac100: Fix clock provider use-after-free on probe failure
From: Aamir Ahmed <hidden>
Date: 2026-09-05 20:41:58
Also in:
linux-sunxi, lkml
Subsystem:
real time clock (rtc) subsystem, the rest · Maintainers:
Alexandre Belloni, Linus Torvalds
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: d00a18a42c14 ("rtc: ac100: Add RTC driver for X-Powers AC100")
Reported-by: Sashiko AI <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/linux-rtc/20260905184936.155E11F00A3A@smtp.kernel.org/ (local)
Assisted-by: LLM
Signed-off-by: Aamir Ahmed <redacted>
---
This applies on top of "[PATCH] rtc: ac100: Assign .num before accessing
.hws", posted to this list earlier today:
https://lore.kernel.org/linux-rtc/AS8P251MB00013E724A77A355668B6CCEC8B42@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM/ (local)
git format-patch emitted a prerequisite-patch-id for it below. I left
out the stable tag, both because the two live triggers are an order-0
allocation failure and a device tree that does not match the binding,
and because the merged fixes of this same shape carried a Fixes: tag
only: c7a639dac8e4 ("rtc: jz4740: Make sure clock provider gets
removed") and 9c48a5368504 ("rtc: pcf8563: fix clock provider leak on
unbind"). Happy to add it if you disagree.
On the choice of devm_of_clk_add_hw_provider(): it does not use
dev->of_node directly but goes through get_clk_provider_node(), which
substitutes the parent's node when the device's own node has no
#clock-cells. That substitution is inert here, since the binding
requires #clock-cells on the x-powers,ac100-rtc node and all six in-tree
boards set it, so the provider is still registered on the same node as
before.
The -EINVAL path is not reachable with any in-tree device tree: every
board gives the codec node clock-output-names and points the rtc node at
it, so of_clk_get_parent_name() always returns a name. It is fixed here
because the conversion covers it, not because I can trigger it.
The problem was pointed out by the Sashiko AI reviewer in reply to that
patch. I verified it against drivers/base/dd.c, drivers/clk/clk.c and
drivers/rtc/class.c before writing this.
chip->rtc_32k_clk is now only assigned and never read. Turning it into a
local and dropping the struct member would be a sensible follow-up, but
it is a separate cleanup so I left it out.
Compile-tested only (W=1, no warnings) on x86_64 with GCC 13.3, with
CONFIG_RTC_DRV_AC100=m forced on the make command line because the
driver has no COMPILE_TEST option. I do not have the hardware, so this
is not runtime-tested and neither error path was exercised. The fix and
this changelog were drafted with an LLM assistant and reviewed by hand.
drivers/rtc/rtc-ac100.c | 36 +++++++-----------------------------
1 file changed, 7 insertions(+), 29 deletions(-)
diff --git a/drivers/rtc/rtc-ac100.c b/drivers/rtc/rtc-ac100.c
index a2f465438fd..c579c4bf557 100644
--- a/drivers/rtc/rtc-ac100.c
+++ b/drivers/rtc/rtc-ac100.c@@ -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),
base-commit: 654ae5d73c05bd2943d65636ce6cd0aa46e62f18 prerequisite-patch-id: 1474421ba7fe25ac3d750fdfdda9bd4209a85869 -- 2.53.0.windows.1