[PATCH 3/3] pci/hotplug/pnv_php: Fix refcount underflow on hot unplug
From: Shawn Anastasio
Date: Fri Apr 04 2025 - 00:27:12 EST
When hot unplugging a slot containing a PCIe switch on a PowerNV system,
the reference count of the device_node corresponding to the root will
underflow. This is due to improper handling of the device_nodes'
refcounts in pnv_php_detach_device nodes that occurs on each unplug
event.
When iterating through children nodes, pnv_php_detach_nodes first
recursively detach each child's children, then it would decrement the
child's refcount and finally call of_detach_node on it, which in turn
would decrement the refcount further and result in an underflow. Fix
this by dropping the explicit of_put call and by moving the final
of_detach_node call after the loop.
The underflow that occurs without this patch produces the following
backtrace on unplug events:
refcount_t: underflow; use-after-free.
WARNING: CPU: 4 PID: 669 at lib/refcount.c:28 refcount_warn_saturate+0x214/0x224
Call Trace:
refcount_warn_saturate+0x210/0x224 (unreliable)
kobject_put+0x154/0x2d4
of_node_put+0x2c/0x40
of_get_next_child+0x74/0xd0
pnv_php_detach_device_nodes+0x2a4/0x30c
pnv_php_set_slot_power_state+0x20c/0x500
pnv_php_disable_slot+0xb8/0xdc
power_write_file+0xf8/0x18c
pci_slot_attr_store+0x40/0x5c
sysfs_kf_write+0x64/0x78
kernfs_fop_write_iter+0x1b4/0x2a4
vfs_write+0x3bc/0x50c
ksys_write+0x84/0x140
system_call_exception+0x124/0x230
system_call_vectored_common+0x15c/0x2ec
Signed-off-by: Shawn Anastasio <sanastasio@xxxxxxxxxxxxxxxxxxxxx>
---
drivers/pci/hotplug/pnv_php.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/hotplug/pnv_php.c b/drivers/pci/hotplug/pnv_php.c
index 1a734adb5b10..a3fa44f7bf1a 100644
--- a/drivers/pci/hotplug/pnv_php.c
+++ b/drivers/pci/hotplug/pnv_php.c
@@ -156,11 +156,12 @@ static void pnv_php_detach_device_nodes(struct device_node *parent)
struct device_node *dn;
for_each_child_of_node(parent, dn) {
+ /* Detach any children of the parent node first */
pnv_php_detach_device_nodes(dn);
-
- of_node_put(dn);
- of_detach_node(dn);
}
+
+ /* Finally, detach the parent */
+ of_detach_node(parent);
}
static void pnv_php_rmv_devtree(struct pnv_php_slot *php_slot)
--
2.30.2