Re: [PATCH] libfdt: fdt_get_alias_namelen: Validate aliases

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]



On Mon, Oct 09, 2023 at 03:09:23PM +0100, Pierre-Clément Tosi wrote:
> Hi David,
> 
> On Sun, Oct 08, 2023 at 06:13:21PM +1100, David Gibson wrote:
> > On Sat, Oct 07, 2023 at 12:07:10PM +0100, Pierre-Clément Tosi wrote:
> > > Ensure that the alias found is valid i.e.
> > > 
> > >     An alias value is a device path and is encoded as a string.
> > >     The value represents the full path to a node, ...
> > 
> > Where's that quote from?  That is typically the case, but I think that
> 
> See the DT specification (v0.4, 3.3 /aliases node):
> 
>     Each property of the /aliases node defines an alias.
>     The property name specifies the alias name.
>     The property value specifies the full path to a node in the devicetree.
>     [...]
>     An alias value is a device path and is encoded as a string.
>     The value represents the full path to a node, [...]
> 
> > at least back in the old OF days, aliases referring to other aliases
> > was allowed.  Even if it's not strictly correct in modern usage, I'd
> > prefer to allow that usage if it's not too difficult to accomplish.
> 
> Compatibility with OF is great but checking that the result is a full path is
> key to preventing recursion.

Ah, that's a fair point.

> Otherwise any DT with "circular" aliases can cause
> the stack overflow I mention below. For example, calling

>     fdt_path_offset(fdt, "loop")
> 
> on
> 
>     /{
>         aliases {
>             loop = "loop";
>         };
>     };

Right.  I think I got thrown off by the unnecessarily specific
description of the loop occurring with an empty alias name, which as
you show above doesn't need to be part of the issue.

