pse_controller_unregister() frees resources that its own asynchronous
event sources are still using:
* The PI array (pcdev->pi) is freed by pse_release_pis() while the
threaded IRQ handler pse_isr() can still be running. pse_isr() walks
pcdev->pi[] (via pse_set_config_isr() and
regulator_notifier_call_chain() on pcdev->pi[i].rdev), so an interrupt
arriving before disable_irq() dereferences freed memory.
* pse_flush_pw_ds() runs before disable_irq() and drops the power domain
references, which can free pw_d->supply via __pse_pw_d_release().
A concurrent interrupt reaches that supply through
_pse_pi_disable() -> pse_pw_d_retry_power_delivery() ->
regulator_request_power_budget(pw_d->supply), another use-after-free.
* cancel_work_sync(&pcdev->ntf_work) runs after pse_release_pis(), but
the notification worker reaches pcdev->pi too: pse_send_ntf_worker()
-> pse_control_put() -> __pse_control_release() dereferences
psec->pcdev->pi[psec->id].admin_state_enabled. Draining the worker
after the PI array is freed is therefore also a use-after-free.
Reorder teardown so every asynchronous accessor is stopped first:
disable the IRQ, drain the notification worker, and only then flush the
power domains and release the PI array.
Fixes: ffef61d6d273 ("net: pse-pd: Add support for budget evaluation strategies")
Signed-off-by: Carlo Szelinsky <redacted>
---
drivers/net/pse-pd/pse_core.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c
index a5e6d7b26b9f..6045b6c399c2 100644
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -1114,11 +1114,15 @@ EXPORT_SYMBOL_GPL(pse_controller_register);
*/
void pse_controller_unregister(struct pse_controller_dev *pcdev)
{
- pse_flush_pw_ds(pcdev);
- pse_release_pis(pcdev);
+ /* Stop the IRQ and notification worker before freeing what they
+ * reach: both touch pcdev->pi, and the IRQ also uses pw_d->supply
+ * that pse_flush_pw_ds() drops.
+ */
if (pcdev->irq)
disable_irq(pcdev->irq);
cancel_work_sync(&pcdev->ntf_work);
+ pse_flush_pw_ds(pcdev);
+ pse_release_pis(pcdev);
kfifo_free(&pcdev->ntf_fifo);
mutex_lock(&pse_list_mutex);
list_del(&pcdev->list);--
2.43.0