[PATCH net v2 11/14] net: pse-pd: unwind allocations when controller registration fails
From: Carlo Szelinsky
Date: Tue Oct 20 2026 - 13:20:53 EST
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@xxxxxxxxxxxx>
---
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