diff mbox series

core/device.c: Fix dt_find_compatible_node

Message ID 20180207025607.22425-1-alistair@popple.id.au
State Changes Requested
Headers show
Series core/device.c: Fix dt_find_compatible_node | expand

Commit Message

Alistair Popple Feb. 7, 2018, 2:56 a.m. UTC
dt_find_compatible_node() and dt_find_compatible_node_on_chip() are used to
find device nodes under a parent/root node with a given compatible
property.

dt_next(root, prev) is used to walk the child nodes of the given parent and
takes two arguments - root contains the parent node to walk whilst prev
contains the previous child to search from so that it can be used as an
iterator over all children nodes.

The first iteration of dt_find_compatible_node(root, prev) calls
dt_next(root, root) which is not a well defined operation as prev is
assumed to be child of the root node. The result is that when a node
contains no children it will start returning the parent nodes siblings
until it hits the top of the tree at which point a NULL derefence is
attempted when looking for the root nodes parent.

Dereferencing NULL can result in undesirable data exceptions during system
boot and untimely non-hilarious system crashes. dt_next() should not be
called with prev == root. Instead we add a check to dt_next() such that
passing prev = NULL will cause it to start iterating from the first child
node (if any).

Also add a unit test for this case to run-device.c.

Signed-off-by: Alistair Popple <alistair@popple.id.au>
---
 core/device.c          | 16 ++++++++++------
 core/test/run-device.c |  1 +
 2 files changed, 11 insertions(+), 6 deletions(-)

Comments

Andrew Donnellan Feb. 7, 2018, 6:56 a.m. UTC | #1
On 07/02/18 13:56, Alistair Popple wrote:
> dt_find_compatible_node() and dt_find_compatible_node_on_chip() are used to
> find device nodes under a parent/root node with a given compatible
> property.
> 
> dt_next(root, prev) is used to walk the child nodes of the given parent and
> takes two arguments - root contains the parent node to walk whilst prev
> contains the previous child to search from so that it can be used as an
> iterator over all children nodes.
> 
> The first iteration of dt_find_compatible_node(root, prev) calls
> dt_next(root, root) which is not a well defined operation as prev is
> assumed to be child of the root node. The result is that when a node
> contains no children it will start returning the parent nodes siblings
> until it hits the top of the tree at which point a NULL derefence is
> attempted when looking for the root nodes parent.
> 
> Dereferencing NULL can result in undesirable data exceptions during system
> boot and untimely non-hilarious system crashes. dt_next() should not be
> called with prev == root. Instead we add a check to dt_next() such that
> passing prev = NULL will cause it to start iterating from the first child
> node (if any).
> 
> Also add a unit test for this case to run-device.c.
> 
> Signed-off-by: Alistair Popple <alistair@popple.id.au>

This is both less buggy and clearer.

Reviewed-by: Andrew Donnellan <andrew.donnellan@au1.ibm.com>

