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:
+80
@@ -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, ¬ifs_mask, pcdev->nr_lines) treats
|
||||
¬ifs_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, ¬ifs_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, ¬ifs_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);
|
||||
+35
@@ -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);
|
||||
+62
@@ -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;
|
||||
+3
-3
@@ -20,7 +20,7 @@ Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
|
||||
--- a/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);
|
||||
mutex_unlock(&pse_list_mutex);
|
||||
|
||||
@@ -30,7 +30,7 @@ Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
return 0;
|
||||
}
|
||||
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)
|
||||
{
|
||||
@@ -38,5 +38,5 @@ Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
+ PSE_UNREGISTERED, pcdev);
|
||||
+
|
||||
pse_flush_pw_ds(pcdev);
|
||||
pse_release_pis(pcdev);
|
||||
if (pcdev->irq)
|
||||
disable_irq(pcdev->irq);
|
||||
|
||||
+1
-1
@@ -380,7 +380,7 @@ Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
|
||||
dev_err(sfp->dev, "phy_device_register failed: %pe\n",
|
||||
--- a/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;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pse_has_c33);
|
||||
|
||||
Reference in New Issue
Block a user