diff mbox series

[2/2] device property: fix UAF in device_get_next_child_node()

Message ID 20241128053937.4076797-2-dmitry.torokhov@gmail.com
State New
Headers show
Series [1/2] device property: do not leak child nodes when using NULL/error pointers | expand

Commit Message

Dmitry Torokhov Nov. 28, 2024, 5:39 a.m. UTC
fwnode_get_next_child_node() always drops reference to the node passed
as the "child" argument, which makes "child" pointer no longer valid
and we can not use it to scan the secondary node in case there are no
more children in primary one.

Also, it is not obvious whether it is safe to pass children of the
secondary node to fwnode_get_next_child_node() called on the primary
node in subsequent calls to device_get_next_child_node().

Fix the issue by checking whether the child node passed in is indeed a
child of primary or secondary node, and do not call
fwnode_get_next_child_node() for the wrong parent node. Also set the
"child" to NULL after unsuccessful call to fwnode_get_next_child_node()
on primary node to make sure secondary node's children are scanned from
the beginning.

Fixes: 114dbb4fa7c4 ("drivers property: When no children in primary, try secondary")
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/base/property.c | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

Comments

Andy Shevchenko Nov. 28, 2024, 1:20 p.m. UTC | #1
On Wed, Nov 27, 2024 at 09:39:35PM -0800, Dmitry Torokhov wrote:
> fwnode_get_next_child_node() always drops reference to the node passed
> as the "child" argument,

As commented previously,it might be just a documentation bug. So, please
elaborate on the use case before this patch that leads to an issue.

> which makes "child" pointer no longer valid
> and we can not use it to scan the secondary node in case there are no
> more children in primary one.
> 
> Also, it is not obvious whether it is safe to pass children of the
> secondary node to fwnode_get_next_child_node() called on the primary
> node in subsequent calls to device_get_next_child_node().
> 
> Fix the issue by checking whether the child node passed in is indeed a
> child of primary or secondary node, and do not call
> fwnode_get_next_child_node() for the wrong parent node. Also set the
> "child" to NULL after unsuccessful call to fwnode_get_next_child_node()
> on primary node to make sure secondary node's children are scanned from
> the beginning.

To me it sounds over complicated. Why not just take reference to the child once
more and put it after we find next in either cases? Current solution provides
a lot of duplication and makes function less understandable.
Dmitry Torokhov Nov. 28, 2024, 11:16 p.m. UTC | #2
On Thu, Nov 28, 2024 at 03:20:09PM +0200, Andy Shevchenko wrote:
> On Wed, Nov 27, 2024 at 09:39:35PM -0800, Dmitry Torokhov wrote:
> > fwnode_get_next_child_node() always drops reference to the node passed
> > as the "child" argument,
> 
> As commented previously,it might be just a documentation bug. So, please

No, absolutely not. Consider calling device_get_next_child_node() with
"child" pointing to the last child of the primary fwnode.
fwnode_get_next_child_node() will drop the reference to "child"
rendering it unusable, and return NULL. The code will go and call
fwnode_get_next_child_node(fwnode->secondary, child) with invalid child
pointer, resulting in UAF condition.

Also, consider what happens next. Let's say we did not crash and instead
returned first child of the secondary node (let's assume primary is an
OF node and secondary is a software node). On the next iteration of
device_get_next_child_node() we will call
fwnode_get_next_child_node(fwnode, child) which results in passing
swnode child to of_fwnode_get_next_child_node() which may or may not
work. It all is very fragile.

That is why it is best to check if the child argument is indeed a child
to a given parent before calling fwnode_get_next_child_node() on them.

> elaborate on the use case before this patch that leads to an issue.
> 
> > which makes "child" pointer no longer valid
> > and we can not use it to scan the secondary node in case there are no
> > more children in primary one.
> > 
> > Also, it is not obvious whether it is safe to pass children of the
> > secondary node to fwnode_get_next_child_node() called on the primary
> > node in subsequent calls to device_get_next_child_node().
> > 
> > Fix the issue by checking whether the child node passed in is indeed a
> > child of primary or secondary node, and do not call
> > fwnode_get_next_child_node() for the wrong parent node. Also set the
> > "child" to NULL after unsuccessful call to fwnode_get_next_child_node()
> > on primary node to make sure secondary node's children are scanned from
> > the beginning.
> 
> To me it sounds over complicated. Why not just take reference to the child once
> more and put it after we find next in either cases?

You want to "reset" when switching from primary node over to secondary
instead of hoping that passing child pointer which is not really a child
to secondary node will somehow cause fwnode_get_next_child_node() to
return first its child.

> Current solution provides
> a lot of duplication and makes function less understandable.

The simplicity of the old version is deceiving. See the explanation
above.

Thanks.
diff mbox series

Patch

diff --git a/drivers/base/property.c b/drivers/base/property.c
index 696ba43b8e8a..0ca3c0908b0c 100644
--- a/drivers/base/property.c
+++ b/drivers/base/property.c
@@ -815,11 +815,26 @@  struct fwnode_handle *device_get_next_child_node(const struct device *dev,
 	}
 
 	/* Try to find a child in primary fwnode */
-	next = fwnode_get_next_child_node(fwnode, child);
-	if (next)
-		return next;
+	if (!child || fwnode_get_parent(child) == fwnode) {
+		next = fwnode_get_next_child_node(fwnode, child);
+		if (next)
+			return next;
+		/*
+		 * We ran out of children in primary - reset the child
+		 * node to start from the beginning when scanning secondary
+		 * node.
+		 */
+		child = NULL;
+	}
 
 	/* When no more children in primary, continue with secondary */
+
+	if (IS_ERR_OR_NULL(fwnode->secondary) ||
+	    (child && fwnode_get_parent(child) != fwnode->secondary)) {
+		fwnode_handle_put(child);
+		return NULL;
+	}
+
 	return fwnode_get_next_child_node(fwnode->secondary, child);
 }
 EXPORT_SYMBOL_GPL(device_get_next_child_node);