> ---
>   core/device.c          | 16 ++++++++++------
>   core/test/run-device.c |  1 +
>   2 files changed, 11 insertions(+), 6 deletions(-)
> 
> diff --git a/core/device.c b/core/device.c
> index fc1db568..194eaef3 100644
> --- a/core/device.c
> +++ b/core/device.c
> @@ -608,6 +608,12 @@ struct dt_node *dt_first(const struct dt_node *root)
>   struct dt_node *dt_next(const struct dt_node *root,
>   			const struct dt_node *prev)
>   {
> +	if (!prev)
> +		prev = dt_first(root);
> +
> +	if (!prev)
> +		return NULL;
> +
>   	/* Children? */
>   	if (!list_empty(&prev->children))
>   		return dt_first(prev);
> @@ -719,10 +725,9 @@ struct dt_node *dt_find_compatible_node(struct dt_node *root,
>   					struct dt_node *prev,
>   					const char *compat)
>   {
> -	struct dt_node *node;
> +	struct dt_node *node = prev;
>   
> -	node = prev ? dt_next(root, prev) : root;
> -	for (; node; node = dt_next(root, node))
> +	while ((node = dt_next(root, node)))
>   		if (dt_node_is_compatible(node, compat))
>   			return node;
>   	return NULL;
> @@ -964,10 +969,9 @@ struct dt_node *dt_find_compatible_node_on_chip(struct dt_node *root,
>   						const char *compat,
>   						uint32_t chip_id)
>   {
> -	struct dt_node *node;
> +	struct dt_node *node = prev;
>   
> -	node = prev ? dt_next(root, prev) : root;
> -	for (; node; node = dt_next(root, node)) {
> +	while ((node = dt_next(root, node))) {
>   		u32 cid = __dt_get_chip_id(node);
>   		if (cid == chip_id &&
>   		    dt_node_is_compatible(node, compat))
> diff --git a/core/test/run-device.c b/core/test/run-device.c
> index 326be3ff..6d3842e6 100644
> --- a/core/test/run-device.c
> +++ b/core/test/run-device.c
> @@ -327,6 +327,7 @@ int main(void)
>   	gc2 = dt_new(c1, "node-without-compatible");
>   	assert(__dt_find_property(gc2, "compatible") == NULL);
>   	assert(!dt_node_is_compatible(gc2, "any-property"));
> +	assert(dt_find_compatible_node(gc2, NULL, "node-without-compatible") == NULL);
>   
>   	assert(dt_find_compatible_node(root, NULL, "generic-fake-bus") == c2);
>   	assert(dt_find_compatible_node(root, c2, "generic-fake-bus") == NULL);
>
Alistair Popple Feb. 8, 2018, 6:11 a.m. UTC | #2
> This is both less buggy and clearer.

I'm not convinced of this. It seems like it might somehow break NVLink (at least
according to git bisect) so we should probably hold off merging this until I
figure out what went wrong. Thanks.

> Reviewed-by: Andrew Donnellan <andrew.donnellan@au1.ibm.com>
> 
> > ---
> >   core/device.c          | 16 ++++++++++------
> >   core/test/run-device.c |  1 +
> >   2 files changed, 11 insertions(+), 6 deletions(-)
> > 
> > diff --git a/core/device.c b/core/device.c
> > index fc1db568..194eaef3 100644
> > --- a/core/device.c
> > +++ b/core/device.c
> > @@ -608,6 +608,12 @@ struct dt_node *dt_first(const struct dt_node *root)
> >   struct dt_node *dt_next(const struct dt_node *root,
> >   			const struct dt_node *prev)
> >   {
> > +	if (!prev)
> > +		prev = dt_first(root);
> > +
> > +	if (!prev)
> > +		return NULL;
> > +
> >   	/* Children? */
> >   	if (!list_empty(&prev->children))
> >   		return dt_first(prev);
> > @@ -719,10 +725,9 @@ struct dt_node *dt_find_compatible_node(struct dt_node *root,
> >   					struct dt_node *prev,
> >   					const char *compat)
> >   {
> > -	struct dt_node *node;
> > +	struct dt_node *node = prev;
> >   
> > -	node = prev ? dt_next(root, prev) : root;
> > -	for (; node; node = dt_next(root, node))
> > +	while ((node = dt_next(root, node)))
> >   		if (dt_node_is_compatible(node, compat))
> >   			return node;
> >   	return NULL;
> > @@ -964,10 +969,9 @@ struct dt_node *dt_find_compatible_node_on_chip(struct dt_node *root,
> >   						const char *compat,
> >   						uint32_t chip_id)
> >   {
> > -	struct dt_node *node;
> > +	struct dt_node *node = prev;
> >   
> > -	node = prev ? dt_next(root, prev) : root;
> > -	for (; node; node = dt_next(root, node)) {
> > +	while ((node = dt_next(root, node))) {
> >   		u32 cid = __dt_get_chip_id(node);
> >   		if (cid == chip_id &&
> >   		    dt_node_is_compatible(node, compat))
> > diff --git a/core/test/run-device.c b/core/test/run-device.c
> > index 326be3ff..6d3842e6 100644
> > --- a/core/test/run-device.c
> > +++ b/core/test/run-device.c
> > @@ -327,6 +327,7 @@ int main(void)
> >   	gc2 = dt_new(c1, "node-without-compatible");
> >   	assert(__dt_find_property(gc2, "compatible") == NULL);
> >   	assert(!dt_node_is_compatible(gc2, "any-property"));
> > +	assert(dt_find_compatible_node(gc2, NULL, "node-without-compatible") == NULL);
> >   
> >   	assert(dt_find_compatible_node(root, NULL, "generic-fake-bus") == c2);
> >   	assert(dt_find_compatible_node(root, c2, "generic-fake-bus") == NULL);
> > 
> 
>
Stewart Smith Feb. 9, 2018, 12:36 a.m. UTC | #3
Alistair Popple <alistair@popple.id.au> writes:

>> This is both less buggy and clearer.
>
> I'm not convinced of this. It seems like it might somehow break NVLink (at least
> according to git bisect) so we should probably hold off merging this until I
> figure out what went wrong. Thanks.

Yeah, fixed booting on ZZ, but kind of enabled turbo-xstop mode if you
actually had nvlinks (and loaded the driver) :)
diff mbox series

Patch

diff --git a/core/device.c b/core/device.c
index fc1db568..194eaef3 100644
--- a/core/device.c
+++ b/core/device.c
@@ -608,6 +608,12 @@  struct dt_node *dt_first(const struct dt_node *root)
 struct dt_node *dt_next(const struct dt_node *root,
 			const struct dt_node *prev)
 {
+	if (!prev)
+		prev = dt_first(root);
+
+	if (!prev)
+		return NULL;
+
 	/* Children? */
 	if (!list_empty(&prev->children))
 		return dt_first(prev);
@@ -719,10 +725,9 @@  struct dt_node *dt_find_compatible_node(struct dt_node *root,
 					struct dt_node *prev,
 					const char *compat)
 {
-	struct dt_node *node;
+	struct dt_node *node = prev;
 
-	node = prev ? dt_next(root, prev) : root;
-	for (; node; node = dt_next(root, node))
+	while ((node = dt_next(root, node)))
 		if (dt_node_is_compatible(node, compat))
 			return node;
 	return NULL;
@@ -964,10 +969,9 @@  struct dt_node *dt_find_compatible_node_on_chip(struct dt_node *root,
 						const char *compat,
 						uint32_t chip_id)
 {
-	struct dt_node *node;
+	struct dt_node *node = prev;
 
-	node = prev ? dt_next(root, prev) : root;
-	for (; node; node = dt_next(root, node)) {
+	while ((node = dt_next(root, node))) {
 		u32 cid = __dt_get_chip_id(node);
 		if (cid == chip_id &&
 		    dt_node_is_compatible(node, compat))
diff --git a/core/test/run-device.c b/core/test/run-device.c
index 326be3ff..6d3842e6 100644
--- a/core/test/run-device.c
+++ b/core/test/run-device.c
@@ -327,6 +327,7 @@  int main(void)
 	gc2 = dt_new(c1, "node-without-compatible");
 	assert(__dt_find_property(gc2, "compatible") == NULL);
 	assert(!dt_node_is_compatible(gc2, "any-property"));
+	assert(dt_find_compatible_node(gc2, NULL, "node-without-compatible") == NULL);
 
 	assert(dt_find_compatible_node(root, NULL, "generic-fake-bus") == c2);
 	assert(dt_find_compatible_node(root, c2, "generic-fake-bus") == NULL);