> (I've added this as a test case in v2)
> 
> > 
> > > This protects against a stack overflow (fdt_path_offset_namelen() calls
> > > fdt_get_alias_namelen() then fdt_path_offset(alias), ...) when /aliases
> > > has an empty property with an empty name.
> > 
> > It would be helpful to spell out that bad situation a bit more
> > precisely.  AFAICT the way we discover an alias at the beginning of a
> > path, it would have to have at least one character, which means I'm
> > confused as to how something with an empty name gets in here.
> 
> I've rephrased the recursion in a more concrete way in v2.
> 
> AFAICT, nothing currently checks that the "alias has at least one character"

Well.. not explicitly.  And, yes, there are the cases of zero or
negative length path addressed by [1].  Apart from those cases,
however, we only enter the alias case if path[0] != '/' and the alias
name is from the start of the path to the first '/' - therefore the
alias must have at least one character.

> and merging [1] will only cover the special "circular" alias of the empty string
> being aliased to itself.
> 
> [1]: https://lore.kernel.org/devicetree-compiler/20231006124839.z7auhc3mk37gxios@xxxxxxxxxx/
> 
> > 
> > > Co-developed-by: Mike McTernan <mikemcternan@xxxxxxxxxx>
> > 
> > I've never seen a "Co-developed-by" tag before, I'd suggest just
> > putting that in the text.  However all developers of the patch should
> > sign off on it, so I'd expect an S-o-b from both people.
> > 
> 
> Okay, I now have a link to the original fix in the Android Open Source Project.
> 
> > > Signed-off-by: Pierre-Clément Tosi <ptosi@xxxxxxxxxx>
> > > ---
> > >  libfdt/fdt_ro.c   | 11 ++++++++++-
> > >  tests/aliases.dts |  3 +++
> > >  tests/get_alias.c | 12 +++++++++++-
> > >  3 files changed, 24 insertions(+), 2 deletions(-)
> > > 
> > > diff --git a/libfdt/fdt_ro.c b/libfdt/fdt_ro.c
> > > index c4c520c..bda5c0d 100644
> > > --- a/libfdt/fdt_ro.c
> > > +++ b/libfdt/fdt_ro.c
> > > @@ -537,7 +537,16 @@ static const void *fdt_path_getprop_namelen(const void *fdt, const char *path,
> > >  const char *fdt_get_alias_namelen(const void *fdt,
> > >  				  const char *name, int namelen)
> > >  {
> > > -	return fdt_path_getprop_namelen(fdt, "/aliases", name, namelen, NULL);
> > > +	int len;
> > > +	const char *alias;
> > > +
> > > +	alias = fdt_path_getprop_namelen(fdt, "/aliases", name, namelen, &len);
> > > +
> > > +	if (!can_assume(VALID_DTB) &&
> > > +	    !(len > 0 && alias && memchr(alias, '\0', len) && *alias == '/'))
> > 
> > The first three conditions here look good to me.  You could even check
> > alias[len - 1] == '\0' to avoid scanning the entire string - an alias
> > should be one single string.
> > 
> > I'd prefer not to enforce that the alias in an absolute path (the last
> > check), if we can avoid it, since, as above, I think historically
> > aliases to aliases has been an allowed thing.
> 
> I've replaced the memchr() call in v2 but kept the absolute path check for the
> reason mentioned above.
> 
> > 
> > > +		return NULL;
> > 
> > Pity we can't return a more specific error here.  Oh well.
> 
> Want me to introduce fdt_find_alias{,_namelen}() wrapped as
> 
>     const char *fdt_get_alias_namelen(...)
>     {
>         const char *alias;
>         int err = fdt_find_alias_namelen(fdt, name, namelen, &alias);
>         return err ? NULL : alias;
>     }
> 
> ? :)
> 
> This would allow callers to handle FDT_ERR_NOTFOUND differently from other
> (probably more serious) errors currently hidden behind a NULL return value and
> would allow our check to be reported as FDT_ERR_BADVALUE.

No.  It's a minor nit that we can't return an error, but I don't think
it's worth the conceptual complexity cost of adding an additional
entry point to allow for it.

> > 
> > > +
> > > +	return alias;
> > >  }
> > >  
> > >  const char *fdt_get_alias(const void *fdt, const char *name)
> > > diff --git a/tests/aliases.dts b/tests/aliases.dts
> > > index 853479a..8820974 100644
> > > --- a/tests/aliases.dts
> > > +++ b/tests/aliases.dts
> > > @@ -8,6 +8,9 @@
> > >  		s1 = &sub1;
> > >  		ss1 = &subsub1;
> > >  		sss1 = &subsubsub1;
> > > +		badpath = "wrong";
> > > +		badpathlong = "wrong/with/parts";
> > 
> > As well as exercising the absolute path test, which I'd prefer to
> > avoid, there doesn't seem to me that these two cases exercise anything
> > much different from each other.
> 
> Yes, the idea was that libfdt could get confused by the presence of separators
> and consider 'badpathlong' valid even if it isn't absolute, hopefully increasing
> coverage. I have instead merged these cases into a single "relative" test case.
> 
> > 
> > > +		empty = "";
> > 
> > This one's a good test.  It would be good to test a non-terminated
> > string too (e.g. nonull = [626164];
> 
> Done; do you know of a way to express a property with an empty name in DTS? Or
> would covering that use-case require a handcrafted DTB?

I don't believe it's possible to express that in dts.  At one point I
was considering an "escaped" syntax to allow construction of node or
property names breaking the usual rules (largely for the purposes of
decompiling bad dtbs), but it doesn't look like I ever implemented it.

I'd also consider it a bug if dtc emitted a dtb with an empty node or
property name (other than the root node, which has an empty name by
convention).  Although it would be acceptable for that to be a "check"
rather than syntactic level check, which means we could still emit
such a thing with the -f option, or if the relevant check were
explicitly disabled.

> > 
> > 
> > >  	};
> > >  
> > >  	sub1: subnode@1 {
> > > diff --git a/tests/get_alias.c b/tests/get_alias.c
> > > index fb2c38c..4f3f6fd 100644
> > > --- a/tests/get_alias.c
> > > +++ b/tests/get_alias.c
> > > @@ -21,9 +21,16 @@ static void check_alias(void *fdt, const char *path, const char *alias)
> > >  
> > >  	aliaspath = fdt_get_alias(fdt, alias);
> > >  
> > > -	if (path && !aliaspath)
> > > +	if (!path && !aliaspath)
> > > +		return;
> > 
> > Not sure what this case is for - we never call this with both values
> > NULL, right?  If we did seems like we shouldn't silently ignore it.
> 
> This returns early if fdt_get_alias() was expected to return NULL and did so.
> Perhaps, are you confusing 'aliaspath' with 'alias'?

Oh, yes, sorry.  I was misreading it as (!path && !alias).

> > 
> > > +	if (!aliaspath)
> > >  		FAIL("fdt_get_alias(%s) failed\n", alias);
> > 
> > Isn't aliaspath == NULL correct for some of these new test cases?
> 
> Only if 'path' was NULL; see above.
> 
> > You're failing unconditionally here.
> > 
> > > +	if (!path)
> > > +		FAIL("fdt_get_alias(%s) returned %s instead of NULL",
> > > +		     alias, aliaspath);
> > > +
> > >  	if (strcmp(aliaspath, path) != 0)
> > >  		FAIL("fdt_get_alias(%s) returned %s instead of %s\n",
> > >  		     alias, aliaspath, path);
> > > @@ -36,6 +43,9 @@ int main(int argc, char *argv[])
> > >  	test_init(argc, argv);
> > >  	fdt = load_blob_arg(argc, argv);
> > >  
> > > +	check_alias(fdt, NULL, "badpath");
> > > +	check_alias(fdt, NULL, "badpathlong");
> > > +	check_alias(fdt, NULL, "empty");
> > >  	check_alias(fdt, "/subnode@1", "s1");
> > >  	check_alias(fdt, "/subnode@1/subsubnode", "ss1");
> > >  	check_alias(fdt, "/subnode@1/subsubnode/subsubsubnode", "sss1");
> > 
> 
> 
> 

-- 
David Gibson			| I'll have my music baroque, and my code
david AT gibson.dropbear.id.au	| minimalist, thank you.  NOT _the_ _other_
				| _way_ _around_!
http://www.ozlabs.org/~dgibson

Attachment: signature.asc
Description: PGP signature


[Index of Archives]     [Device Tree]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux