kernel: pse-pd: backport net fixes for unregister/isr

pse_isr() 32-bit OOB bitmap access, disable IRQ before freeing PI data
on unregister, and guard against freed PI data on regulator disable.

Pending upstream in the PSE-core teardown UAF fix series
[PATCH net v2 0/2] (in review; merge expected soon).
Link: https://lore.kernel.org/all/20260711121611.1639086-1-github@szelinsky.de/

Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
Link: https://github.com/openwrt/openwrt/pull/22245
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
This commit is contained in:
Carlo Szelinsky
2026-07-21 18:59:25 +02:00
committed by Jonas Jelonek
parent 0f256a0a7a
commit d060557066
5 changed files with 181 additions and 4 deletions
@@ -0,0 +1,80 @@
From 5099807f335ce4f783f0578bef7278fffad30b07 Mon Sep 17 00:00:00 2001
From: Kory Maincent <kory.maincent@bootlin.com>
Date: Wed, 15 Apr 2026 15:02:59 +0200
Subject: [PATCH] net: pse-pd: fix out-of-bounds bitmap access in pse_isr() on
32-bit
In pse_isr(), notifs_mask was declared as a single unsigned long on the
stack (32 bits on 32-bit architectures). For PSE controllers with more
than 32 ports, this causes two problems:
- map_event callbacks could wrote bit positions >= 32 via
*notifs_mask |= BIT(i), which is undefined behaviour on a 32-bit
unsigned long and corrupts adjacent stack memory.
- for_each_set_bit(i, &notifs_mask, pcdev->nr_lines) treats
&notifs_mask as a multi-word bitmap and reads beyond the single
unsigned long when nr_lines > BITS_PER_LONG.
Fix this by moving notifs_mask out of the stack and into struct pse_irq
as a dynamically allocated bitmap. It is sized with
BITS_TO_LONGS(pcdev->nr_lines) words in devm_pse_irq_helper(), so it
is always wide enough regardless of the host word size.
[Jakub]: No upstream driver currently supports >=32 ports.
Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
Link: https://patch.msgid.link/20260415130300.806152-1-kory.maincent@bootlin.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/pse-pd/pse_core.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -1170,6 +1170,7 @@ struct pse_irq {
struct pse_controller_dev *pcdev;
struct pse_irq_desc desc;
unsigned long *notifs;
+ unsigned long *notifs_mask;
};
/**
@@ -1247,7 +1248,6 @@ static int pse_set_config_isr(struct pse
static irqreturn_t pse_isr(int irq, void *data)
{
struct pse_controller_dev *pcdev;
- unsigned long notifs_mask = 0;
struct pse_irq_desc *desc;
struct pse_irq *h = data;
int ret, i;
@@ -1257,14 +1257,15 @@ static irqreturn_t pse_isr(int irq, void
/* Clear notifs mask */
memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs));
+ bitmap_zero(h->notifs_mask, pcdev->nr_lines);
mutex_lock(&pcdev->lock);
- ret = desc->map_event(irq, pcdev, h->notifs, &notifs_mask);
- if (ret || !notifs_mask) {
+ ret = desc->map_event(irq, pcdev, h->notifs, h->notifs_mask);
+ if (ret || bitmap_empty(h->notifs_mask, pcdev->nr_lines)) {
mutex_unlock(&pcdev->lock);
return IRQ_NONE;
}
- for_each_set_bit(i, &notifs_mask, pcdev->nr_lines) {
+ for_each_set_bit(i, h->notifs_mask, pcdev->nr_lines) {
unsigned long notifs, rnotifs;
struct pse_ntf ntf = {};
@@ -1340,6 +1341,10 @@ int devm_pse_irq_helper(struct pse_contr
if (!h->notifs)
return -ENOMEM;
+ h->notifs_mask = devm_bitmap_zalloc(dev, pcdev->nr_lines, GFP_KERNEL);
+ if (!h->notifs_mask)
+ return -ENOMEM;
+
ret = devm_request_threaded_irq(dev, irq, NULL, pse_isr,
IRQF_ONESHOT | irq_flags,
irq_name, h);
@@ -0,0 +1,35 @@
From a2d81faf75c1661d7a5977725731f481ba8de92d Mon Sep 17 00:00:00 2001
From: Carlo Szelinsky <github@szelinsky.de>
Date: Sat, 23 May 2026 21:32:58 +0200
Subject: [PATCH] net: pse-pd: disable IRQ before freeing PI data in unregister
pse_controller_unregister() frees the PI array via pse_release_pis()
before disabling the controller IRQ. The threaded IRQ handler pse_isr()
walks pcdev->pi[] (via pse_set_config_isr() and
regulator_notifier_call_chain() on pcdev->pi[i].rdev), so an interrupt
arriving in the window between pse_release_pis() and disable_irq()
dereferences freed memory.
Disable the IRQ first, then release the PI array. cancel_work_sync()
for the notification worker stays after pse_release_pis(): the worker
only touches the kfifo and the pse_control list, not pcdev->pi.
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 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -1115,9 +1115,9 @@ EXPORT_SYMBOL_GPL(pse_controller_registe
void pse_controller_unregister(struct pse_controller_dev *pcdev)
{
pse_flush_pw_ds(pcdev);
- pse_release_pis(pcdev);
if (pcdev->irq)
disable_irq(pcdev->irq);
+ pse_release_pis(pcdev);
cancel_work_sync(&pcdev->ntf_work);
kfifo_free(&pcdev->ntf_fifo);
mutex_lock(&pse_list_mutex);
@@ -0,0 +1,62 @@
From c4bd3afcf943491228e284cc6e68a145fcd07c2b Mon Sep 17 00:00:00 2001
From: Carlo Szelinsky <github@szelinsky.de>
Date: Sat, 23 May 2026 21:33:24 +0200
Subject: [PATCH] net: pse-pd: guard against freed PI data on regulator disable
PSE PI regulators are devm-registered inside pse_controller_register(),
which runs before devres_add() arms the controller's own release in
devm_pse_controller_register(). On driver detach, devres unwinds in LIFO
order, so pse_controller_unregister() runs first and frees pcdev->pi via
pse_release_pis(); the regulators are torn down afterwards.
When regulator_unregister() flushes a pending disable, the regulator core
invokes pse_pi_disable(), which dereferences pcdev->pi[id] (directly and
via _pse_pi_disable() -> pse_pi_deallocate_pw_budget()). At that point the
PI array is already freed, so this is a use-after-free.
pse_release_pis() now clears pcdev->pi after freeing it, and
pse_pi_disable() bails out under the lock when pcdev->pi is NULL, so a
late disable from the regulator core is a no-op once the controller has
been unregistered.
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 | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -145,6 +145,7 @@ static void pse_release_pis(struct pse_c
of_node_put(pcdev->pi[i].np);
}
kfree(pcdev->pi);
+ pcdev->pi = NULL;
}
/**
@@ -702,15 +703,21 @@ static int pse_pi_enable(struct regulato
static int pse_pi_disable(struct regulator_dev *rdev)
{
struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
- struct pse_pi *pi;
int id, ret;
id = rdev_get_id(rdev);
- pi = &pcdev->pi[id];
mutex_lock(&pcdev->lock);
+ /* The controller may already be unregistered (pcdev->pi freed) by the
+ * time the regulator core flushes a deferred disable during
+ * regulator_unregister(). Bail out to avoid touching freed PI data.
+ */
+ if (!pcdev->pi) {
+ mutex_unlock(&pcdev->lock);
+ return 0;
+ }
ret = _pse_pi_disable(pcdev, id);
if (!ret)
- pi->admin_state_enabled = 0;
+ pcdev->pi[id].admin_state_enabled = 0;
mutex_unlock(&pcdev->lock);
return 0;
@@ -20,7 +20,7 @@ Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
--- a/drivers/net/pse-pd/pse_core.c --- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c
@@ -1138,6 +1138,9 @@ int pse_controller_register(struct pse_c @@ -1145,6 +1145,9 @@ int pse_controller_register(struct pse_c
list_add(&pcdev->list, &pse_controller_list); list_add(&pcdev->list, &pse_controller_list);
mutex_unlock(&pse_list_mutex); mutex_unlock(&pse_list_mutex);
@@ -30,7 +30,7 @@ Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(pse_controller_register); EXPORT_SYMBOL_GPL(pse_controller_register);
@@ -1148,6 +1151,9 @@ EXPORT_SYMBOL_GPL(pse_controller_registe @@ -1155,6 +1158,9 @@ EXPORT_SYMBOL_GPL(pse_controller_registe
*/ */
void pse_controller_unregister(struct pse_controller_dev *pcdev) void pse_controller_unregister(struct pse_controller_dev *pcdev)
{ {
@@ -38,5 +38,5 @@ Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+ PSE_UNREGISTERED, pcdev); + PSE_UNREGISTERED, pcdev);
+ +
pse_flush_pw_ds(pcdev); pse_flush_pw_ds(pcdev);
pse_release_pis(pcdev);
if (pcdev->irq) if (pcdev->irq)
disable_irq(pcdev->irq);
@@ -380,7 +380,7 @@ Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
dev_err(sfp->dev, "phy_device_register failed: %pe\n", dev_err(sfp->dev, "phy_device_register failed: %pe\n",
--- a/drivers/net/pse-pd/pse_core.c --- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c
@@ -2016,3 +2016,17 @@ bool pse_has_c33(struct pse_control *pse @@ -2028,3 +2028,17 @@ bool pse_has_c33(struct pse_control *pse
return psec->pcdev->types & ETHTOOL_PSE_C33; return psec->pcdev->types & ETHTOOL_PSE_C33;
} }
EXPORT_SYMBOL_GPL(pse_has_c33); EXPORT_SYMBOL_GPL(pse_has_c33);