devicetree-compiler.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: David Gibson <david@gibson.dropbear.id.au>
To: "Pierre-Clément Tosi" <ptosi@google.com>
Cc: devicetree-compiler@vger.kernel.org,
	Mike McTernan <mikemcternan@google.com>,
	Simon Glass <sjg@chromium.org>
Subject: Re: [PATCH] libfdt: fdt_get_alias_namelen: Validate aliases
Date: Tue, 10 Oct 2023 15:04:13 +1100	[thread overview]
Message-ID: <ZSTNPUv+dxfnXBZ5@zatzit> (raw)
In-Reply-To: <20231009140923.xxhi2cdkemqod7j7@google.com>

[-- Attachment #1: Type: text/plain, Size: 9458 bytes --]

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@google.com/
> 
> > 
> > > Co-developed-by: Mike McTernan <mikemcternan@google.com>
> > 
> > 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@google.com>
> > > ---
> > >  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 #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

  reply	other threads:[~2023-10-10  4:04 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-10-07 11:07 [PATCH] libfdt: fdt_get_alias_namelen: Validate aliases Pierre-Clément Tosi
2023-10-08  7:13 ` David Gibson
2023-10-09 14:09   ` Pierre-Clément Tosi
2023-10-10  4:04     ` David Gibson [this message]
2023-10-10 14:18 ` Mike McTernan
2023-10-11  0:36   ` David Gibson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ZSTNPUv+dxfnXBZ5@zatzit \
    --to=david@gibson.dropbear.id.au \
    --cc=devicetree-compiler@vger.kernel.org \
    --cc=mikemcternan@google.com \
    --cc=ptosi@google.com \
    --cc=sjg@chromium.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).