[PATCH 1/4] software node: do not resurrect swnode being released
From: Peng Fan (OSS)
Date: Wed Nov 04 2026 - 10:49:30 EST
From: Peng Fan <peng.fan@xxxxxxx>
software_node_to_swnode() walks the kset list under list_lock, but the
last kobject_put() drops the reference count to zero before
kobject_cleanup() removes the object from the list. A lookup can
therefore find a zero-ref swnode and call kobject_get() on it, which
triggers refcount warnings and a use-after-free.
One way to hit this is the dwc3-imx driver on i.MX95: when
dwc3_core_probe() returns -EPROBE_DEFER, dwc3_imx_probe() removes the
static software node from the device, and the deferred probe retry
immediately adds the same node again. The previous swnode is still
waiting for its release callback, and CONFIG_DEBUG_KOBJECT_RELEASE makes
the window large and deterministic.
Fix the lookup by acquiring references with kobject_get_unless_zero()
and skipping objects that are already in their release path. Use a
reference-returning variant for callers that keep the node (register,
unregister and device_add_software_node()) and keep a borrowed-pointer
variant for software_node_fwnode().
To avoid looking the node up again after registration,
software_node_register() now uses an internal helper that returns the
newly created swnode reference, and device_add_software_node() consumes
that reference directly.
Fixes: 80488a6b1d3c ("software node: Add support for static node descriptors")
Signed-off-by: Peng Fan <peng.fan@xxxxxxx>
---
drivers/base/swnode.c | 114 ++++++++++++++++++++++++++++++++++----------------
1 file changed, 78 insertions(+), 36 deletions(-)
diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c
index 84a77ffa3dc4b..19a4153f3fac1 100644
--- a/drivers/base/swnode.c
+++ b/drivers/base/swnode.c
@@ -75,8 +75,28 @@ static inline struct swnode *dev_to_swnode(struct device *dev)
return to_swnode(fwnode);
}
+static struct swnode *swnode_get(struct swnode *swnode)
+{
+ kobject_get(&swnode->kobj);
+
+ return swnode;
+}
+
+static void swnode_put(struct swnode *swnode)
+{
+ kobject_put(&swnode->kobj);
+}
+
+/*
+ * Find the swnode registered for @node and return it with a new reference.
+ *
+ * A kobject remains on the kset list until its release callback runs, so an
+ * entry whose last reference has already been dropped can still be found
+ * here. Use kobject_get_unless_zero() to skip such objects instead of
+ * resurrecting them.
+ */
static struct swnode *
-software_node_to_swnode(const struct software_node *node)
+software_node_to_swnode_get(const struct software_node *node)
{
struct swnode *swnode = NULL;
struct kobject *k;
@@ -87,10 +107,16 @@ software_node_to_swnode(const struct software_node *node)
spin_lock(&swnode_kset->list_lock);
list_for_each_entry(k, &swnode_kset->list, entry) {
- swnode = kobj_to_swnode(k);
- if (swnode->node == node)
- break;
- swnode = NULL;
+ struct swnode *candidate = kobj_to_swnode(k);
+
+ if (candidate->node != node)
+ continue;
+
+ if (!kobject_get_unless_zero(k))
+ continue;
+
+ swnode = candidate;
+ break;
}
spin_unlock(&swnode_kset->list_lock);
@@ -98,6 +124,21 @@ software_node_to_swnode(const struct software_node *node)
return swnode;
}
+/*
+ * Borrowed-pointer variant of software_node_to_swnode_get(). The caller must
+ * guarantee the lifetime of @node by other means.
+ */
+static struct swnode *
+software_node_to_swnode(const struct software_node *node)
+{
+ struct swnode *swnode = software_node_to_swnode_get(node);
+
+ if (swnode)
+ swnode_put(swnode);
+
+ return swnode;
+}
+
const struct software_node *to_software_node(const struct fwnode_handle *fwnode)
{
const struct swnode *swnode = to_swnode(fwnode);
@@ -374,18 +415,6 @@ EXPORT_SYMBOL_GPL(property_entries_free);
/* -------------------------------------------------------------------------- */
/* fwnode operations */
-static struct swnode *swnode_get(struct swnode *swnode)
-{
- kobject_get(&swnode->kobj);
-
- return swnode;
-}
-
-static void swnode_put(struct swnode *swnode)
-{
- kobject_put(&swnode->kobj);
-}
-
static struct fwnode_handle *software_node_get(struct fwnode_handle *fwnode)
{
struct swnode *swnode = swnode_get(to_swnode(fwnode));
@@ -977,21 +1006,39 @@ void software_node_unregister_node_group(const struct software_node * const *nod
}
EXPORT_SYMBOL_GPL(software_node_unregister_node_group);
+static struct swnode *
+software_node_register_node(const struct software_node *node)
+{
+ struct swnode *parent, *swnode;
+ struct fwnode_handle *fwnode;
+
+ swnode = software_node_to_swnode_get(node);
+ if (swnode) {
+ swnode_put(swnode);
+ return ERR_PTR(-EEXIST);
+ }
+
+ parent = software_node_to_swnode_get(node->parent);
+ if (node->parent && !parent)
+ return ERR_PTR(-EINVAL);
+
+ fwnode = swnode_register(node, parent, 0);
+ if (parent)
+ swnode_put(parent);
+
+ if (IS_ERR(fwnode))
+ return ERR_CAST(fwnode);
+
+ return to_swnode(fwnode);
+}
+
/**
* software_node_register - Register static software node
* @node: The software node to be registered
*/
int software_node_register(const struct software_node *node)
{
- struct swnode *parent = software_node_to_swnode(node->parent);
-
- if (software_node_to_swnode(node))
- return -EEXIST;
-
- if (node->parent && !parent)
- return -EINVAL;
-
- return PTR_ERR_OR_ZERO(swnode_register(node, parent, 0));
+ return PTR_ERR_OR_ZERO(software_node_register_node(node));
}
EXPORT_SYMBOL_GPL(software_node_register);
@@ -1061,21 +1108,16 @@ EXPORT_SYMBOL_GPL(fwnode_remove_software_node);
int device_add_software_node(struct device *dev, const struct software_node *node)
{
struct swnode *swnode;
- int ret;
/* Only one software node per device. */
if (dev_to_swnode(dev))
return -EBUSY;
- swnode = software_node_to_swnode(node);
- if (swnode) {
- swnode_get(swnode);
- } else {
- ret = software_node_register(node);
- if (ret)
- return ret;
-
- swnode = software_node_to_swnode(node);
+ swnode = software_node_to_swnode_get(node);
+ if (!swnode) {
+ swnode = software_node_register_node(node);
+ if (IS_ERR(swnode))
+ return PTR_ERR(swnode);
}
set_secondary_fwnode(dev, &swnode->fwnode);
--
2.34.1