Re: [PATCH v2] elf: Open the normalized $ORIGIN rpath in AT_SECURE programs (BZ 34360)

Adhemerval Zanella Netto <[email protected]>
Newsgroups gmane.comp.lib.glibc.alpha
Organization Linaro
Message-ID <[email protected]>

On 31/08/26 10:54, Florian Weimer wrote:
> * Adhemerval Zanella:
> 
>> diff --git a/elf/dl-path-normalize.h b/elf/dl-path-normalize.h
>> new file mode 100644
>> index 00000000000..96e04f5d14e
>> --- /dev/null
>> +++ b/elf/dl-path-normalize.h
>> @@ -0,0 +1,114 @@
> 
>> +static inline size_t
>> +_dl_normalize_path (char *path)
>> +{
>> +  /* The root '/' of an absolute path is not removed.  */
>> +  char *pstart = path + (path[0] == '/');
>> +  const char *rnp = pstart;
>> +  char *wnp = pstart;
>> +  /* End of the prefix a ".." may not remove, the root '/' plus any preserved
>> +     leading ".." components of a relative path.  */
>> +  char *limit = pstart;
> 
> Suggestion: 
> 
> +  /* End of the prefix a ".." may not remove.  Either the root '/', or,
> +     for relative paths, the original start of the string or any ascending
> +     leading ".." components.  */
> 
> Or something like that.

Ack, you suggestion sounds good.
> 
>> +
>> +  while (*rnp != '\0')
>> +    {
>> +      /* Collapse consecutive separators.  */
>> +      if (*rnp == '/')
>> +	{
>> +	  ++rnp;
>> +	  continue;
>> +	}
>> +
>> +      /* [RNP, REND) is the next input component.  */
>> +      const char *rend = rnp;
>> +      while (*rend != '\0' && *rend != '/')
>> +	++rend;
>> +      size_t clen = rend - rnp;
> 
> I think that's just
> 
>   const char *rend = __strchrnul (rnp, '/');
> 
> ?

Yes, but it would require pulling strchrnul in the loader. Maybe now it is used
in more than one place, we can consolidate it along with other usages (like
_dl_hwcaps_split, which contains this very justification).

I will send a patch for that.

> 
>> +
>> +      /* Drop '.' component.  */
>> +      if (clen == 1 && rnp[0] == '.')
>> +	;
>> +      else if (clen == 2 && rnp[0] == '.' && rnp[1] == '.')
>> +	{
>> +	  if (wnp > limit)
>> +	    {
>> +	      /* Remove the last component along with the '/' separating it
>> +		 from its predecessor (the root '/' of an absolute path is
>> +		 retained).  */
>> +	      while (wnp > limit && wnp[-1] != '/')
>> +		--wnp;
> 
> I believe this can be expressed in terms of memrchr, but memrchr is not
> available in the dynamic linker.

The problem here is backward scan must stop at limit, not the buffer start.

> 
>> +	      if (wnp > pstart)
>> +		--wnp;
>> +	    }
>> +	  else if (pstart == path)
>> +	    {
> 
> “pstart == path” checks for a relative orignal path.  Maybe mention
> this here?

Right, I changed to:

             /* No component is left and the original path is relative,
                keep the unresolvable ".." (it becomes part of the
                preserved prefix).  */

> 
>> +	      /* No component is left: keep the unresolvable ".." for a
>> +		 relative path (it becomes part of the preserved prefix),
>> +		 drop it at the root of an absolute one.  */
>> +	      if (wnp > pstart)
>> +		*wnp++ = '/';
>> +	      *wnp++ = '.';
>> +	      *wnp++ = '.';
>> +	      limit = wnp;
>> +	    }
> 
> The missing empty else branch here drops the the ../.  Maybe put that
> part of the comment here.
> 
>> +	}
>> +      else
>> +	{
>> +	  if (wnp > pstart)
>> +	    *wnp++ = '/';
>> +	  while (rnp < rend)
>> +	    *wnp++ = *rnp++;
>> +	}
> 
> The while loop is just mempcpy?  clen has already been computed above.
> The update of rnp is redundant with the assigned below.

The destination may overlap the source, for e.g. "//abc" they partially
overlap (3 bytes from path+2 to path+1). We can use memmove here, ld.so
already pulls it.

> 
>> +
>> +      rnp = rend;
>> +    }
>> +
>> +  *wnp = '\0';
>> +  return wnp - path;
>> +}
> 
> All these are just suggestions.  Maybe leave the code as-is and only
> theck the comments because changing the code would invalidate the
> testing below.

Sounds reasonable, I will send the strchrnul and memmove changes as a
subsequent patch.

> 
> I reviewed the logic and it is sound.  The specification also matches
> what we need.
> 
> Furthermore, I auto-generated a completely different implementation
> (using recursion).  I ran both implementations against each other in a
> fuzzer, and found no differences.  I also made sure that your
> implementation would not crash on the fuzzing corpus when built with
> Address Sanitizer.
> 
> Together with my manual review, I'm pretty confident that the
> implementation is correct.
> 
> Rest of the patch looks okay, too.
> 
> Reviewed-by: Florian Weimer <[email protected]>
> 
> I still believe this needs CVE assignment, for either the TOCTOU race
> condition fix or the buffer overflow fix.
Ok, I will check with Carlos and Siddhesh about it.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.