From: Carlo Szelinsky <github@szelinsky.de>
To: Oleksij Rempel <o.rempel@pengutronix.de>,
Kory Maincent <kory.maincent@bootlin.com>,
Andrew Lunn <andrew+netdev@lunn.ch>,
"David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@kernel.org>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>,
Kyle Swenson <kyle.swenson@est.tech>, Ross Cawston <ross@r-sc.ca>,
netdev-bot+sashiko@kernel.org, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org,
Carlo Szelinsky <github@szelinsky.de>
Subject: [PATCH net v2 11/14] net: pse-pd: unwind allocations when controller registration fails
Date: Tue, 20 Oct 2026 19:12:46 +0200 [thread overview]
Message-ID: <20261020171249.853085-12-github@szelinsky.de> (raw)
In-Reply-To: <20261020171249.853085-1-github@szelinsky.de>
When pse_controller_register() fails, it does not free the
notification kfifo. Worse, if pse_register_pw_ds() fails part way, the
power domains it already set up stay in the global map. Their supply
handle belongs to a PI regulator that devres then frees, so the next
user of the map reads freed memory.
Free the kfifo on every error path, and let pse_register_pw_ds() undo
its own power domains while it still holds pse_pw_d_mutex, so no other
controller can pick one up first. The PI array is already freed by
devres.
Found by code inspection and AI review. Triggered in QEMU (arm64,
KASAN) with a test-only PSE controller and an injected failure in
pse_register_pw_ds() at the third PI: reading the regulator summary in
debugfs then gives
BUG: KASAN: slab-use-after-free in regulator_summary_show_subtree+0x770/0x9c4
With this patch the failure is undone, and kmemleak reports nothing.
Fixes: 50f8b341d268 ("net: pse-pd: Add support for PSE power domains")
Fixes: ffef61d6d273 ("net: pse-pd: Add support for budget evaluation strategies")
Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
---
drivers/net/pse-pd/pse_core.c | 73 +++++++++++++++++++++++++----------
1 file changed, 52 insertions(+), 21 deletions(-)
diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c
index 63217a4808e3..116ad2d1984a 100644
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -889,7 +889,8 @@ devm_pse_pi_regulator_register(struct pse_controller_dev *pcdev,
return 0;
}
-static void __pse_pw_d_release(struct kref *kref)
+/* Release for a put made with pse_pw_d_mutex already held. */
+static void pse_pw_d_release_locked(struct kref *kref)
{
struct pse_power_domain *pw_d = container_of(kref,
struct pse_power_domain,
@@ -897,7 +898,6 @@ static void __pse_pw_d_release(struct kref *kref)
regulator_put(pw_d->supply);
xa_erase(&pse_pw_d_map, pw_d->id);
- mutex_unlock(&pse_pw_d_mutex);
kfree(pw_d);
}
@@ -922,6 +922,30 @@ static void pse_pi_power_off(struct pse_controller_dev *pcdev, int id)
pse_pi_deallocate_pw_budget(&pcdev->pi[id]);
}
+static void __pse_pw_d_release(struct kref *kref)
+{
+ pse_pw_d_release_locked(kref);
+ mutex_unlock(&pse_pw_d_mutex);
+}
+
+/* Switch off the PI, give back its budget and clear pi[id].pw_d before
+ * the domain's reference is dropped. The PI regulator ops and pse_isr()
+ * hold pcdev->lock while they use it.
+ */
+static struct pse_power_domain *
+pse_pi_take_pw_d(struct pse_controller_dev *pcdev, int id)
+{
+ struct pse_power_domain *pw_d;
+
+ mutex_lock(&pcdev->lock);
+ pse_pi_power_off(pcdev, id);
+ pw_d = pcdev->pi[id].pw_d;
+ pcdev->pi[id].pw_d = NULL;
+ mutex_unlock(&pcdev->lock);
+
+ return pw_d;
+}
+
/**
* pse_flush_pw_ds - flush all PSE power domains of a PSE
* @pcdev: a pointer to the initialized PSE controller device
@@ -932,15 +956,7 @@ static void pse_flush_pw_ds(struct pse_controller_dev *pcdev)
int i;
for (i = 0; i < pcdev->nr_lines; i++) {
- /* Clear it before the put that may free the domain. The PI
- * regulator ops and pse_isr() hold pcdev->lock while they
- * use it.
- */
- mutex_lock(&pcdev->lock);
- pse_pi_power_off(pcdev, i);
- pw_d = pcdev->pi[i].pw_d;
- pcdev->pi[i].pw_d = NULL;
- mutex_unlock(&pcdev->lock);
+ pw_d = pse_pi_take_pw_d(pcdev, i);
if (!pw_d)
continue;
@@ -958,8 +974,8 @@ static void pse_flush_pw_ds(struct pse_controller_dev *pcdev)
*
* A power domain is shared by every controller whose PIs sit on the same
* supply, so it must not be tied to the lifetime of the controller that
- * happened to create it. It is freed by __pse_pw_d_release() on the last
- * kref_put().
+ * happened to create it. It is freed by pse_pw_d_release_locked() on the
+ * last kref_put().
*
* Return: Pointer to the newly allocated PSE power domain or error pointers
*/
@@ -1047,6 +1063,13 @@ static int pse_register_pw_ds(struct pse_controller_dev *pcdev)
}
out:
+ /* Undo this call's domains before another controller can find them */
+ while (ret && i--) {
+ struct pse_power_domain *pw_d = pse_pi_take_pw_d(pcdev, i);
+
+ if (pw_d)
+ kref_put(&pw_d->refcnt, pse_pw_d_release_locked);
+ }
mutex_unlock(&pse_pw_d_mutex);
return ret;
}
@@ -1120,24 +1143,25 @@ int pse_controller_register(struct pse_controller_dev *pcdev)
!pcdev->ops->pi_get_pw_status) {
dev_err(pcdev->dev,
"Mandatory status report callbacks are missing");
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_kfifo;
}
ret = of_load_pse_pis(pcdev);
if (ret)
- return ret;
+ goto free_kfifo;
/* The PI regulator ops use pcdev->pi until devres unregisters the
* regulators, so free it from devres too, after them.
*/
ret = devm_add_action_or_reset(pcdev->dev, devm_pse_release_pis, pcdev);
if (ret)
- return ret;
+ goto free_kfifo;
if (pcdev->ops->setup_pi_matrix) {
ret = pcdev->ops->setup_pi_matrix(pcdev);
if (ret)
- return ret;
+ goto free_kfifo;
}
/* Each regulator name len is pcdev dev name + 7 char +
@@ -1154,26 +1178,33 @@ int pse_controller_register(struct pse_controller_dev *pcdev)
continue;
reg_name = devm_kzalloc(pcdev->dev, reg_name_len, GFP_KERNEL);
- if (!reg_name)
- return -ENOMEM;
+ if (!reg_name) {
+ ret = -ENOMEM;
+ goto free_kfifo;
+ }
snprintf(reg_name, reg_name_len, "pse-%s_pi%d",
dev_name(pcdev->dev), i);
ret = devm_pse_pi_regulator_register(pcdev, reg_name, i);
if (ret)
- return ret;
+ goto free_kfifo;
}
ret = pse_register_pw_ds(pcdev);
if (ret)
- return ret;
+ goto free_kfifo;
mutex_lock(&pse_list_mutex);
list_add(&pcdev->list, &pse_controller_list);
mutex_unlock(&pse_list_mutex);
return 0;
+
+free_kfifo:
+ cancel_work_sync(&pcdev->ntf_work);
+ kfifo_free(&pcdev->ntf_fifo);
+ return ret;
}
EXPORT_SYMBOL_GPL(pse_controller_register);
--
2.43.0
next prev parent reply other threads:[~2026-10-20 17:13 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 17:12 [PATCH net v2 00/14] net: pse-pd: assorted lifetime and error-path fixes Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 01/14] net: pse-pd: size the notification kfifo after defaulting nr_lines Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 02/14] net: pse-pd: remove dead pi_get_admin_state() checks Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 03/14] net: pse-pd: fix module refcount leak in pse_control_get_internal() Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 04/14] net: pse-pd: do not keep a failed IRQ in pcdev->irq Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 05/14] net: pse-pd: do not reach a freed PHY from the notification worker Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 06/14] net: pse-pd: check the PI state under the lock when setting a power limit Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 07/14] net: pse-pd: do not tie a shared power domain to its creator's devres Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 08/14] net: pse-pd: stop async event sources before freeing PI data in unregister Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 09/14] net: pse-pd: unregister from the controller list before freeing PI data Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 10/14] net: pse-pd: keep the PI data until the PI regulators are gone Carlo Szelinsky
2026-10-20 17:12 ` Carlo Szelinsky [this message]
2026-10-20 17:12 ` [PATCH net v2 12/14] net: pse-pd: use each PI's own supply handle for the power budget Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 13/14] net: pse-pd: pd692x0: release the manager power budget through devres Carlo Szelinsky
2026-10-20 17:12 ` [PATCH net v2 14/14] net: pse-pd: pd692x0: defer probe while a manager supply is unresolved Carlo Szelinsky
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261020171249.853085-12-github@szelinsky.de \
--to=github@szelinsky.de \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=kory.maincent@bootlin.com \
--cc=kuba@kernel.org \
--cc=kyle.swenson@est.tech \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev-bot+sashiko@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=o.rempel@pengutronix.de \
--cc=pabeni@redhat.com \
--cc=ross@r-sc.ca \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.