diff mbox

[BZ,17251] Calculate RPATH $ORIGIN from absolute path

Message ID 1407860209-21797-1-git-send-email-bpshacklett@gmail.com
State New
Headers show

Commit Message

Brennan Shacklett Aug. 12, 2014, 4:16 p.m. UTC
Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
for shared libraries. Currently $ORIGIN is calculated correctly for executables
on linux, because readlink is called on /proc/self/exe, which means the
resulting path is absolute and has no symlinks. 
Shared libraries with relative paths on the other hand are based 
off of appending the name / path of the library to the current working 
directory, which means if the library is a symlink, it is not followed, which 
breaks RPATH $ORIGIN in the following scenario:

libone.so and libtwo.so are both in ~/lib
libone.so needs libtwo.so, so libone.so has an RPATH of $ORIGIN.
If I run ldd on ~/lib/libone.so, libtwo.so is found and all is good.
If I create a symlink named ~/libone.so to ~/lib/libone.so, and run ldd on it
libtwo is not found, because $ORIGIN for the library is calculated as ~
instead of ~/lib.

If I was to repeat the above test but instead of libone.so use an executable,
everything would work as expected, which is why I think the shared library
behavior is a bug. 

I ran into this in the real world when attempting to dynamically load a .so
for python SWIG bindings, because the .so which python was loading was
symlinked to the actual library directory where all of the .so's dependencies
were located.

The attached patch fixes this behavior by having realname evaluated with
__realpath in _dl_map_object_from_fd, before _dl_new_object is called (which is
where l_origin is assigned). This also somewhat simplifies the code in
elf/dl-object.c, because realname is guaranteed to be an absolute path
generated by realpath, so I was able to remove the code dealing with relative 
paths.
I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
generic implementation, and extended it with lstat and readlink in the linux
version. This means the bug is still present on other systems than linux (the
generic implementation only returns an absolute path, it doesn't do anything
with symlinks), but if there is a way to get the generic version to follow
symlinks on all systems please let me know.

I tested and ran the elf test suite on Gentoo Linux x86-64. If this change is
wanted, I will happily write a test to go along with it.
I don't have any sort of copyright attribution set up with the FSF, let me know
if it is necessary for this change.

Thanks,
Brennan Shacklett

2014-08-12  Brennan Shacklett  <bpshacklett@gmail.com>

	[BZ #17251]
	* elf/Makefile (rtld-routines): Add dl-realpath.
	* elf/dl-load.c (_dl_map_object_from_fd): 
	Calculate realname with __realpath.
	* elf/dl-object.c (_dl_new_object): Remove relative path handling.
	* elf/dl-realpath.c: New file. Generic implementation of __realpath.
	* elf/tile/dl-runtime.c (_dl_after_load): Remove dl_realpath and 
	change call to __realpath.
	* sysdeps/unix/sysv/linux/Makefile (sysdep-rtld-routines):
	Add dl-lxstat64.
	* sysdeps/unix/sysv/linux/dl-lxstat64.c: New file.
	* sysdeps/unix/sysv/linux/dl-realpath.c: New file. Handle symlinks
	unlike generic implementation.
---
 elf/Makefile                          |   3 +-
 elf/dl-load.c                         |  18 +++++
 elf/dl-object.c                       |  54 ++-------------
 elf/dl-realpath.c                     |  85 ++++++++++++++++++++++++
 sysdeps/tile/dl-runtime.c             |  66 +------------------
 sysdeps/unix/sysv/linux/Makefile      |   2 +-
 sysdeps/unix/sysv/linux/dl-lxstat64.c |   1 +
 sysdeps/unix/sysv/linux/dl-realpath.c | 121 ++++++++++++++++++++++++++++++++++
 8 files changed, 236 insertions(+), 114 deletions(-)
 create mode 100644 elf/dl-realpath.c
 create mode 100644 sysdeps/unix/sysv/linux/dl-lxstat64.c
 create mode 100644 sysdeps/unix/sysv/linux/dl-realpath.c

Comments

Brennan Shacklett Aug. 26, 2014, 7:37 p.m. UTC | #1
On Tue, Aug 12, 2014 at 09:16:49AM -0700, Brennan Shacklett wrote:
> Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
> for shared libraries. Currently $ORIGIN is calculated correctly for executables
> on linux, because readlink is called on /proc/self/exe, which means the
> resulting path is absolute and has no symlinks. 
> Shared libraries with relative paths on the other hand are based 
> off of appending the name / path of the library to the current working 
> directory, which means if the library is a symlink, it is not followed, which 
> breaks RPATH $ORIGIN in the following scenario:
> 
> libone.so and libtwo.so are both in ~/lib
> libone.so needs libtwo.so, so libone.so has an RPATH of $ORIGIN.
> If I run ldd on ~/lib/libone.so, libtwo.so is found and all is good.
> If I create a symlink named ~/libone.so to ~/lib/libone.so, and run ldd on it
> libtwo is not found, because $ORIGIN for the library is calculated as ~
> instead of ~/lib.
> 
> If I was to repeat the above test but instead of libone.so use an executable,
> everything would work as expected, which is why I think the shared library
> behavior is a bug. 
> 
> I ran into this in the real world when attempting to dynamically load a .so
> for python SWIG bindings, because the .so which python was loading was
> symlinked to the actual library directory where all of the .so's dependencies
> were located.
> 
> The attached patch fixes this behavior by having realname evaluated with
> __realpath in _dl_map_object_from_fd, before _dl_new_object is called (which is
> where l_origin is assigned). This also somewhat simplifies the code in
> elf/dl-object.c, because realname is guaranteed to be an absolute path
> generated by realpath, so I was able to remove the code dealing with relative 
> paths.
> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
> generic implementation, and extended it with lstat and readlink in the linux
> version. This means the bug is still present on other systems than linux (the
> generic implementation only returns an absolute path, it doesn't do anything
> with symlinks), but if there is a way to get the generic version to follow
> symlinks on all systems please let me know.
> 
> I tested and ran the elf test suite on Gentoo Linux x86-64. If this change is
> wanted, I will happily write a test to go along with it.
> I don't have any sort of copyright attribution set up with the FSF, let me know
> if it is necessary for this change.
> 
> Thanks,
> Brennan Shacklett
> 
> 2014-08-12  Brennan Shacklett  <bpshacklett@gmail.com>
> 
> 	[BZ #17251]
> 	* elf/Makefile (rtld-routines): Add dl-realpath.
> 	* elf/dl-load.c (_dl_map_object_from_fd): 
> 	Calculate realname with __realpath.
> 	* elf/dl-object.c (_dl_new_object): Remove relative path handling.
> 	* elf/dl-realpath.c: New file. Generic implementation of __realpath.
> 	* elf/tile/dl-runtime.c (_dl_after_load): Remove dl_realpath and 
> 	change call to __realpath.
> 	* sysdeps/unix/sysv/linux/Makefile (sysdep-rtld-routines):
> 	Add dl-lxstat64.
> 	* sysdeps/unix/sysv/linux/dl-lxstat64.c: New file.
> 	* sysdeps/unix/sysv/linux/dl-realpath.c: New file. Handle symlinks
> 	unlike generic implementation.
> ---
>  elf/Makefile                          |   3 +-
>  elf/dl-load.c                         |  18 +++++
>  elf/dl-object.c                       |  54 ++-------------
>  elf/dl-realpath.c                     |  85 ++++++++++++++++++++++++
>  sysdeps/tile/dl-runtime.c             |  66 +------------------
>  sysdeps/unix/sysv/linux/Makefile      |   2 +-
>  sysdeps/unix/sysv/linux/dl-lxstat64.c |   1 +
>  sysdeps/unix/sysv/linux/dl-realpath.c | 121 ++++++++++++++++++++++++++++++++++
>  8 files changed, 236 insertions(+), 114 deletions(-)
>  create mode 100644 elf/dl-realpath.c
>  create mode 100644 sysdeps/unix/sysv/linux/dl-lxstat64.c
>  create mode 100644 sysdeps/unix/sysv/linux/dl-realpath.c
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index 25012cc..c130f21 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -43,7 +43,8 @@ shared-only-routines += dl-caller
>  
>  # ld.so uses those routines, plus some special stuff for being the program
>  # interpreter and operating independent of libc.
> -rtld-routines	:= rtld $(dl-routines) dl-sysdep dl-environ dl-minimal
> +rtld-routines	:= rtld $(dl-routines) dl-sysdep dl-environ dl-minimal \
> +		   dl-realpath
>  all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
>  
>  CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> index 016a99c..7353837 100644
> --- a/elf/dl-load.c
> +++ b/elf/dl-load.c
> @@ -891,6 +891,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>    int errval = 0;
>    struct r_debug *r = _dl_debug_initialize (0, nsid);
>    bool make_consistent = false;
> +  char *absolutename = NULL;
>  
>    /* Get file information.  */
>    if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &st) < 0))
> @@ -902,6 +903,23 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>        lose (errval, fd, name, realname, l, errstring,
>  	    make_consistent ? r : NULL, nsid);
>      }
> +  /* Find absolute pathname for object */
> +  absolutename = (char *) malloc (PATH_MAX);
> +  if (!absolutename)
> +    {
> +      errstring = N_("cannot allocate memory for absolute path");
> +      goto call_lose_errno;
> +    }
> +
> +  if (!__realpath (realname, absolutename))
> +    {
> +      free (absolutename);
> +      errstring = N_("cannot find absolute path of shared object");
> +      goto call_lose_errno;
> +    }
> +
> +  free (realname);
> +  realname = absolutename;
>  
>    /* Look again to see if the real name matched another already loaded.  */
>    for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
> diff --git a/elf/dl-object.c b/elf/dl-object.c
> index afd80a6..d93935b 100644
> --- a/elf/dl-object.c
> +++ b/elf/dl-object.c
> @@ -158,55 +158,15 @@ _dl_new_object (char *realname, const char *libname, int type,
>        char *origin;
>        char *cp;
>  
> -      if (realname[0] == '/')
> +      /* It is an absolute path, calculated by realpath. Use it.  
> +       * But we have to make a copy since we strip out the trailing slash. */
> +      assert (realname[0] == '/');
> +      cp = origin = (char *) malloc (realname_len);
> +      if (origin == NULL)
>  	{
> -	  /* It is an absolute path.  Use it.  But we have to make a
> -	     copy since we strip out the trailing slash.  */
> -	  cp = origin = (char *) malloc (realname_len);
> -	  if (origin == NULL)
> -	    {
> -	      origin = (char *) -1;
> -	      goto out;
> -	    }
> +	  origin = (char *) -1;
> +	  goto out;
>  	}
> -      else
> -	{
> -	  size_t len = realname_len;
> -	  char *result = NULL;
> -
> -	  /* Get the current directory name.  */
> -	  origin = NULL;
> -	  do
> -	    {
> -	      char *new_origin;
> -
> -	      len += 128;
> -	      new_origin = (char *) realloc (origin, len);
> -	      if (new_origin == NULL)
> -		/* We exit the loop.  Note that result == NULL.  */
> -		break;
> -	      origin = new_origin;
> -	    }
> -	  while ((result = __getcwd (origin, len - realname_len)) == NULL
> -		 && errno == ERANGE);
> -
> -	  if (result == NULL)
> -	    {
> -	      /* We were not able to determine the current directory.
> -		 Note that free(origin) is OK if origin == NULL.  */
> -	      free (origin);
> -	      origin = (char *) -1;
> -	      goto out;
> -	    }
> -
> -	  /* Find the end of the path and see whether we have to add a
> -	     slash.  We could use rawmemchr but this need not be
> -	     fast.  */
> -	  cp = (strchr) (origin, '\0');
> -	  if (cp[-1] != '/')
> -	    *cp++ = '/';
> -	}
> -
>        /* Add the real file name.  */
>        cp = __mempcpy (cp, realname, realname_len);
>  
> diff --git a/elf/dl-realpath.c b/elf/dl-realpath.c
> new file mode 100644
> index 0000000..2549bb3
> --- /dev/null
> +++ b/elf/dl-realpath.c
> @@ -0,0 +1,85 @@
> +/* Dynamic loader version of realpath.
> +   Copyright (C) 2014 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <limits.h>
> +#include <ldsodefs.h>
> +
> +/* Simplified implementation of realpath(): no dynamic memory use, no lstat(),
> +   no set_errno(), no valid "rpath" on error, etc.  This simplifies cases
> +   involving relative paths, specifically where $ORIGIN needs to be 
> +   calculated. For this relatively rare case, one could also imagine using
> +   link_map.l_origin to avoid the getcwd() here, but the simpler code
> +   here seems like a better solution.  */
> +char * internal_function
> +__realpath (const char *name, char *rpath)
> +{
> +  char *dest;
> +  const char *start, *end;
> +
> +  if (name[0] != '/')
> +    {
> +      if (!__getcwd (rpath, PATH_MAX))
> +	return NULL;
> +      dest = __rawmemchr (rpath, '\0');
> +    }
> +  else
> +    {
> +      rpath[0] = '/';
> +      dest = rpath + 1;
> +    }
> +
> +  for (start = end = name; *start; start = end)
> +    {
> +      /* Skip sequence of multiple path-separators.  */
> +      while (*start == '/')
> +	++start;
> +
> +      /* Find end of path component.  */
> +      for (end = start; *end && *end != '/'; ++end)
> +	/* Nothing.  */;
> +
> +      if (end - start == 0)
> +	break;
> +      else if (end - start == 1 && start[0] == '.')
> +	/* nothing */;
> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> +	{
> +	  /* Back up to previous component, ignore if at root already.  */
> +	  if (dest > rpath + 1)
> +	    while ((--dest)[-1] != '/');
> +	}
> +      else
> +	{
> +	  if (dest[-1] != '/')
> +	    *dest++ = '/';
> +
> +	  if (dest + (end - start) >= rpath + PATH_MAX)
> +	    return NULL;
> +
> +	  dest = __mempcpy (dest, start, end - start);
> +	  *dest = '\0';
> +	}
> +    }
> +  if (dest > rpath + 1 && dest[-1] == '/')
> +    --dest;
> +  *dest = '\0';
> +
> +  return rpath;
> +}
> diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
> index bcc00bc..45251d0 100644
> --- a/sysdeps/tile/dl-runtime.c
> +++ b/sysdeps/tile/dl-runtime.c
> @@ -29,70 +29,6 @@
>  #include <arch/sim.h>
>  #include <dl-unmap-segments.h>
>  
> -/* Like realpath(), but simplified: no dynamic memory use, no lstat(),
> -   no set_errno(), no valid "rpath" on error, etc.  This handles some
> -   simple cases where the simulator might not have a valid entry for
> -   a loaded Elf object, in particular dlopen() with a relative path.
> -   For this relatively rare case, one could also imagine using
> -   link_map.l_origin to avoid the getcwd() here, but the simpler code
> -   here seems like a better solution.  */
> -static char *
> -dl_realpath (const char *name, char *rpath)
> -{
> -  char *dest;
> -  const char *start, *end;
> -
> -  if (name[0] != '/')
> -    {
> -      if (!__getcwd (rpath, PATH_MAX))
> -        return NULL;
> -      dest = __rawmemchr (rpath, '\0');
> -    }
> -  else
> -    {
> -      rpath[0] = '/';
> -      dest = rpath + 1;
> -    }
> -
> -  for (start = end = name; *start; start = end)
> -    {
> -      /* Skip sequence of multiple path-separators.  */
> -      while (*start == '/')
> -	++start;
> -
> -      /* Find end of path component.  */
> -      for (end = start; *end && *end != '/'; ++end)
> -	/* Nothing.  */;
> -
> -      if (end - start == 0)
> -	break;
> -      else if (end - start == 1 && start[0] == '.')
> -	/* nothing */;
> -      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> -	{
> -	  /* Back up to previous component, ignore if at root already.  */
> -	  if (dest > rpath + 1)
> -	    while ((--dest)[-1] != '/');
> -	}
> -      else
> -	{
> -	  if (dest[-1] != '/')
> -	    *dest++ = '/';
> -
> -	  if (dest + (end - start) >= rpath + PATH_MAX)
> -            return NULL;
> -
> -	  dest = __mempcpy (dest, start, end - start);
> -	  *dest = '\0';
> -	}
> -    }
> -  if (dest > rpath + 1 && dest[-1] == '/')
> -    --dest;
> -  *dest = '\0';
> -
> -  return rpath;
> -}
> -
>  /* Support notifying the simulator about new objects.  */
>  void internal_function
>  _dl_after_load (struct link_map *l)
> @@ -117,7 +53,7 @@ _dl_after_load (struct link_map *l)
>    DLPUTC (':');
>  
>    /* Write the library path, including the terminating '\0'.  */
> -  path = dl_realpath (l->l_name, pathbuf) ?: l->l_name;
> +  path = __realpath (l->l_name, pathbuf) ?: l->l_name;
>    for (size_t i = 0;; i++)
>      {
>        DLPUTC (path[i]);
> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
> index 9ad6d22..4525429 100644
> --- a/sysdeps/unix/sysv/linux/Makefile
> +++ b/sysdeps/unix/sysv/linux/Makefile
> @@ -176,7 +176,7 @@ endif
>  
>  ifeq ($(subdir),elf)
>  sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \
> -			dl-fxstatat64
> +			dl-fxstatat64 dl-lxstat64
>  
>  CPPFLAGS-lddlibc4 += -DNOT_IN_libc
>  
> diff --git a/sysdeps/unix/sysv/linux/dl-lxstat64.c b/sysdeps/unix/sysv/linux/dl-lxstat64.c
> new file mode 100644
> index 0000000..63e6800
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/dl-lxstat64.c
> @@ -0,0 +1 @@
> +#include <lxstat64.c>
> diff --git a/sysdeps/unix/sysv/linux/dl-realpath.c b/sysdeps/unix/sysv/linux/dl-realpath.c
> new file mode 100644
> index 0000000..91652dc
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/dl-realpath.c
> @@ -0,0 +1,121 @@
> +/* Dynamic loader version of realpath for linux.
> +   Copyright (C) 2014 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <limits.h>
> +#include <ldsodefs.h>
> +#include <sys/stat.h>
> +#include <kernel-features.h>
> +#include <sysdep.h>
> +
> +/* Simplified version of realpath which extends elf/dl-realpath.c to use
> + * linux syscalls for handling symlinks */
> +char * internal_function
> +__realpath (const char *name, char *rpath)
> +{
> +  char *dest;
> +  char extra_buf[PATH_MAX];
> +  const char *start, *end;
> +  int num_links = 0;
> +
> +  if (name[0] != '/')
> +    {
> +      if (!__getcwd (rpath, PATH_MAX))
> +	return NULL;
> +      dest = __rawmemchr (rpath, '\0');
> +    }
> +  else
> +    {
> +      rpath[0] = '/';
> +      dest = rpath + 1;
> +    }
> +
> +  for (start = end = name; *start; start = end)
> +    {
> +      struct stat64 st;
> +      int n;
> +      /* Skip sequence of multiple path-separators.  */
> +      while (*start == '/')
> +	++start;
> +
> +      /* Find end of path component.  */
> +      for (end = start; *end && *end != '/'; ++end)
> +	/* Nothing.  */;
> +
> +      if (end - start == 0)
> +	break;
> +      else if (end - start == 1 && start[0] == '.')
> +	/* nothing */;
> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> +	{
> +	  /* Back up to previous component, ignore if at root already.  */
> +	  if (dest > rpath + 1)
> +	    while ((--dest)[-1] != '/');
> +	}
> +      else
> +	{
> +	  if (dest[-1] != '/')
> +	    *dest++ = '/';
> +
> +	  if (dest + (end - start) >= rpath + PATH_MAX)
> +	    return NULL;
> +
> +	  dest = __mempcpy (dest, start, end - start);
> +	  *dest = '\0';
> +	  if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
> +	    return NULL;
> +
> +	  if (S_ISLNK (st.st_mode))
> +	    {
> +	      char buf[PATH_MAX];
> +	      size_t len;
> +
> +	      INTERNAL_SYSCALL_DECL (err);
> +
> +	      n = INTERNAL_SYSCALL (readlink, err, 3, rpath, buf, 
> +				    PATH_MAX - 1);
> +
> +	      if (n <= 0 || buf[0] == '[')
> +		return NULL;
> +	      buf[n] = '\0';
> +
> +	      len = strlen (end);
> +	      if ((long int) (n + len) >= PATH_MAX)
> +		return NULL;
> +
> +	      /* Careful here, end may be a pointer into extra_buf... */
> +	      memmove (&extra_buf[n], end, len + 1);
> +	      name = end = memcpy (extra_buf, buf, n);
> +
> +	      if (buf[0] == '/')
> +		dest = rpath + 1;     /* Absolute symlink */
> +	      else 
> +		/* Back up to previous component, ignore if at root already: */
> +		if (dest > rpath + 1)
> +		  while ((--dest)[-1] != '/');
> +	    }
> +	}
> +    }
> +  if (dest > rpath + 1 && dest[-1] == '/')
> +    --dest;
> +  *dest = '\0';
> +
> +  return rpath;
> +
> +}
> -- 
> 2.0.4
> 

ping...
Brennan Shacklett Sept. 2, 2014, 7:06 p.m. UTC | #2
On Tue, Aug 12, 2014 at 09:16:49AM -0700, Brennan Shacklett wrote:
> Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
> for shared libraries. Currently $ORIGIN is calculated correctly for executables
> on linux, because readlink is called on /proc/self/exe, which means the
> resulting path is absolute and has no symlinks. 
> Shared libraries with relative paths on the other hand are based 
> off of appending the name / path of the library to the current working 
> directory, which means if the library is a symlink, it is not followed, which 
> breaks RPATH $ORIGIN in the following scenario:
> 
> libone.so and libtwo.so are both in ~/lib
> libone.so needs libtwo.so, so libone.so has an RPATH of $ORIGIN.
> If I run ldd on ~/lib/libone.so, libtwo.so is found and all is good.
> If I create a symlink named ~/libone.so to ~/lib/libone.so, and run ldd on it
> libtwo is not found, because $ORIGIN for the library is calculated as ~
> instead of ~/lib.
> 
> If I was to repeat the above test but instead of libone.so use an executable,
> everything would work as expected, which is why I think the shared library
> behavior is a bug. 
> 
> I ran into this in the real world when attempting to dynamically load a .so
> for python SWIG bindings, because the .so which python was loading was
> symlinked to the actual library directory where all of the .so's dependencies
> were located.
> 
> The attached patch fixes this behavior by having realname evaluated with
> __realpath in _dl_map_object_from_fd, before _dl_new_object is called (which is
> where l_origin is assigned). This also somewhat simplifies the code in
> elf/dl-object.c, because realname is guaranteed to be an absolute path
> generated by realpath, so I was able to remove the code dealing with relative 
> paths.
> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
> generic implementation, and extended it with lstat and readlink in the linux
> version. This means the bug is still present on other systems than linux (the
> generic implementation only returns an absolute path, it doesn't do anything
> with symlinks), but if there is a way to get the generic version to follow
> symlinks on all systems please let me know.
> 
> I tested and ran the elf test suite on Gentoo Linux x86-64. If this change is
> wanted, I will happily write a test to go along with it.
> I don't have any sort of copyright attribution set up with the FSF, let me know
> if it is necessary for this change.
> 
> Thanks,
> Brennan Shacklett
> 
> 2014-08-12  Brennan Shacklett  <bpshacklett@gmail.com>
> 
> 	[BZ #17251]
> 	* elf/Makefile (rtld-routines): Add dl-realpath.
> 	* elf/dl-load.c (_dl_map_object_from_fd): 
> 	Calculate realname with __realpath.
> 	* elf/dl-object.c (_dl_new_object): Remove relative path handling.
> 	* elf/dl-realpath.c: New file. Generic implementation of __realpath.
> 	* elf/tile/dl-runtime.c (_dl_after_load): Remove dl_realpath and 
> 	change call to __realpath.
> 	* sysdeps/unix/sysv/linux/Makefile (sysdep-rtld-routines):
> 	Add dl-lxstat64.
> 	* sysdeps/unix/sysv/linux/dl-lxstat64.c: New file.
> 	* sysdeps/unix/sysv/linux/dl-realpath.c: New file. Handle symlinks
> 	unlike generic implementation.
> ---
>  elf/Makefile                          |   3 +-
>  elf/dl-load.c                         |  18 +++++
>  elf/dl-object.c                       |  54 ++-------------
>  elf/dl-realpath.c                     |  85 ++++++++++++++++++++++++
>  sysdeps/tile/dl-runtime.c             |  66 +------------------
>  sysdeps/unix/sysv/linux/Makefile      |   2 +-
>  sysdeps/unix/sysv/linux/dl-lxstat64.c |   1 +
>  sysdeps/unix/sysv/linux/dl-realpath.c | 121 ++++++++++++++++++++++++++++++++++
>  8 files changed, 236 insertions(+), 114 deletions(-)
>  create mode 100644 elf/dl-realpath.c
>  create mode 100644 sysdeps/unix/sysv/linux/dl-lxstat64.c
>  create mode 100644 sysdeps/unix/sysv/linux/dl-realpath.c
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index 25012cc..c130f21 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -43,7 +43,8 @@ shared-only-routines += dl-caller
>  
>  # ld.so uses those routines, plus some special stuff for being the program
>  # interpreter and operating independent of libc.
> -rtld-routines	:= rtld $(dl-routines) dl-sysdep dl-environ dl-minimal
> +rtld-routines	:= rtld $(dl-routines) dl-sysdep dl-environ dl-minimal \
> +		   dl-realpath
>  all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
>  
>  CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> index 016a99c..7353837 100644
> --- a/elf/dl-load.c
> +++ b/elf/dl-load.c
> @@ -891,6 +891,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>    int errval = 0;
>    struct r_debug *r = _dl_debug_initialize (0, nsid);
>    bool make_consistent = false;
> +  char *absolutename = NULL;
>  
>    /* Get file information.  */
>    if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &st) < 0))
> @@ -902,6 +903,23 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>        lose (errval, fd, name, realname, l, errstring,
>  	    make_consistent ? r : NULL, nsid);
>      }
> +  /* Find absolute pathname for object */
> +  absolutename = (char *) malloc (PATH_MAX);
> +  if (!absolutename)
> +    {
> +      errstring = N_("cannot allocate memory for absolute path");
> +      goto call_lose_errno;
> +    }
> +
> +  if (!__realpath (realname, absolutename))
> +    {
> +      free (absolutename);
> +      errstring = N_("cannot find absolute path of shared object");
> +      goto call_lose_errno;
> +    }
> +
> +  free (realname);
> +  realname = absolutename;
>  
>    /* Look again to see if the real name matched another already loaded.  */
>    for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
> diff --git a/elf/dl-object.c b/elf/dl-object.c
> index afd80a6..d93935b 100644
> --- a/elf/dl-object.c
> +++ b/elf/dl-object.c
> @@ -158,55 +158,15 @@ _dl_new_object (char *realname, const char *libname, int type,
>        char *origin;
>        char *cp;
>  
> -      if (realname[0] == '/')
> +      /* It is an absolute path, calculated by realpath. Use it.  
> +       * But we have to make a copy since we strip out the trailing slash. */
> +      assert (realname[0] == '/');
> +      cp = origin = (char *) malloc (realname_len);
> +      if (origin == NULL)
>  	{
> -	  /* It is an absolute path.  Use it.  But we have to make a
> -	     copy since we strip out the trailing slash.  */
> -	  cp = origin = (char *) malloc (realname_len);
> -	  if (origin == NULL)
> -	    {
> -	      origin = (char *) -1;
> -	      goto out;
> -	    }
> +	  origin = (char *) -1;
> +	  goto out;
>  	}
> -      else
> -	{
> -	  size_t len = realname_len;
> -	  char *result = NULL;
> -
> -	  /* Get the current directory name.  */
> -	  origin = NULL;
> -	  do
> -	    {
> -	      char *new_origin;
> -
> -	      len += 128;
> -	      new_origin = (char *) realloc (origin, len);
> -	      if (new_origin == NULL)
> -		/* We exit the loop.  Note that result == NULL.  */
> -		break;
> -	      origin = new_origin;
> -	    }
> -	  while ((result = __getcwd (origin, len - realname_len)) == NULL
> -		 && errno == ERANGE);
> -
> -	  if (result == NULL)
> -	    {
> -	      /* We were not able to determine the current directory.
> -		 Note that free(origin) is OK if origin == NULL.  */
> -	      free (origin);
> -	      origin = (char *) -1;
> -	      goto out;
> -	    }
> -
> -	  /* Find the end of the path and see whether we have to add a
> -	     slash.  We could use rawmemchr but this need not be
> -	     fast.  */
> -	  cp = (strchr) (origin, '\0');
> -	  if (cp[-1] != '/')
> -	    *cp++ = '/';
> -	}
> -
>        /* Add the real file name.  */
>        cp = __mempcpy (cp, realname, realname_len);
>  
> diff --git a/elf/dl-realpath.c b/elf/dl-realpath.c
> new file mode 100644
> index 0000000..2549bb3
> --- /dev/null
> +++ b/elf/dl-realpath.c
> @@ -0,0 +1,85 @@
> +/* Dynamic loader version of realpath.
> +   Copyright (C) 2014 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <limits.h>
> +#include <ldsodefs.h>
> +
> +/* Simplified implementation of realpath(): no dynamic memory use, no lstat(),
> +   no set_errno(), no valid "rpath" on error, etc.  This simplifies cases
> +   involving relative paths, specifically where $ORIGIN needs to be 
> +   calculated. For this relatively rare case, one could also imagine using
> +   link_map.l_origin to avoid the getcwd() here, but the simpler code
> +   here seems like a better solution.  */
> +char * internal_function
> +__realpath (const char *name, char *rpath)
> +{
> +  char *dest;
> +  const char *start, *end;
> +
> +  if (name[0] != '/')
> +    {
> +      if (!__getcwd (rpath, PATH_MAX))
> +	return NULL;
> +      dest = __rawmemchr (rpath, '\0');
> +    }
> +  else
> +    {
> +      rpath[0] = '/';
> +      dest = rpath + 1;
> +    }
> +
> +  for (start = end = name; *start; start = end)
> +    {
> +      /* Skip sequence of multiple path-separators.  */
> +      while (*start == '/')
> +	++start;
> +
> +      /* Find end of path component.  */
> +      for (end = start; *end && *end != '/'; ++end)
> +	/* Nothing.  */;
> +
> +      if (end - start == 0)
> +	break;
> +      else if (end - start == 1 && start[0] == '.')
> +	/* nothing */;
> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> +	{
> +	  /* Back up to previous component, ignore if at root already.  */
> +	  if (dest > rpath + 1)
> +	    while ((--dest)[-1] != '/');
> +	}
> +      else
> +	{
> +	  if (dest[-1] != '/')
> +	    *dest++ = '/';
> +
> +	  if (dest + (end - start) >= rpath + PATH_MAX)
> +	    return NULL;
> +
> +	  dest = __mempcpy (dest, start, end - start);
> +	  *dest = '\0';
> +	}
> +    }
> +  if (dest > rpath + 1 && dest[-1] == '/')
> +    --dest;
> +  *dest = '\0';
> +
> +  return rpath;
> +}
> diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
> index bcc00bc..45251d0 100644
> --- a/sysdeps/tile/dl-runtime.c
> +++ b/sysdeps/tile/dl-runtime.c
> @@ -29,70 +29,6 @@
>  #include <arch/sim.h>
>  #include <dl-unmap-segments.h>
>  
> -/* Like realpath(), but simplified: no dynamic memory use, no lstat(),
> -   no set_errno(), no valid "rpath" on error, etc.  This handles some
> -   simple cases where the simulator might not have a valid entry for
> -   a loaded Elf object, in particular dlopen() with a relative path.
> -   For this relatively rare case, one could also imagine using
> -   link_map.l_origin to avoid the getcwd() here, but the simpler code
> -   here seems like a better solution.  */
> -static char *
> -dl_realpath (const char *name, char *rpath)
> -{
> -  char *dest;
> -  const char *start, *end;
> -
> -  if (name[0] != '/')
> -    {
> -      if (!__getcwd (rpath, PATH_MAX))
> -        return NULL;
> -      dest = __rawmemchr (rpath, '\0');
> -    }
> -  else
> -    {
> -      rpath[0] = '/';
> -      dest = rpath + 1;
> -    }
> -
> -  for (start = end = name; *start; start = end)
> -    {
> -      /* Skip sequence of multiple path-separators.  */
> -      while (*start == '/')
> -	++start;
> -
> -      /* Find end of path component.  */
> -      for (end = start; *end && *end != '/'; ++end)
> -	/* Nothing.  */;
> -
> -      if (end - start == 0)
> -	break;
> -      else if (end - start == 1 && start[0] == '.')
> -	/* nothing */;
> -      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> -	{
> -	  /* Back up to previous component, ignore if at root already.  */
> -	  if (dest > rpath + 1)
> -	    while ((--dest)[-1] != '/');
> -	}
> -      else
> -	{
> -	  if (dest[-1] != '/')
> -	    *dest++ = '/';
> -
> -	  if (dest + (end - start) >= rpath + PATH_MAX)
> -            return NULL;
> -
> -	  dest = __mempcpy (dest, start, end - start);
> -	  *dest = '\0';
> -	}
> -    }
> -  if (dest > rpath + 1 && dest[-1] == '/')
> -    --dest;
> -  *dest = '\0';
> -
> -  return rpath;
> -}
> -
>  /* Support notifying the simulator about new objects.  */
>  void internal_function
>  _dl_after_load (struct link_map *l)
> @@ -117,7 +53,7 @@ _dl_after_load (struct link_map *l)
>    DLPUTC (':');
>  
>    /* Write the library path, including the terminating '\0'.  */
> -  path = dl_realpath (l->l_name, pathbuf) ?: l->l_name;
> +  path = __realpath (l->l_name, pathbuf) ?: l->l_name;
>    for (size_t i = 0;; i++)
>      {
>        DLPUTC (path[i]);
> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
> index 9ad6d22..4525429 100644
> --- a/sysdeps/unix/sysv/linux/Makefile
> +++ b/sysdeps/unix/sysv/linux/Makefile
> @@ -176,7 +176,7 @@ endif
>  
>  ifeq ($(subdir),elf)
>  sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \
> -			dl-fxstatat64
> +			dl-fxstatat64 dl-lxstat64
>  
>  CPPFLAGS-lddlibc4 += -DNOT_IN_libc
>  
> diff --git a/sysdeps/unix/sysv/linux/dl-lxstat64.c b/sysdeps/unix/sysv/linux/dl-lxstat64.c
> new file mode 100644
> index 0000000..63e6800
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/dl-lxstat64.c
> @@ -0,0 +1 @@
> +#include <lxstat64.c>
> diff --git a/sysdeps/unix/sysv/linux/dl-realpath.c b/sysdeps/unix/sysv/linux/dl-realpath.c
> new file mode 100644
> index 0000000..91652dc
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/dl-realpath.c
> @@ -0,0 +1,121 @@
> +/* Dynamic loader version of realpath for linux.
> +   Copyright (C) 2014 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <limits.h>
> +#include <ldsodefs.h>
> +#include <sys/stat.h>
> +#include <kernel-features.h>
> +#include <sysdep.h>
> +
> +/* Simplified version of realpath which extends elf/dl-realpath.c to use
> + * linux syscalls for handling symlinks */
> +char * internal_function
> +__realpath (const char *name, char *rpath)
> +{
> +  char *dest;
> +  char extra_buf[PATH_MAX];
> +  const char *start, *end;
> +  int num_links = 0;
> +
> +  if (name[0] != '/')
> +    {
> +      if (!__getcwd (rpath, PATH_MAX))
> +	return NULL;
> +      dest = __rawmemchr (rpath, '\0');
> +    }
> +  else
> +    {
> +      rpath[0] = '/';
> +      dest = rpath + 1;
> +    }
> +
> +  for (start = end = name; *start; start = end)
> +    {
> +      struct stat64 st;
> +      int n;
> +      /* Skip sequence of multiple path-separators.  */
> +      while (*start == '/')
> +	++start;
> +
> +      /* Find end of path component.  */
> +      for (end = start; *end && *end != '/'; ++end)
> +	/* Nothing.  */;
> +
> +      if (end - start == 0)
> +	break;
> +      else if (end - start == 1 && start[0] == '.')
> +	/* nothing */;
> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> +	{
> +	  /* Back up to previous component, ignore if at root already.  */
> +	  if (dest > rpath + 1)
> +	    while ((--dest)[-1] != '/');
> +	}
> +      else
> +	{
> +	  if (dest[-1] != '/')
> +	    *dest++ = '/';
> +
> +	  if (dest + (end - start) >= rpath + PATH_MAX)
> +	    return NULL;
> +
> +	  dest = __mempcpy (dest, start, end - start);
> +	  *dest = '\0';
> +	  if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
> +	    return NULL;
> +
> +	  if (S_ISLNK (st.st_mode))
> +	    {
> +	      char buf[PATH_MAX];
> +	      size_t len;
> +
> +	      INTERNAL_SYSCALL_DECL (err);
> +
> +	      n = INTERNAL_SYSCALL (readlink, err, 3, rpath, buf, 
> +				    PATH_MAX - 1);
> +
> +	      if (n <= 0 || buf[0] == '[')
> +		return NULL;
> +	      buf[n] = '\0';
> +
> +	      len = strlen (end);
> +	      if ((long int) (n + len) >= PATH_MAX)
> +		return NULL;
> +
> +	      /* Careful here, end may be a pointer into extra_buf... */
> +	      memmove (&extra_buf[n], end, len + 1);
> +	      name = end = memcpy (extra_buf, buf, n);
> +
> +	      if (buf[0] == '/')
> +		dest = rpath + 1;     /* Absolute symlink */
> +	      else 
> +		/* Back up to previous component, ignore if at root already: */
> +		if (dest > rpath + 1)
> +		  while ((--dest)[-1] != '/');
> +	    }
> +	}
> +    }
> +  if (dest > rpath + 1 && dest[-1] == '/')
> +    --dest;
> +  *dest = '\0';
> +
> +  return rpath;
> +
> +}
> -- 
> 2.0.4
> 

ping
Joseph Myers Sept. 2, 2014, 8:27 p.m. UTC | #3
I don't think it's particularly useful to ping during the freeze unless 
for patches it's important to get in for 2.20.  It's better to wait until 
master is open for new development after 2.20 is out, and then ping.
Brennan Shacklett Sept. 2, 2014, 10:35 p.m. UTC | #4
All right, thank you for letting me know about policies regarding the
freeze. I was just going off the contribution checklist.
--Brennan

On Sep 2, 2014 1:27 PM, "Joseph S. Myers" <joseph@codesourcery.com> wrote:
>
> I don't think it's particularly useful to ping during the freeze unless
> for patches it's important to get in for 2.20.  It's better to wait until
> master is open for new development after 2.20 is out, and then ping.
>
> --
> Joseph S. Myers
> joseph@codesourcery.com
Brennan Shacklett Sept. 18, 2014, 7:18 a.m. UTC | #5
On Tue, Aug 12, 2014 at 09:16:49AM -0700, Brennan Shacklett wrote:
> Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
> for shared libraries. Currently $ORIGIN is calculated correctly for executables
> on linux, because readlink is called on /proc/self/exe, which means the
> resulting path is absolute and has no symlinks. 
> Shared libraries with relative paths on the other hand are based 
> off of appending the name / path of the library to the current working 
> directory, which means if the library is a symlink, it is not followed, which 
> breaks RPATH $ORIGIN in the following scenario:
> 
> libone.so and libtwo.so are both in ~/lib
> libone.so needs libtwo.so, so libone.so has an RPATH of $ORIGIN.
> If I run ldd on ~/lib/libone.so, libtwo.so is found and all is good.
> If I create a symlink named ~/libone.so to ~/lib/libone.so, and run ldd on it
> libtwo is not found, because $ORIGIN for the library is calculated as ~
> instead of ~/lib.
> 
> If I was to repeat the above test but instead of libone.so use an executable,
> everything would work as expected, which is why I think the shared library
> behavior is a bug. 
> 
> I ran into this in the real world when attempting to dynamically load a .so
> for python SWIG bindings, because the .so which python was loading was
> symlinked to the actual library directory where all of the .so's dependencies
> were located.
> 
> The attached patch fixes this behavior by having realname evaluated with
> __realpath in _dl_map_object_from_fd, before _dl_new_object is called (which is
> where l_origin is assigned). This also somewhat simplifies the code in
> elf/dl-object.c, because realname is guaranteed to be an absolute path
> generated by realpath, so I was able to remove the code dealing with relative 
> paths.
> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
> generic implementation, and extended it with lstat and readlink in the linux
> version. This means the bug is still present on other systems than linux (the
> generic implementation only returns an absolute path, it doesn't do anything
> with symlinks), but if there is a way to get the generic version to follow
> symlinks on all systems please let me know.
> 
> I tested and ran the elf test suite on Gentoo Linux x86-64. If this change is
> wanted, I will happily write a test to go along with it.
> I don't have any sort of copyright attribution set up with the FSF, let me know
> if it is necessary for this change.
> 
> Thanks,
> Brennan Shacklett
> 
> 2014-08-12  Brennan Shacklett  <bpshacklett@gmail.com>
> 
> 	[BZ #17251]
> 	* elf/Makefile (rtld-routines): Add dl-realpath.
> 	* elf/dl-load.c (_dl_map_object_from_fd): 
> 	Calculate realname with __realpath.
> 	* elf/dl-object.c (_dl_new_object): Remove relative path handling.
> 	* elf/dl-realpath.c: New file. Generic implementation of __realpath.
> 	* elf/tile/dl-runtime.c (_dl_after_load): Remove dl_realpath and 
> 	change call to __realpath.
> 	* sysdeps/unix/sysv/linux/Makefile (sysdep-rtld-routines):
> 	Add dl-lxstat64.
> 	* sysdeps/unix/sysv/linux/dl-lxstat64.c: New file.
> 	* sysdeps/unix/sysv/linux/dl-realpath.c: New file. Handle symlinks
> 	unlike generic implementation.
> ---
>  elf/Makefile                          |   3 +-
>  elf/dl-load.c                         |  18 +++++
>  elf/dl-object.c                       |  54 ++-------------
>  elf/dl-realpath.c                     |  85 ++++++++++++++++++++++++
>  sysdeps/tile/dl-runtime.c             |  66 +------------------
>  sysdeps/unix/sysv/linux/Makefile      |   2 +-
>  sysdeps/unix/sysv/linux/dl-lxstat64.c |   1 +
>  sysdeps/unix/sysv/linux/dl-realpath.c | 121 ++++++++++++++++++++++++++++++++++
>  8 files changed, 236 insertions(+), 114 deletions(-)
>  create mode 100644 elf/dl-realpath.c
>  create mode 100644 sysdeps/unix/sysv/linux/dl-lxstat64.c
>  create mode 100644 sysdeps/unix/sysv/linux/dl-realpath.c
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index 25012cc..c130f21 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -43,7 +43,8 @@ shared-only-routines += dl-caller
>  
>  # ld.so uses those routines, plus some special stuff for being the program
>  # interpreter and operating independent of libc.
> -rtld-routines	:= rtld $(dl-routines) dl-sysdep dl-environ dl-minimal
> +rtld-routines	:= rtld $(dl-routines) dl-sysdep dl-environ dl-minimal \
> +		   dl-realpath
>  all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
>  
>  CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> index 016a99c..7353837 100644
> --- a/elf/dl-load.c
> +++ b/elf/dl-load.c
> @@ -891,6 +891,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>    int errval = 0;
>    struct r_debug *r = _dl_debug_initialize (0, nsid);
>    bool make_consistent = false;
> +  char *absolutename = NULL;
>  
>    /* Get file information.  */
>    if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &st) < 0))
> @@ -902,6 +903,23 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>        lose (errval, fd, name, realname, l, errstring,
>  	    make_consistent ? r : NULL, nsid);
>      }
> +  /* Find absolute pathname for object */
> +  absolutename = (char *) malloc (PATH_MAX);
> +  if (!absolutename)
> +    {
> +      errstring = N_("cannot allocate memory for absolute path");
> +      goto call_lose_errno;
> +    }
> +
> +  if (!__realpath (realname, absolutename))
> +    {
> +      free (absolutename);
> +      errstring = N_("cannot find absolute path of shared object");
> +      goto call_lose_errno;
> +    }
> +
> +  free (realname);
> +  realname = absolutename;
>  
>    /* Look again to see if the real name matched another already loaded.  */
>    for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
> diff --git a/elf/dl-object.c b/elf/dl-object.c
> index afd80a6..d93935b 100644
> --- a/elf/dl-object.c
> +++ b/elf/dl-object.c
> @@ -158,55 +158,15 @@ _dl_new_object (char *realname, const char *libname, int type,
>        char *origin;
>        char *cp;
>  
> -      if (realname[0] == '/')
> +      /* It is an absolute path, calculated by realpath. Use it.  
> +       * But we have to make a copy since we strip out the trailing slash. */
> +      assert (realname[0] == '/');
> +      cp = origin = (char *) malloc (realname_len);
> +      if (origin == NULL)
>  	{
> -	  /* It is an absolute path.  Use it.  But we have to make a
> -	     copy since we strip out the trailing slash.  */
> -	  cp = origin = (char *) malloc (realname_len);
> -	  if (origin == NULL)
> -	    {
> -	      origin = (char *) -1;
> -	      goto out;
> -	    }
> +	  origin = (char *) -1;
> +	  goto out;
>  	}
> -      else
> -	{
> -	  size_t len = realname_len;
> -	  char *result = NULL;
> -
> -	  /* Get the current directory name.  */
> -	  origin = NULL;
> -	  do
> -	    {
> -	      char *new_origin;
> -
> -	      len += 128;
> -	      new_origin = (char *) realloc (origin, len);
> -	      if (new_origin == NULL)
> -		/* We exit the loop.  Note that result == NULL.  */
> -		break;
> -	      origin = new_origin;
> -	    }
> -	  while ((result = __getcwd (origin, len - realname_len)) == NULL
> -		 && errno == ERANGE);
> -
> -	  if (result == NULL)
> -	    {
> -	      /* We were not able to determine the current directory.
> -		 Note that free(origin) is OK if origin == NULL.  */
> -	      free (origin);
> -	      origin = (char *) -1;
> -	      goto out;
> -	    }
> -
> -	  /* Find the end of the path and see whether we have to add a
> -	     slash.  We could use rawmemchr but this need not be
> -	     fast.  */
> -	  cp = (strchr) (origin, '\0');
> -	  if (cp[-1] != '/')
> -	    *cp++ = '/';
> -	}
> -
>        /* Add the real file name.  */
>        cp = __mempcpy (cp, realname, realname_len);
>  
> diff --git a/elf/dl-realpath.c b/elf/dl-realpath.c
> new file mode 100644
> index 0000000..2549bb3
> --- /dev/null
> +++ b/elf/dl-realpath.c
> @@ -0,0 +1,85 @@
> +/* Dynamic loader version of realpath.
> +   Copyright (C) 2014 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <limits.h>
> +#include <ldsodefs.h>
> +
> +/* Simplified implementation of realpath(): no dynamic memory use, no lstat(),
> +   no set_errno(), no valid "rpath" on error, etc.  This simplifies cases
> +   involving relative paths, specifically where $ORIGIN needs to be 
> +   calculated. For this relatively rare case, one could also imagine using
> +   link_map.l_origin to avoid the getcwd() here, but the simpler code
> +   here seems like a better solution.  */
> +char * internal_function
> +__realpath (const char *name, char *rpath)
> +{
> +  char *dest;
> +  const char *start, *end;
> +
> +  if (name[0] != '/')
> +    {
> +      if (!__getcwd (rpath, PATH_MAX))
> +	return NULL;
> +      dest = __rawmemchr (rpath, '\0');
> +    }
> +  else
> +    {
> +      rpath[0] = '/';
> +      dest = rpath + 1;
> +    }
> +
> +  for (start = end = name; *start; start = end)
> +    {
> +      /* Skip sequence of multiple path-separators.  */
> +      while (*start == '/')
> +	++start;
> +
> +      /* Find end of path component.  */
> +      for (end = start; *end && *end != '/'; ++end)
> +	/* Nothing.  */;
> +
> +      if (end - start == 0)
> +	break;
> +      else if (end - start == 1 && start[0] == '.')
> +	/* nothing */;
> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> +	{
> +	  /* Back up to previous component, ignore if at root already.  */
> +	  if (dest > rpath + 1)
> +	    while ((--dest)[-1] != '/');
> +	}
> +      else
> +	{
> +	  if (dest[-1] != '/')
> +	    *dest++ = '/';
> +
> +	  if (dest + (end - start) >= rpath + PATH_MAX)
> +	    return NULL;
> +
> +	  dest = __mempcpy (dest, start, end - start);
> +	  *dest = '\0';
> +	}
> +    }
> +  if (dest > rpath + 1 && dest[-1] == '/')
> +    --dest;
> +  *dest = '\0';
> +
> +  return rpath;
> +}
> diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
> index bcc00bc..45251d0 100644
> --- a/sysdeps/tile/dl-runtime.c
> +++ b/sysdeps/tile/dl-runtime.c
> @@ -29,70 +29,6 @@
>  #include <arch/sim.h>
>  #include <dl-unmap-segments.h>
>  
> -/* Like realpath(), but simplified: no dynamic memory use, no lstat(),
> -   no set_errno(), no valid "rpath" on error, etc.  This handles some
> -   simple cases where the simulator might not have a valid entry for
> -   a loaded Elf object, in particular dlopen() with a relative path.
> -   For this relatively rare case, one could also imagine using
> -   link_map.l_origin to avoid the getcwd() here, but the simpler code
> -   here seems like a better solution.  */
> -static char *
> -dl_realpath (const char *name, char *rpath)
> -{
> -  char *dest;
> -  const char *start, *end;
> -
> -  if (name[0] != '/')
> -    {
> -      if (!__getcwd (rpath, PATH_MAX))
> -        return NULL;
> -      dest = __rawmemchr (rpath, '\0');
> -    }
> -  else
> -    {
> -      rpath[0] = '/';
> -      dest = rpath + 1;
> -    }
> -
> -  for (start = end = name; *start; start = end)
> -    {
> -      /* Skip sequence of multiple path-separators.  */
> -      while (*start == '/')
> -	++start;
> -
> -      /* Find end of path component.  */
> -      for (end = start; *end && *end != '/'; ++end)
> -	/* Nothing.  */;
> -
> -      if (end - start == 0)
> -	break;
> -      else if (end - start == 1 && start[0] == '.')
> -	/* nothing */;
> -      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> -	{
> -	  /* Back up to previous component, ignore if at root already.  */
> -	  if (dest > rpath + 1)
> -	    while ((--dest)[-1] != '/');
> -	}
> -      else
> -	{
> -	  if (dest[-1] != '/')
> -	    *dest++ = '/';
> -
> -	  if (dest + (end - start) >= rpath + PATH_MAX)
> -            return NULL;
> -
> -	  dest = __mempcpy (dest, start, end - start);
> -	  *dest = '\0';
> -	}
> -    }
> -  if (dest > rpath + 1 && dest[-1] == '/')
> -    --dest;
> -  *dest = '\0';
> -
> -  return rpath;
> -}
> -
>  /* Support notifying the simulator about new objects.  */
>  void internal_function
>  _dl_after_load (struct link_map *l)
> @@ -117,7 +53,7 @@ _dl_after_load (struct link_map *l)
>    DLPUTC (':');
>  
>    /* Write the library path, including the terminating '\0'.  */
> -  path = dl_realpath (l->l_name, pathbuf) ?: l->l_name;
> +  path = __realpath (l->l_name, pathbuf) ?: l->l_name;
>    for (size_t i = 0;; i++)
>      {
>        DLPUTC (path[i]);
> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
> index 9ad6d22..4525429 100644
> --- a/sysdeps/unix/sysv/linux/Makefile
> +++ b/sysdeps/unix/sysv/linux/Makefile
> @@ -176,7 +176,7 @@ endif
>  
>  ifeq ($(subdir),elf)
>  sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \
> -			dl-fxstatat64
> +			dl-fxstatat64 dl-lxstat64
>  
>  CPPFLAGS-lddlibc4 += -DNOT_IN_libc
>  
> diff --git a/sysdeps/unix/sysv/linux/dl-lxstat64.c b/sysdeps/unix/sysv/linux/dl-lxstat64.c
> new file mode 100644
> index 0000000..63e6800
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/dl-lxstat64.c
> @@ -0,0 +1 @@
> +#include <lxstat64.c>
> diff --git a/sysdeps/unix/sysv/linux/dl-realpath.c b/sysdeps/unix/sysv/linux/dl-realpath.c
> new file mode 100644
> index 0000000..91652dc
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/dl-realpath.c
> @@ -0,0 +1,121 @@
> +/* Dynamic loader version of realpath for linux.
> +   Copyright (C) 2014 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <limits.h>
> +#include <ldsodefs.h>
> +#include <sys/stat.h>
> +#include <kernel-features.h>
> +#include <sysdep.h>
> +
> +/* Simplified version of realpath which extends elf/dl-realpath.c to use
> + * linux syscalls for handling symlinks */
> +char * internal_function
> +__realpath (const char *name, char *rpath)
> +{
> +  char *dest;
> +  char extra_buf[PATH_MAX];
> +  const char *start, *end;
> +  int num_links = 0;
> +
> +  if (name[0] != '/')
> +    {
> +      if (!__getcwd (rpath, PATH_MAX))
> +	return NULL;
> +      dest = __rawmemchr (rpath, '\0');
> +    }
> +  else
> +    {
> +      rpath[0] = '/';
> +      dest = rpath + 1;
> +    }
> +
> +  for (start = end = name; *start; start = end)
> +    {
> +      struct stat64 st;
> +      int n;
> +      /* Skip sequence of multiple path-separators.  */
> +      while (*start == '/')
> +	++start;
> +
> +      /* Find end of path component.  */
> +      for (end = start; *end && *end != '/'; ++end)
> +	/* Nothing.  */;
> +
> +      if (end - start == 0)
> +	break;
> +      else if (end - start == 1 && start[0] == '.')
> +	/* nothing */;
> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> +	{
> +	  /* Back up to previous component, ignore if at root already.  */
> +	  if (dest > rpath + 1)
> +	    while ((--dest)[-1] != '/');
> +	}
> +      else
> +	{
> +	  if (dest[-1] != '/')
> +	    *dest++ = '/';
> +
> +	  if (dest + (end - start) >= rpath + PATH_MAX)
> +	    return NULL;
> +
> +	  dest = __mempcpy (dest, start, end - start);
> +	  *dest = '\0';
> +	  if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
> +	    return NULL;
> +
> +	  if (S_ISLNK (st.st_mode))
> +	    {
> +	      char buf[PATH_MAX];
> +	      size_t len;
> +
> +	      INTERNAL_SYSCALL_DECL (err);
> +
> +	      n = INTERNAL_SYSCALL (readlink, err, 3, rpath, buf, 
> +				    PATH_MAX - 1);
> +
> +	      if (n <= 0 || buf[0] == '[')
> +		return NULL;
> +	      buf[n] = '\0';
> +
> +	      len = strlen (end);
> +	      if ((long int) (n + len) >= PATH_MAX)
> +		return NULL;
> +
> +	      /* Careful here, end may be a pointer into extra_buf... */
> +	      memmove (&extra_buf[n], end, len + 1);
> +	      name = end = memcpy (extra_buf, buf, n);
> +
> +	      if (buf[0] == '/')
> +		dest = rpath + 1;     /* Absolute symlink */
> +	      else 
> +		/* Back up to previous component, ignore if at root already: */
> +		if (dest > rpath + 1)
> +		  while ((--dest)[-1] != '/');
> +	    }
> +	}
> +    }
> +  if (dest > rpath + 1 && dest[-1] == '/')
> +    --dest;
> +  *dest = '\0';
> +
> +  return rpath;
> +
> +}
> -- 
> 2.0.4
> 

pinging now that 2.20 is out.
--Brennan
Brennan Shacklett Sept. 24, 2014, 3:09 a.m. UTC | #6
On Tue, Aug 12, 2014 at 9:16 AM, Brennan Shacklett
<bpshacklett@gmail.com> wrote:
> Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
> for shared libraries. Currently $ORIGIN is calculated correctly for executables
> on linux, because readlink is called on /proc/self/exe, which means the
> resulting path is absolute and has no symlinks.
> Shared libraries with relative paths on the other hand are based
> off of appending the name / path of the library to the current working
> directory, which means if the library is a symlink, it is not followed, which
> breaks RPATH $ORIGIN in the following scenario:
>
> libone.so and libtwo.so are both in ~/lib
> libone.so needs libtwo.so, so libone.so has an RPATH of $ORIGIN.
> If I run ldd on ~/lib/libone.so, libtwo.so is found and all is good.
> If I create a symlink named ~/libone.so to ~/lib/libone.so, and run ldd on it
> libtwo is not found, because $ORIGIN for the library is calculated as ~
> instead of ~/lib.
>
> If I was to repeat the above test but instead of libone.so use an executable,
> everything would work as expected, which is why I think the shared library
> behavior is a bug.
>
> I ran into this in the real world when attempting to dynamically load a .so
> for python SWIG bindings, because the .so which python was loading was
> symlinked to the actual library directory where all of the .so's dependencies
> were located.
>
> The attached patch fixes this behavior by having realname evaluated with
> __realpath in _dl_map_object_from_fd, before _dl_new_object is called (which is
> where l_origin is assigned). This also somewhat simplifies the code in
> elf/dl-object.c, because realname is guaranteed to be an absolute path
> generated by realpath, so I was able to remove the code dealing with relative
> paths.
> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
> generic implementation, and extended it with lstat and readlink in the linux
> version. This means the bug is still present on other systems than linux (the
> generic implementation only returns an absolute path, it doesn't do anything
> with symlinks), but if there is a way to get the generic version to follow
> symlinks on all systems please let me know.
>
> I tested and ran the elf test suite on Gentoo Linux x86-64. If this change is
> wanted, I will happily write a test to go along with it.
> I don't have any sort of copyright attribution set up with the FSF, let me know
> if it is necessary for this change.
>
> Thanks,
> Brennan Shacklett
>
> 2014-08-12  Brennan Shacklett  <bpshacklett@gmail.com>
>
>         [BZ #17251]
>         * elf/Makefile (rtld-routines): Add dl-realpath.
>         * elf/dl-load.c (_dl_map_object_from_fd):
>         Calculate realname with __realpath.
>         * elf/dl-object.c (_dl_new_object): Remove relative path handling.
>         * elf/dl-realpath.c: New file. Generic implementation of __realpath.
>         * elf/tile/dl-runtime.c (_dl_after_load): Remove dl_realpath and
>         change call to __realpath.
>         * sysdeps/unix/sysv/linux/Makefile (sysdep-rtld-routines):
>         Add dl-lxstat64.
>         * sysdeps/unix/sysv/linux/dl-lxstat64.c: New file.
>         * sysdeps/unix/sysv/linux/dl-realpath.c: New file. Handle symlinks
>         unlike generic implementation.
> ---
>  elf/Makefile                          |   3 +-
>  elf/dl-load.c                         |  18 +++++
>  elf/dl-object.c                       |  54 ++-------------
>  elf/dl-realpath.c                     |  85 ++++++++++++++++++++++++
>  sysdeps/tile/dl-runtime.c             |  66 +------------------
>  sysdeps/unix/sysv/linux/Makefile      |   2 +-
>  sysdeps/unix/sysv/linux/dl-lxstat64.c |   1 +
>  sysdeps/unix/sysv/linux/dl-realpath.c | 121 ++++++++++++++++++++++++++++++++++
>  8 files changed, 236 insertions(+), 114 deletions(-)
>  create mode 100644 elf/dl-realpath.c
>  create mode 100644 sysdeps/unix/sysv/linux/dl-lxstat64.c
>  create mode 100644 sysdeps/unix/sysv/linux/dl-realpath.c
>
> diff --git a/elf/Makefile b/elf/Makefile
> index 25012cc..c130f21 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -43,7 +43,8 @@ shared-only-routines += dl-caller
>
>  # ld.so uses those routines, plus some special stuff for being the program
>  # interpreter and operating independent of libc.
> -rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal
> +rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal \
> +                  dl-realpath
>  all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
>
>  CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> index 016a99c..7353837 100644
> --- a/elf/dl-load.c
> +++ b/elf/dl-load.c
> @@ -891,6 +891,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>    int errval = 0;
>    struct r_debug *r = _dl_debug_initialize (0, nsid);
>    bool make_consistent = false;
> +  char *absolutename = NULL;
>
>    /* Get file information.  */
>    if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &st) < 0))
> @@ -902,6 +903,23 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>        lose (errval, fd, name, realname, l, errstring,
>             make_consistent ? r : NULL, nsid);
>      }
> +  /* Find absolute pathname for object */
> +  absolutename = (char *) malloc (PATH_MAX);
> +  if (!absolutename)
> +    {
> +      errstring = N_("cannot allocate memory for absolute path");
> +      goto call_lose_errno;
> +    }
> +
> +  if (!__realpath (realname, absolutename))
> +    {
> +      free (absolutename);
> +      errstring = N_("cannot find absolute path of shared object");
> +      goto call_lose_errno;
> +    }
> +
> +  free (realname);
> +  realname = absolutename;
>
>    /* Look again to see if the real name matched another already loaded.  */
>    for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
> diff --git a/elf/dl-object.c b/elf/dl-object.c
> index afd80a6..d93935b 100644
> --- a/elf/dl-object.c
> +++ b/elf/dl-object.c
> @@ -158,55 +158,15 @@ _dl_new_object (char *realname, const char *libname, int type,
>        char *origin;
>        char *cp;
>
> -      if (realname[0] == '/')
> +      /* It is an absolute path, calculated by realpath. Use it.
> +       * But we have to make a copy since we strip out the trailing slash. */
> +      assert (realname[0] == '/');
> +      cp = origin = (char *) malloc (realname_len);
> +      if (origin == NULL)
>         {
> -         /* It is an absolute path.  Use it.  But we have to make a
> -            copy since we strip out the trailing slash.  */
> -         cp = origin = (char *) malloc (realname_len);
> -         if (origin == NULL)
> -           {
> -             origin = (char *) -1;
> -             goto out;
> -           }
> +         origin = (char *) -1;
> +         goto out;
>         }
> -      else
> -       {
> -         size_t len = realname_len;
> -         char *result = NULL;
> -
> -         /* Get the current directory name.  */
> -         origin = NULL;
> -         do
> -           {
> -             char *new_origin;
> -
> -             len += 128;
> -             new_origin = (char *) realloc (origin, len);
> -             if (new_origin == NULL)
> -               /* We exit the loop.  Note that result == NULL.  */
> -               break;
> -             origin = new_origin;
> -           }
> -         while ((result = __getcwd (origin, len - realname_len)) == NULL
> -                && errno == ERANGE);
> -
> -         if (result == NULL)
> -           {
> -             /* We were not able to determine the current directory.
> -                Note that free(origin) is OK if origin == NULL.  */
> -             free (origin);
> -             origin = (char *) -1;
> -             goto out;
> -           }
> -
> -         /* Find the end of the path and see whether we have to add a
> -            slash.  We could use rawmemchr but this need not be
> -            fast.  */
> -         cp = (strchr) (origin, '\0');
> -         if (cp[-1] != '/')
> -           *cp++ = '/';
> -       }
> -
>        /* Add the real file name.  */
>        cp = __mempcpy (cp, realname, realname_len);
>
> diff --git a/elf/dl-realpath.c b/elf/dl-realpath.c
> new file mode 100644
> index 0000000..2549bb3
> --- /dev/null
> +++ b/elf/dl-realpath.c
> @@ -0,0 +1,85 @@
> +/* Dynamic loader version of realpath.
> +   Copyright (C) 2014 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <limits.h>
> +#include <ldsodefs.h>
> +
> +/* Simplified implementation of realpath(): no dynamic memory use, no lstat(),
> +   no set_errno(), no valid "rpath" on error, etc.  This simplifies cases
> +   involving relative paths, specifically where $ORIGIN needs to be
> +   calculated. For this relatively rare case, one could also imagine using
> +   link_map.l_origin to avoid the getcwd() here, but the simpler code
> +   here seems like a better solution.  */
> +char * internal_function
> +__realpath (const char *name, char *rpath)
> +{
> +  char *dest;
> +  const char *start, *end;
> +
> +  if (name[0] != '/')
> +    {
> +      if (!__getcwd (rpath, PATH_MAX))
> +       return NULL;
> +      dest = __rawmemchr (rpath, '\0');
> +    }
> +  else
> +    {
> +      rpath[0] = '/';
> +      dest = rpath + 1;
> +    }
> +
> +  for (start = end = name; *start; start = end)
> +    {
> +      /* Skip sequence of multiple path-separators.  */
> +      while (*start == '/')
> +       ++start;
> +
> +      /* Find end of path component.  */
> +      for (end = start; *end && *end != '/'; ++end)
> +       /* Nothing.  */;
> +
> +      if (end - start == 0)
> +       break;
> +      else if (end - start == 1 && start[0] == '.')
> +       /* nothing */;
> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> +       {
> +         /* Back up to previous component, ignore if at root already.  */
> +         if (dest > rpath + 1)
> +           while ((--dest)[-1] != '/');
> +       }
> +      else
> +       {
> +         if (dest[-1] != '/')
> +           *dest++ = '/';
> +
> +         if (dest + (end - start) >= rpath + PATH_MAX)
> +           return NULL;
> +
> +         dest = __mempcpy (dest, start, end - start);
> +         *dest = '\0';
> +       }
> +    }
> +  if (dest > rpath + 1 && dest[-1] == '/')
> +    --dest;
> +  *dest = '\0';
> +
> +  return rpath;
> +}
> diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
> index bcc00bc..45251d0 100644
> --- a/sysdeps/tile/dl-runtime.c
> +++ b/sysdeps/tile/dl-runtime.c
> @@ -29,70 +29,6 @@
>  #include <arch/sim.h>
>  #include <dl-unmap-segments.h>
>
> -/* Like realpath(), but simplified: no dynamic memory use, no lstat(),
> -   no set_errno(), no valid "rpath" on error, etc.  This handles some
> -   simple cases where the simulator might not have a valid entry for
> -   a loaded Elf object, in particular dlopen() with a relative path.
> -   For this relatively rare case, one could also imagine using
> -   link_map.l_origin to avoid the getcwd() here, but the simpler code
> -   here seems like a better solution.  */
> -static char *
> -dl_realpath (const char *name, char *rpath)
> -{
> -  char *dest;
> -  const char *start, *end;
> -
> -  if (name[0] != '/')
> -    {
> -      if (!__getcwd (rpath, PATH_MAX))
> -        return NULL;
> -      dest = __rawmemchr (rpath, '\0');
> -    }
> -  else
> -    {
> -      rpath[0] = '/';
> -      dest = rpath + 1;
> -    }
> -
> -  for (start = end = name; *start; start = end)
> -    {
> -      /* Skip sequence of multiple path-separators.  */
> -      while (*start == '/')
> -       ++start;
> -
> -      /* Find end of path component.  */
> -      for (end = start; *end && *end != '/'; ++end)
> -       /* Nothing.  */;
> -
> -      if (end - start == 0)
> -       break;
> -      else if (end - start == 1 && start[0] == '.')
> -       /* nothing */;
> -      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> -       {
> -         /* Back up to previous component, ignore if at root already.  */
> -         if (dest > rpath + 1)
> -           while ((--dest)[-1] != '/');
> -       }
> -      else
> -       {
> -         if (dest[-1] != '/')
> -           *dest++ = '/';
> -
> -         if (dest + (end - start) >= rpath + PATH_MAX)
> -            return NULL;
> -
> -         dest = __mempcpy (dest, start, end - start);
> -         *dest = '\0';
> -       }
> -    }
> -  if (dest > rpath + 1 && dest[-1] == '/')
> -    --dest;
> -  *dest = '\0';
> -
> -  return rpath;
> -}
> -
>  /* Support notifying the simulator about new objects.  */
>  void internal_function
>  _dl_after_load (struct link_map *l)
> @@ -117,7 +53,7 @@ _dl_after_load (struct link_map *l)
>    DLPUTC (':');
>
>    /* Write the library path, including the terminating '\0'.  */
> -  path = dl_realpath (l->l_name, pathbuf) ?: l->l_name;
> +  path = __realpath (l->l_name, pathbuf) ?: l->l_name;
>    for (size_t i = 0;; i++)
>      {
>        DLPUTC (path[i]);
> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
> index 9ad6d22..4525429 100644
> --- a/sysdeps/unix/sysv/linux/Makefile
> +++ b/sysdeps/unix/sysv/linux/Makefile
> @@ -176,7 +176,7 @@ endif
>
>  ifeq ($(subdir),elf)
>  sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \
> -                       dl-fxstatat64
> +                       dl-fxstatat64 dl-lxstat64
>
>  CPPFLAGS-lddlibc4 += -DNOT_IN_libc
>
> diff --git a/sysdeps/unix/sysv/linux/dl-lxstat64.c b/sysdeps/unix/sysv/linux/dl-lxstat64.c
> new file mode 100644
> index 0000000..63e6800
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/dl-lxstat64.c
> @@ -0,0 +1 @@
> +#include <lxstat64.c>
> diff --git a/sysdeps/unix/sysv/linux/dl-realpath.c b/sysdeps/unix/sysv/linux/dl-realpath.c
> new file mode 100644
> index 0000000..91652dc
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/dl-realpath.c
> @@ -0,0 +1,121 @@
> +/* Dynamic loader version of realpath for linux.
> +   Copyright (C) 2014 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <limits.h>
> +#include <ldsodefs.h>
> +#include <sys/stat.h>
> +#include <kernel-features.h>
> +#include <sysdep.h>
> +
> +/* Simplified version of realpath which extends elf/dl-realpath.c to use
> + * linux syscalls for handling symlinks */
> +char * internal_function
> +__realpath (const char *name, char *rpath)
> +{
> +  char *dest;
> +  char extra_buf[PATH_MAX];
> +  const char *start, *end;
> +  int num_links = 0;
> +
> +  if (name[0] != '/')
> +    {
> +      if (!__getcwd (rpath, PATH_MAX))
> +       return NULL;
> +      dest = __rawmemchr (rpath, '\0');
> +    }
> +  else
> +    {
> +      rpath[0] = '/';
> +      dest = rpath + 1;
> +    }
> +
> +  for (start = end = name; *start; start = end)
> +    {
> +      struct stat64 st;
> +      int n;
> +      /* Skip sequence of multiple path-separators.  */
> +      while (*start == '/')
> +       ++start;
> +
> +      /* Find end of path component.  */
> +      for (end = start; *end && *end != '/'; ++end)
> +       /* Nothing.  */;
> +
> +      if (end - start == 0)
> +       break;
> +      else if (end - start == 1 && start[0] == '.')
> +       /* nothing */;
> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
> +       {
> +         /* Back up to previous component, ignore if at root already.  */
> +         if (dest > rpath + 1)
> +           while ((--dest)[-1] != '/');
> +       }
> +      else
> +       {
> +         if (dest[-1] != '/')
> +           *dest++ = '/';
> +
> +         if (dest + (end - start) >= rpath + PATH_MAX)
> +           return NULL;
> +
> +         dest = __mempcpy (dest, start, end - start);
> +         *dest = '\0';
> +         if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
> +           return NULL;
> +
> +         if (S_ISLNK (st.st_mode))
> +           {
> +             char buf[PATH_MAX];
> +             size_t len;
> +
> +             INTERNAL_SYSCALL_DECL (err);
> +
> +             n = INTERNAL_SYSCALL (readlink, err, 3, rpath, buf,
> +                                   PATH_MAX - 1);
> +
> +             if (n <= 0 || buf[0] == '[')
> +               return NULL;
> +             buf[n] = '\0';
> +
> +             len = strlen (end);
> +             if ((long int) (n + len) >= PATH_MAX)
> +               return NULL;
> +
> +             /* Careful here, end may be a pointer into extra_buf... */
> +             memmove (&extra_buf[n], end, len + 1);
> +             name = end = memcpy (extra_buf, buf, n);
> +
> +             if (buf[0] == '/')
> +               dest = rpath + 1;     /* Absolute symlink */
> +             else
> +               /* Back up to previous component, ignore if at root already: */
> +               if (dest > rpath + 1)
> +                 while ((--dest)[-1] != '/');
> +           }
> +       }
> +    }
> +  if (dest > rpath + 1 && dest[-1] == '/')
> +    --dest;
> +  *dest = '\0';
> +
> +  return rpath;
> +
> +}
> --
> 2.0.4
>

ping.
Brennan Shacklett Oct. 1, 2014, 7:28 a.m. UTC | #7
ping

On Tue, Sep 23, 2014 at 8:09 PM, Brennan Shacklett
<bpshacklett@gmail.com> wrote:
> On Tue, Aug 12, 2014 at 9:16 AM, Brennan Shacklett
> <bpshacklett@gmail.com> wrote:
>> Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
>> for shared libraries. Currently $ORIGIN is calculated correctly for executables
>> on linux, because readlink is called on /proc/self/exe, which means the
>> resulting path is absolute and has no symlinks.
>> Shared libraries with relative paths on the other hand are based
>> off of appending the name / path of the library to the current working
>> directory, which means if the library is a symlink, it is not followed, which
>> breaks RPATH $ORIGIN in the following scenario:
>>
>> libone.so and libtwo.so are both in ~/lib
>> libone.so needs libtwo.so, so libone.so has an RPATH of $ORIGIN.
>> If I run ldd on ~/lib/libone.so, libtwo.so is found and all is good.
>> If I create a symlink named ~/libone.so to ~/lib/libone.so, and run ldd on it
>> libtwo is not found, because $ORIGIN for the library is calculated as ~
>> instead of ~/lib.
>>
>> If I was to repeat the above test but instead of libone.so use an executable,
>> everything would work as expected, which is why I think the shared library
>> behavior is a bug.
>>
>> I ran into this in the real world when attempting to dynamically load a .so
>> for python SWIG bindings, because the .so which python was loading was
>> symlinked to the actual library directory where all of the .so's dependencies
>> were located.
>>
>> The attached patch fixes this behavior by having realname evaluated with
>> __realpath in _dl_map_object_from_fd, before _dl_new_object is called (which is
>> where l_origin is assigned). This also somewhat simplifies the code in
>> elf/dl-object.c, because realname is guaranteed to be an absolute path
>> generated by realpath, so I was able to remove the code dealing with relative
>> paths.
>> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
>> generic implementation, and extended it with lstat and readlink in the linux
>> version. This means the bug is still present on other systems than linux (the
>> generic implementation only returns an absolute path, it doesn't do anything
>> with symlinks), but if there is a way to get the generic version to follow
>> symlinks on all systems please let me know.
>>
>> I tested and ran the elf test suite on Gentoo Linux x86-64. If this change is
>> wanted, I will happily write a test to go along with it.
>> I don't have any sort of copyright attribution set up with the FSF, let me know
>> if it is necessary for this change.
>>
>> Thanks,
>> Brennan Shacklett
>>
>> 2014-08-12  Brennan Shacklett  <bpshacklett@gmail.com>
>>
>>         [BZ #17251]
>>         * elf/Makefile (rtld-routines): Add dl-realpath.
>>         * elf/dl-load.c (_dl_map_object_from_fd):
>>         Calculate realname with __realpath.
>>         * elf/dl-object.c (_dl_new_object): Remove relative path handling.
>>         * elf/dl-realpath.c: New file. Generic implementation of __realpath.
>>         * elf/tile/dl-runtime.c (_dl_after_load): Remove dl_realpath and
>>         change call to __realpath.
>>         * sysdeps/unix/sysv/linux/Makefile (sysdep-rtld-routines):
>>         Add dl-lxstat64.
>>         * sysdeps/unix/sysv/linux/dl-lxstat64.c: New file.
>>         * sysdeps/unix/sysv/linux/dl-realpath.c: New file. Handle symlinks
>>         unlike generic implementation.
>> ---
>>  elf/Makefile                          |   3 +-
>>  elf/dl-load.c                         |  18 +++++
>>  elf/dl-object.c                       |  54 ++-------------
>>  elf/dl-realpath.c                     |  85 ++++++++++++++++++++++++
>>  sysdeps/tile/dl-runtime.c             |  66 +------------------
>>  sysdeps/unix/sysv/linux/Makefile      |   2 +-
>>  sysdeps/unix/sysv/linux/dl-lxstat64.c |   1 +
>>  sysdeps/unix/sysv/linux/dl-realpath.c | 121 ++++++++++++++++++++++++++++++++++
>>  8 files changed, 236 insertions(+), 114 deletions(-)
>>  create mode 100644 elf/dl-realpath.c
>>  create mode 100644 sysdeps/unix/sysv/linux/dl-lxstat64.c
>>  create mode 100644 sysdeps/unix/sysv/linux/dl-realpath.c
>>
>> diff --git a/elf/Makefile b/elf/Makefile
>> index 25012cc..c130f21 100644
>> --- a/elf/Makefile
>> +++ b/elf/Makefile
>> @@ -43,7 +43,8 @@ shared-only-routines += dl-caller
>>
>>  # ld.so uses those routines, plus some special stuff for being the program
>>  # interpreter and operating independent of libc.
>> -rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal
>> +rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal \
>> +                  dl-realpath
>>  all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
>>
>>  CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
>> diff --git a/elf/dl-load.c b/elf/dl-load.c
>> index 016a99c..7353837 100644
>> --- a/elf/dl-load.c
>> +++ b/elf/dl-load.c
>> @@ -891,6 +891,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>>    int errval = 0;
>>    struct r_debug *r = _dl_debug_initialize (0, nsid);
>>    bool make_consistent = false;
>> +  char *absolutename = NULL;
>>
>>    /* Get file information.  */
>>    if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &st) < 0))
>> @@ -902,6 +903,23 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>>        lose (errval, fd, name, realname, l, errstring,
>>             make_consistent ? r : NULL, nsid);
>>      }
>> +  /* Find absolute pathname for object */
>> +  absolutename = (char *) malloc (PATH_MAX);
>> +  if (!absolutename)
>> +    {
>> +      errstring = N_("cannot allocate memory for absolute path");
>> +      goto call_lose_errno;
>> +    }
>> +
>> +  if (!__realpath (realname, absolutename))
>> +    {
>> +      free (absolutename);
>> +      errstring = N_("cannot find absolute path of shared object");
>> +      goto call_lose_errno;
>> +    }
>> +
>> +  free (realname);
>> +  realname = absolutename;
>>
>>    /* Look again to see if the real name matched another already loaded.  */
>>    for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
>> diff --git a/elf/dl-object.c b/elf/dl-object.c
>> index afd80a6..d93935b 100644
>> --- a/elf/dl-object.c
>> +++ b/elf/dl-object.c
>> @@ -158,55 +158,15 @@ _dl_new_object (char *realname, const char *libname, int type,
>>        char *origin;
>>        char *cp;
>>
>> -      if (realname[0] == '/')
>> +      /* It is an absolute path, calculated by realpath. Use it.
>> +       * But we have to make a copy since we strip out the trailing slash. */
>> +      assert (realname[0] == '/');
>> +      cp = origin = (char *) malloc (realname_len);
>> +      if (origin == NULL)
>>         {
>> -         /* It is an absolute path.  Use it.  But we have to make a
>> -            copy since we strip out the trailing slash.  */
>> -         cp = origin = (char *) malloc (realname_len);
>> -         if (origin == NULL)
>> -           {
>> -             origin = (char *) -1;
>> -             goto out;
>> -           }
>> +         origin = (char *) -1;
>> +         goto out;
>>         }
>> -      else
>> -       {
>> -         size_t len = realname_len;
>> -         char *result = NULL;
>> -
>> -         /* Get the current directory name.  */
>> -         origin = NULL;
>> -         do
>> -           {
>> -             char *new_origin;
>> -
>> -             len += 128;
>> -             new_origin = (char *) realloc (origin, len);
>> -             if (new_origin == NULL)
>> -               /* We exit the loop.  Note that result == NULL.  */
>> -               break;
>> -             origin = new_origin;
>> -           }
>> -         while ((result = __getcwd (origin, len - realname_len)) == NULL
>> -                && errno == ERANGE);
>> -
>> -         if (result == NULL)
>> -           {
>> -             /* We were not able to determine the current directory.
>> -                Note that free(origin) is OK if origin == NULL.  */
>> -             free (origin);
>> -             origin = (char *) -1;
>> -             goto out;
>> -           }
>> -
>> -         /* Find the end of the path and see whether we have to add a
>> -            slash.  We could use rawmemchr but this need not be
>> -            fast.  */
>> -         cp = (strchr) (origin, '\0');
>> -         if (cp[-1] != '/')
>> -           *cp++ = '/';
>> -       }
>> -
>>        /* Add the real file name.  */
>>        cp = __mempcpy (cp, realname, realname_len);
>>
>> diff --git a/elf/dl-realpath.c b/elf/dl-realpath.c
>> new file mode 100644
>> index 0000000..2549bb3
>> --- /dev/null
>> +++ b/elf/dl-realpath.c
>> @@ -0,0 +1,85 @@
>> +/* Dynamic loader version of realpath.
>> +   Copyright (C) 2014 Free Software Foundation, Inc.
>> +   This file is part of the GNU C Library.
>> +
>> +   The GNU C Library is free software; you can redistribute it and/or
>> +   modify it under the terms of the GNU Lesser General Public
>> +   License as published by the Free Software Foundation; either
>> +   version 2.1 of the License, or (at your option) any later version.
>> +
>> +   The GNU C Library is distributed in the hope that it will be useful,
>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> +   Lesser General Public License for more details.
>> +
>> +   You should have received a copy of the GNU Lesser General Public
>> +   License along with the GNU C Library; if not, see
>> +   <http://www.gnu.org/licenses/>.  */
>> +
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +#include <limits.h>
>> +#include <ldsodefs.h>
>> +
>> +/* Simplified implementation of realpath(): no dynamic memory use, no lstat(),
>> +   no set_errno(), no valid "rpath" on error, etc.  This simplifies cases
>> +   involving relative paths, specifically where $ORIGIN needs to be
>> +   calculated. For this relatively rare case, one could also imagine using
>> +   link_map.l_origin to avoid the getcwd() here, but the simpler code
>> +   here seems like a better solution.  */
>> +char * internal_function
>> +__realpath (const char *name, char *rpath)
>> +{
>> +  char *dest;
>> +  const char *start, *end;
>> +
>> +  if (name[0] != '/')
>> +    {
>> +      if (!__getcwd (rpath, PATH_MAX))
>> +       return NULL;
>> +      dest = __rawmemchr (rpath, '\0');
>> +    }
>> +  else
>> +    {
>> +      rpath[0] = '/';
>> +      dest = rpath + 1;
>> +    }
>> +
>> +  for (start = end = name; *start; start = end)
>> +    {
>> +      /* Skip sequence of multiple path-separators.  */
>> +      while (*start == '/')
>> +       ++start;
>> +
>> +      /* Find end of path component.  */
>> +      for (end = start; *end && *end != '/'; ++end)
>> +       /* Nothing.  */;
>> +
>> +      if (end - start == 0)
>> +       break;
>> +      else if (end - start == 1 && start[0] == '.')
>> +       /* nothing */;
>> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>> +       {
>> +         /* Back up to previous component, ignore if at root already.  */
>> +         if (dest > rpath + 1)
>> +           while ((--dest)[-1] != '/');
>> +       }
>> +      else
>> +       {
>> +         if (dest[-1] != '/')
>> +           *dest++ = '/';
>> +
>> +         if (dest + (end - start) >= rpath + PATH_MAX)
>> +           return NULL;
>> +
>> +         dest = __mempcpy (dest, start, end - start);
>> +         *dest = '\0';
>> +       }
>> +    }
>> +  if (dest > rpath + 1 && dest[-1] == '/')
>> +    --dest;
>> +  *dest = '\0';
>> +
>> +  return rpath;
>> +}
>> diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
>> index bcc00bc..45251d0 100644
>> --- a/sysdeps/tile/dl-runtime.c
>> +++ b/sysdeps/tile/dl-runtime.c
>> @@ -29,70 +29,6 @@
>>  #include <arch/sim.h>
>>  #include <dl-unmap-segments.h>
>>
>> -/* Like realpath(), but simplified: no dynamic memory use, no lstat(),
>> -   no set_errno(), no valid "rpath" on error, etc.  This handles some
>> -   simple cases where the simulator might not have a valid entry for
>> -   a loaded Elf object, in particular dlopen() with a relative path.
>> -   For this relatively rare case, one could also imagine using
>> -   link_map.l_origin to avoid the getcwd() here, but the simpler code
>> -   here seems like a better solution.  */
>> -static char *
>> -dl_realpath (const char *name, char *rpath)
>> -{
>> -  char *dest;
>> -  const char *start, *end;
>> -
>> -  if (name[0] != '/')
>> -    {
>> -      if (!__getcwd (rpath, PATH_MAX))
>> -        return NULL;
>> -      dest = __rawmemchr (rpath, '\0');
>> -    }
>> -  else
>> -    {
>> -      rpath[0] = '/';
>> -      dest = rpath + 1;
>> -    }
>> -
>> -  for (start = end = name; *start; start = end)
>> -    {
>> -      /* Skip sequence of multiple path-separators.  */
>> -      while (*start == '/')
>> -       ++start;
>> -
>> -      /* Find end of path component.  */
>> -      for (end = start; *end && *end != '/'; ++end)
>> -       /* Nothing.  */;
>> -
>> -      if (end - start == 0)
>> -       break;
>> -      else if (end - start == 1 && start[0] == '.')
>> -       /* nothing */;
>> -      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>> -       {
>> -         /* Back up to previous component, ignore if at root already.  */
>> -         if (dest > rpath + 1)
>> -           while ((--dest)[-1] != '/');
>> -       }
>> -      else
>> -       {
>> -         if (dest[-1] != '/')
>> -           *dest++ = '/';
>> -
>> -         if (dest + (end - start) >= rpath + PATH_MAX)
>> -            return NULL;
>> -
>> -         dest = __mempcpy (dest, start, end - start);
>> -         *dest = '\0';
>> -       }
>> -    }
>> -  if (dest > rpath + 1 && dest[-1] == '/')
>> -    --dest;
>> -  *dest = '\0';
>> -
>> -  return rpath;
>> -}
>> -
>>  /* Support notifying the simulator about new objects.  */
>>  void internal_function
>>  _dl_after_load (struct link_map *l)
>> @@ -117,7 +53,7 @@ _dl_after_load (struct link_map *l)
>>    DLPUTC (':');
>>
>>    /* Write the library path, including the terminating '\0'.  */
>> -  path = dl_realpath (l->l_name, pathbuf) ?: l->l_name;
>> +  path = __realpath (l->l_name, pathbuf) ?: l->l_name;
>>    for (size_t i = 0;; i++)
>>      {
>>        DLPUTC (path[i]);
>> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
>> index 9ad6d22..4525429 100644
>> --- a/sysdeps/unix/sysv/linux/Makefile
>> +++ b/sysdeps/unix/sysv/linux/Makefile
>> @@ -176,7 +176,7 @@ endif
>>
>>  ifeq ($(subdir),elf)
>>  sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \
>> -                       dl-fxstatat64
>> +                       dl-fxstatat64 dl-lxstat64
>>
>>  CPPFLAGS-lddlibc4 += -DNOT_IN_libc
>>
>> diff --git a/sysdeps/unix/sysv/linux/dl-lxstat64.c b/sysdeps/unix/sysv/linux/dl-lxstat64.c
>> new file mode 100644
>> index 0000000..63e6800
>> --- /dev/null
>> +++ b/sysdeps/unix/sysv/linux/dl-lxstat64.c
>> @@ -0,0 +1 @@
>> +#include <lxstat64.c>
>> diff --git a/sysdeps/unix/sysv/linux/dl-realpath.c b/sysdeps/unix/sysv/linux/dl-realpath.c
>> new file mode 100644
>> index 0000000..91652dc
>> --- /dev/null
>> +++ b/sysdeps/unix/sysv/linux/dl-realpath.c
>> @@ -0,0 +1,121 @@
>> +/* Dynamic loader version of realpath for linux.
>> +   Copyright (C) 2014 Free Software Foundation, Inc.
>> +   This file is part of the GNU C Library.
>> +
>> +   The GNU C Library is free software; you can redistribute it and/or
>> +   modify it under the terms of the GNU Lesser General Public
>> +   License as published by the Free Software Foundation; either
>> +   version 2.1 of the License, or (at your option) any later version.
>> +
>> +   The GNU C Library is distributed in the hope that it will be useful,
>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> +   Lesser General Public License for more details.
>> +
>> +   You should have received a copy of the GNU Lesser General Public
>> +   License along with the GNU C Library; if not, see
>> +   <http://www.gnu.org/licenses/>.  */
>> +
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +#include <limits.h>
>> +#include <ldsodefs.h>
>> +#include <sys/stat.h>
>> +#include <kernel-features.h>
>> +#include <sysdep.h>
>> +
>> +/* Simplified version of realpath which extends elf/dl-realpath.c to use
>> + * linux syscalls for handling symlinks */
>> +char * internal_function
>> +__realpath (const char *name, char *rpath)
>> +{
>> +  char *dest;
>> +  char extra_buf[PATH_MAX];
>> +  const char *start, *end;
>> +  int num_links = 0;
>> +
>> +  if (name[0] != '/')
>> +    {
>> +      if (!__getcwd (rpath, PATH_MAX))
>> +       return NULL;
>> +      dest = __rawmemchr (rpath, '\0');
>> +    }
>> +  else
>> +    {
>> +      rpath[0] = '/';
>> +      dest = rpath + 1;
>> +    }
>> +
>> +  for (start = end = name; *start; start = end)
>> +    {
>> +      struct stat64 st;
>> +      int n;
>> +      /* Skip sequence of multiple path-separators.  */
>> +      while (*start == '/')
>> +       ++start;
>> +
>> +      /* Find end of path component.  */
>> +      for (end = start; *end && *end != '/'; ++end)
>> +       /* Nothing.  */;
>> +
>> +      if (end - start == 0)
>> +       break;
>> +      else if (end - start == 1 && start[0] == '.')
>> +       /* nothing */;
>> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>> +       {
>> +         /* Back up to previous component, ignore if at root already.  */
>> +         if (dest > rpath + 1)
>> +           while ((--dest)[-1] != '/');
>> +       }
>> +      else
>> +       {
>> +         if (dest[-1] != '/')
>> +           *dest++ = '/';
>> +
>> +         if (dest + (end - start) >= rpath + PATH_MAX)
>> +           return NULL;
>> +
>> +         dest = __mempcpy (dest, start, end - start);
>> +         *dest = '\0';
>> +         if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
>> +           return NULL;
>> +
>> +         if (S_ISLNK (st.st_mode))
>> +           {
>> +             char buf[PATH_MAX];
>> +             size_t len;
>> +
>> +             INTERNAL_SYSCALL_DECL (err);
>> +
>> +             n = INTERNAL_SYSCALL (readlink, err, 3, rpath, buf,
>> +                                   PATH_MAX - 1);
>> +
>> +             if (n <= 0 || buf[0] == '[')
>> +               return NULL;
>> +             buf[n] = '\0';
>> +
>> +             len = strlen (end);
>> +             if ((long int) (n + len) >= PATH_MAX)
>> +               return NULL;
>> +
>> +             /* Careful here, end may be a pointer into extra_buf... */
>> +             memmove (&extra_buf[n], end, len + 1);
>> +             name = end = memcpy (extra_buf, buf, n);
>> +
>> +             if (buf[0] == '/')
>> +               dest = rpath + 1;     /* Absolute symlink */
>> +             else
>> +               /* Back up to previous component, ignore if at root already: */
>> +               if (dest > rpath + 1)
>> +                 while ((--dest)[-1] != '/');
>> +           }
>> +       }
>> +    }
>> +  if (dest > rpath + 1 && dest[-1] == '/')
>> +    --dest;
>> +  *dest = '\0';
>> +
>> +  return rpath;
>> +
>> +}
>> --
>> 2.0.4
>>
>
> ping.
Brennan Shacklett Oct. 6, 2014, 4:18 p.m. UTC | #8
ping

On Wed, Oct 1, 2014 at 12:28 AM, Brennan Shacklett
<bpshacklett@gmail.com> wrote:
> ping
>
> On Tue, Sep 23, 2014 at 8:09 PM, Brennan Shacklett
> <bpshacklett@gmail.com> wrote:
>> On Tue, Aug 12, 2014 at 9:16 AM, Brennan Shacklett
>> <bpshacklett@gmail.com> wrote:
>>> Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
>>> for shared libraries. Currently $ORIGIN is calculated correctly for executables
>>> on linux, because readlink is called on /proc/self/exe, which means the
>>> resulting path is absolute and has no symlinks.
>>> Shared libraries with relative paths on the other hand are based
>>> off of appending the name / path of the library to the current working
>>> directory, which means if the library is a symlink, it is not followed, which
>>> breaks RPATH $ORIGIN in the following scenario:
>>>
>>> libone.so and libtwo.so are both in ~/lib
>>> libone.so needs libtwo.so, so libone.so has an RPATH of $ORIGIN.
>>> If I run ldd on ~/lib/libone.so, libtwo.so is found and all is good.
>>> If I create a symlink named ~/libone.so to ~/lib/libone.so, and run ldd on it
>>> libtwo is not found, because $ORIGIN for the library is calculated as ~
>>> instead of ~/lib.
>>>
>>> If I was to repeat the above test but instead of libone.so use an executable,
>>> everything would work as expected, which is why I think the shared library
>>> behavior is a bug.
>>>
>>> I ran into this in the real world when attempting to dynamically load a .so
>>> for python SWIG bindings, because the .so which python was loading was
>>> symlinked to the actual library directory where all of the .so's dependencies
>>> were located.
>>>
>>> The attached patch fixes this behavior by having realname evaluated with
>>> __realpath in _dl_map_object_from_fd, before _dl_new_object is called (which is
>>> where l_origin is assigned). This also somewhat simplifies the code in
>>> elf/dl-object.c, because realname is guaranteed to be an absolute path
>>> generated by realpath, so I was able to remove the code dealing with relative
>>> paths.
>>> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
>>> generic implementation, and extended it with lstat and readlink in the linux
>>> version. This means the bug is still present on other systems than linux (the
>>> generic implementation only returns an absolute path, it doesn't do anything
>>> with symlinks), but if there is a way to get the generic version to follow
>>> symlinks on all systems please let me know.
>>>
>>> I tested and ran the elf test suite on Gentoo Linux x86-64. If this change is
>>> wanted, I will happily write a test to go along with it.
>>> I don't have any sort of copyright attribution set up with the FSF, let me know
>>> if it is necessary for this change.
>>>
>>> Thanks,
>>> Brennan Shacklett
>>>
>>> 2014-08-12  Brennan Shacklett  <bpshacklett@gmail.com>
>>>
>>>         [BZ #17251]
>>>         * elf/Makefile (rtld-routines): Add dl-realpath.
>>>         * elf/dl-load.c (_dl_map_object_from_fd):
>>>         Calculate realname with __realpath.
>>>         * elf/dl-object.c (_dl_new_object): Remove relative path handling.
>>>         * elf/dl-realpath.c: New file. Generic implementation of __realpath.
>>>         * elf/tile/dl-runtime.c (_dl_after_load): Remove dl_realpath and
>>>         change call to __realpath.
>>>         * sysdeps/unix/sysv/linux/Makefile (sysdep-rtld-routines):
>>>         Add dl-lxstat64.
>>>         * sysdeps/unix/sysv/linux/dl-lxstat64.c: New file.
>>>         * sysdeps/unix/sysv/linux/dl-realpath.c: New file. Handle symlinks
>>>         unlike generic implementation.
>>> ---
>>>  elf/Makefile                          |   3 +-
>>>  elf/dl-load.c                         |  18 +++++
>>>  elf/dl-object.c                       |  54 ++-------------
>>>  elf/dl-realpath.c                     |  85 ++++++++++++++++++++++++
>>>  sysdeps/tile/dl-runtime.c             |  66 +------------------
>>>  sysdeps/unix/sysv/linux/Makefile      |   2 +-
>>>  sysdeps/unix/sysv/linux/dl-lxstat64.c |   1 +
>>>  sysdeps/unix/sysv/linux/dl-realpath.c | 121 ++++++++++++++++++++++++++++++++++
>>>  8 files changed, 236 insertions(+), 114 deletions(-)
>>>  create mode 100644 elf/dl-realpath.c
>>>  create mode 100644 sysdeps/unix/sysv/linux/dl-lxstat64.c
>>>  create mode 100644 sysdeps/unix/sysv/linux/dl-realpath.c
>>>
>>> diff --git a/elf/Makefile b/elf/Makefile
>>> index 25012cc..c130f21 100644
>>> --- a/elf/Makefile
>>> +++ b/elf/Makefile
>>> @@ -43,7 +43,8 @@ shared-only-routines += dl-caller
>>>
>>>  # ld.so uses those routines, plus some special stuff for being the program
>>>  # interpreter and operating independent of libc.
>>> -rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal
>>> +rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal \
>>> +                  dl-realpath
>>>  all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
>>>
>>>  CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
>>> diff --git a/elf/dl-load.c b/elf/dl-load.c
>>> index 016a99c..7353837 100644
>>> --- a/elf/dl-load.c
>>> +++ b/elf/dl-load.c
>>> @@ -891,6 +891,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>>>    int errval = 0;
>>>    struct r_debug *r = _dl_debug_initialize (0, nsid);
>>>    bool make_consistent = false;
>>> +  char *absolutename = NULL;
>>>
>>>    /* Get file information.  */
>>>    if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &st) < 0))
>>> @@ -902,6 +903,23 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>>>        lose (errval, fd, name, realname, l, errstring,
>>>             make_consistent ? r : NULL, nsid);
>>>      }
>>> +  /* Find absolute pathname for object */
>>> +  absolutename = (char *) malloc (PATH_MAX);
>>> +  if (!absolutename)
>>> +    {
>>> +      errstring = N_("cannot allocate memory for absolute path");
>>> +      goto call_lose_errno;
>>> +    }
>>> +
>>> +  if (!__realpath (realname, absolutename))
>>> +    {
>>> +      free (absolutename);
>>> +      errstring = N_("cannot find absolute path of shared object");
>>> +      goto call_lose_errno;
>>> +    }
>>> +
>>> +  free (realname);
>>> +  realname = absolutename;
>>>
>>>    /* Look again to see if the real name matched another already loaded.  */
>>>    for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
>>> diff --git a/elf/dl-object.c b/elf/dl-object.c
>>> index afd80a6..d93935b 100644
>>> --- a/elf/dl-object.c
>>> +++ b/elf/dl-object.c
>>> @@ -158,55 +158,15 @@ _dl_new_object (char *realname, const char *libname, int type,
>>>        char *origin;
>>>        char *cp;
>>>
>>> -      if (realname[0] == '/')
>>> +      /* It is an absolute path, calculated by realpath. Use it.
>>> +       * But we have to make a copy since we strip out the trailing slash. */
>>> +      assert (realname[0] == '/');
>>> +      cp = origin = (char *) malloc (realname_len);
>>> +      if (origin == NULL)
>>>         {
>>> -         /* It is an absolute path.  Use it.  But we have to make a
>>> -            copy since we strip out the trailing slash.  */
>>> -         cp = origin = (char *) malloc (realname_len);
>>> -         if (origin == NULL)
>>> -           {
>>> -             origin = (char *) -1;
>>> -             goto out;
>>> -           }
>>> +         origin = (char *) -1;
>>> +         goto out;
>>>         }
>>> -      else
>>> -       {
>>> -         size_t len = realname_len;
>>> -         char *result = NULL;
>>> -
>>> -         /* Get the current directory name.  */
>>> -         origin = NULL;
>>> -         do
>>> -           {
>>> -             char *new_origin;
>>> -
>>> -             len += 128;
>>> -             new_origin = (char *) realloc (origin, len);
>>> -             if (new_origin == NULL)
>>> -               /* We exit the loop.  Note that result == NULL.  */
>>> -               break;
>>> -             origin = new_origin;
>>> -           }
>>> -         while ((result = __getcwd (origin, len - realname_len)) == NULL
>>> -                && errno == ERANGE);
>>> -
>>> -         if (result == NULL)
>>> -           {
>>> -             /* We were not able to determine the current directory.
>>> -                Note that free(origin) is OK if origin == NULL.  */
>>> -             free (origin);
>>> -             origin = (char *) -1;
>>> -             goto out;
>>> -           }
>>> -
>>> -         /* Find the end of the path and see whether we have to add a
>>> -            slash.  We could use rawmemchr but this need not be
>>> -            fast.  */
>>> -         cp = (strchr) (origin, '\0');
>>> -         if (cp[-1] != '/')
>>> -           *cp++ = '/';
>>> -       }
>>> -
>>>        /* Add the real file name.  */
>>>        cp = __mempcpy (cp, realname, realname_len);
>>>
>>> diff --git a/elf/dl-realpath.c b/elf/dl-realpath.c
>>> new file mode 100644
>>> index 0000000..2549bb3
>>> --- /dev/null
>>> +++ b/elf/dl-realpath.c
>>> @@ -0,0 +1,85 @@
>>> +/* Dynamic loader version of realpath.
>>> +   Copyright (C) 2014 Free Software Foundation, Inc.
>>> +   This file is part of the GNU C Library.
>>> +
>>> +   The GNU C Library is free software; you can redistribute it and/or
>>> +   modify it under the terms of the GNU Lesser General Public
>>> +   License as published by the Free Software Foundation; either
>>> +   version 2.1 of the License, or (at your option) any later version.
>>> +
>>> +   The GNU C Library is distributed in the hope that it will be useful,
>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>> +   Lesser General Public License for more details.
>>> +
>>> +   You should have received a copy of the GNU Lesser General Public
>>> +   License along with the GNU C Library; if not, see
>>> +   <http://www.gnu.org/licenses/>.  */
>>> +
>>> +#include <stdlib.h>
>>> +#include <unistd.h>
>>> +#include <limits.h>
>>> +#include <ldsodefs.h>
>>> +
>>> +/* Simplified implementation of realpath(): no dynamic memory use, no lstat(),
>>> +   no set_errno(), no valid "rpath" on error, etc.  This simplifies cases
>>> +   involving relative paths, specifically where $ORIGIN needs to be
>>> +   calculated. For this relatively rare case, one could also imagine using
>>> +   link_map.l_origin to avoid the getcwd() here, but the simpler code
>>> +   here seems like a better solution.  */
>>> +char * internal_function
>>> +__realpath (const char *name, char *rpath)
>>> +{
>>> +  char *dest;
>>> +  const char *start, *end;
>>> +
>>> +  if (name[0] != '/')
>>> +    {
>>> +      if (!__getcwd (rpath, PATH_MAX))
>>> +       return NULL;
>>> +      dest = __rawmemchr (rpath, '\0');
>>> +    }
>>> +  else
>>> +    {
>>> +      rpath[0] = '/';
>>> +      dest = rpath + 1;
>>> +    }
>>> +
>>> +  for (start = end = name; *start; start = end)
>>> +    {
>>> +      /* Skip sequence of multiple path-separators.  */
>>> +      while (*start == '/')
>>> +       ++start;
>>> +
>>> +      /* Find end of path component.  */
>>> +      for (end = start; *end && *end != '/'; ++end)
>>> +       /* Nothing.  */;
>>> +
>>> +      if (end - start == 0)
>>> +       break;
>>> +      else if (end - start == 1 && start[0] == '.')
>>> +       /* nothing */;
>>> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>> +       {
>>> +         /* Back up to previous component, ignore if at root already.  */
>>> +         if (dest > rpath + 1)
>>> +           while ((--dest)[-1] != '/');
>>> +       }
>>> +      else
>>> +       {
>>> +         if (dest[-1] != '/')
>>> +           *dest++ = '/';
>>> +
>>> +         if (dest + (end - start) >= rpath + PATH_MAX)
>>> +           return NULL;
>>> +
>>> +         dest = __mempcpy (dest, start, end - start);
>>> +         *dest = '\0';
>>> +       }
>>> +    }
>>> +  if (dest > rpath + 1 && dest[-1] == '/')
>>> +    --dest;
>>> +  *dest = '\0';
>>> +
>>> +  return rpath;
>>> +}
>>> diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
>>> index bcc00bc..45251d0 100644
>>> --- a/sysdeps/tile/dl-runtime.c
>>> +++ b/sysdeps/tile/dl-runtime.c
>>> @@ -29,70 +29,6 @@
>>>  #include <arch/sim.h>
>>>  #include <dl-unmap-segments.h>
>>>
>>> -/* Like realpath(), but simplified: no dynamic memory use, no lstat(),
>>> -   no set_errno(), no valid "rpath" on error, etc.  This handles some
>>> -   simple cases where the simulator might not have a valid entry for
>>> -   a loaded Elf object, in particular dlopen() with a relative path.
>>> -   For this relatively rare case, one could also imagine using
>>> -   link_map.l_origin to avoid the getcwd() here, but the simpler code
>>> -   here seems like a better solution.  */
>>> -static char *
>>> -dl_realpath (const char *name, char *rpath)
>>> -{
>>> -  char *dest;
>>> -  const char *start, *end;
>>> -
>>> -  if (name[0] != '/')
>>> -    {
>>> -      if (!__getcwd (rpath, PATH_MAX))
>>> -        return NULL;
>>> -      dest = __rawmemchr (rpath, '\0');
>>> -    }
>>> -  else
>>> -    {
>>> -      rpath[0] = '/';
>>> -      dest = rpath + 1;
>>> -    }
>>> -
>>> -  for (start = end = name; *start; start = end)
>>> -    {
>>> -      /* Skip sequence of multiple path-separators.  */
>>> -      while (*start == '/')
>>> -       ++start;
>>> -
>>> -      /* Find end of path component.  */
>>> -      for (end = start; *end && *end != '/'; ++end)
>>> -       /* Nothing.  */;
>>> -
>>> -      if (end - start == 0)
>>> -       break;
>>> -      else if (end - start == 1 && start[0] == '.')
>>> -       /* nothing */;
>>> -      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>> -       {
>>> -         /* Back up to previous component, ignore if at root already.  */
>>> -         if (dest > rpath + 1)
>>> -           while ((--dest)[-1] != '/');
>>> -       }
>>> -      else
>>> -       {
>>> -         if (dest[-1] != '/')
>>> -           *dest++ = '/';
>>> -
>>> -         if (dest + (end - start) >= rpath + PATH_MAX)
>>> -            return NULL;
>>> -
>>> -         dest = __mempcpy (dest, start, end - start);
>>> -         *dest = '\0';
>>> -       }
>>> -    }
>>> -  if (dest > rpath + 1 && dest[-1] == '/')
>>> -    --dest;
>>> -  *dest = '\0';
>>> -
>>> -  return rpath;
>>> -}
>>> -
>>>  /* Support notifying the simulator about new objects.  */
>>>  void internal_function
>>>  _dl_after_load (struct link_map *l)
>>> @@ -117,7 +53,7 @@ _dl_after_load (struct link_map *l)
>>>    DLPUTC (':');
>>>
>>>    /* Write the library path, including the terminating '\0'.  */
>>> -  path = dl_realpath (l->l_name, pathbuf) ?: l->l_name;
>>> +  path = __realpath (l->l_name, pathbuf) ?: l->l_name;
>>>    for (size_t i = 0;; i++)
>>>      {
>>>        DLPUTC (path[i]);
>>> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
>>> index 9ad6d22..4525429 100644
>>> --- a/sysdeps/unix/sysv/linux/Makefile
>>> +++ b/sysdeps/unix/sysv/linux/Makefile
>>> @@ -176,7 +176,7 @@ endif
>>>
>>>  ifeq ($(subdir),elf)
>>>  sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \
>>> -                       dl-fxstatat64
>>> +                       dl-fxstatat64 dl-lxstat64
>>>
>>>  CPPFLAGS-lddlibc4 += -DNOT_IN_libc
>>>
>>> diff --git a/sysdeps/unix/sysv/linux/dl-lxstat64.c b/sysdeps/unix/sysv/linux/dl-lxstat64.c
>>> new file mode 100644
>>> index 0000000..63e6800
>>> --- /dev/null
>>> +++ b/sysdeps/unix/sysv/linux/dl-lxstat64.c
>>> @@ -0,0 +1 @@
>>> +#include <lxstat64.c>
>>> diff --git a/sysdeps/unix/sysv/linux/dl-realpath.c b/sysdeps/unix/sysv/linux/dl-realpath.c
>>> new file mode 100644
>>> index 0000000..91652dc
>>> --- /dev/null
>>> +++ b/sysdeps/unix/sysv/linux/dl-realpath.c
>>> @@ -0,0 +1,121 @@
>>> +/* Dynamic loader version of realpath for linux.
>>> +   Copyright (C) 2014 Free Software Foundation, Inc.
>>> +   This file is part of the GNU C Library.
>>> +
>>> +   The GNU C Library is free software; you can redistribute it and/or
>>> +   modify it under the terms of the GNU Lesser General Public
>>> +   License as published by the Free Software Foundation; either
>>> +   version 2.1 of the License, or (at your option) any later version.
>>> +
>>> +   The GNU C Library is distributed in the hope that it will be useful,
>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>> +   Lesser General Public License for more details.
>>> +
>>> +   You should have received a copy of the GNU Lesser General Public
>>> +   License along with the GNU C Library; if not, see
>>> +   <http://www.gnu.org/licenses/>.  */
>>> +
>>> +#include <stdlib.h>
>>> +#include <unistd.h>
>>> +#include <limits.h>
>>> +#include <ldsodefs.h>
>>> +#include <sys/stat.h>
>>> +#include <kernel-features.h>
>>> +#include <sysdep.h>
>>> +
>>> +/* Simplified version of realpath which extends elf/dl-realpath.c to use
>>> + * linux syscalls for handling symlinks */
>>> +char * internal_function
>>> +__realpath (const char *name, char *rpath)
>>> +{
>>> +  char *dest;
>>> +  char extra_buf[PATH_MAX];
>>> +  const char *start, *end;
>>> +  int num_links = 0;
>>> +
>>> +  if (name[0] != '/')
>>> +    {
>>> +      if (!__getcwd (rpath, PATH_MAX))
>>> +       return NULL;
>>> +      dest = __rawmemchr (rpath, '\0');
>>> +    }
>>> +  else
>>> +    {
>>> +      rpath[0] = '/';
>>> +      dest = rpath + 1;
>>> +    }
>>> +
>>> +  for (start = end = name; *start; start = end)
>>> +    {
>>> +      struct stat64 st;
>>> +      int n;
>>> +      /* Skip sequence of multiple path-separators.  */
>>> +      while (*start == '/')
>>> +       ++start;
>>> +
>>> +      /* Find end of path component.  */
>>> +      for (end = start; *end && *end != '/'; ++end)
>>> +       /* Nothing.  */;
>>> +
>>> +      if (end - start == 0)
>>> +       break;
>>> +      else if (end - start == 1 && start[0] == '.')
>>> +       /* nothing */;
>>> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>> +       {
>>> +         /* Back up to previous component, ignore if at root already.  */
>>> +         if (dest > rpath + 1)
>>> +           while ((--dest)[-1] != '/');
>>> +       }
>>> +      else
>>> +       {
>>> +         if (dest[-1] != '/')
>>> +           *dest++ = '/';
>>> +
>>> +         if (dest + (end - start) >= rpath + PATH_MAX)
>>> +           return NULL;
>>> +
>>> +         dest = __mempcpy (dest, start, end - start);
>>> +         *dest = '\0';
>>> +         if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
>>> +           return NULL;
>>> +
>>> +         if (S_ISLNK (st.st_mode))
>>> +           {
>>> +             char buf[PATH_MAX];
>>> +             size_t len;
>>> +
>>> +             INTERNAL_SYSCALL_DECL (err);
>>> +
>>> +             n = INTERNAL_SYSCALL (readlink, err, 3, rpath, buf,
>>> +                                   PATH_MAX - 1);
>>> +
>>> +             if (n <= 0 || buf[0] == '[')
>>> +               return NULL;
>>> +             buf[n] = '\0';
>>> +
>>> +             len = strlen (end);
>>> +             if ((long int) (n + len) >= PATH_MAX)
>>> +               return NULL;
>>> +
>>> +             /* Careful here, end may be a pointer into extra_buf... */
>>> +             memmove (&extra_buf[n], end, len + 1);
>>> +             name = end = memcpy (extra_buf, buf, n);
>>> +
>>> +             if (buf[0] == '/')
>>> +               dest = rpath + 1;     /* Absolute symlink */
>>> +             else
>>> +               /* Back up to previous component, ignore if at root already: */
>>> +               if (dest > rpath + 1)
>>> +                 while ((--dest)[-1] != '/');
>>> +           }
>>> +       }
>>> +    }
>>> +  if (dest > rpath + 1 && dest[-1] == '/')
>>> +    --dest;
>>> +  *dest = '\0';
>>> +
>>> +  return rpath;
>>> +
>>> +}
>>> --
>>> 2.0.4
>>>
>>
>> ping.
Brennan Shacklett Oct. 26, 2014, 7:43 p.m. UTC | #9
ping

On Mon, Oct 6, 2014 at 9:18 AM, Brennan Shacklett <bpshacklett@gmail.com> wrote:
> ping
>
> On Wed, Oct 1, 2014 at 12:28 AM, Brennan Shacklett
> <bpshacklett@gmail.com> wrote:
>> ping
>>
>> On Tue, Sep 23, 2014 at 8:09 PM, Brennan Shacklett
>> <bpshacklett@gmail.com> wrote:
>>> On Tue, Aug 12, 2014 at 9:16 AM, Brennan Shacklett
>>> <bpshacklett@gmail.com> wrote:
>>>> Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
>>>> for shared libraries. Currently $ORIGIN is calculated correctly for executables
>>>> on linux, because readlink is called on /proc/self/exe, which means the
>>>> resulting path is absolute and has no symlinks.
>>>> Shared libraries with relative paths on the other hand are based
>>>> off of appending the name / path of the library to the current working
>>>> directory, which means if the library is a symlink, it is not followed, which
>>>> breaks RPATH $ORIGIN in the following scenario:
>>>>
>>>> libone.so and libtwo.so are both in ~/lib
>>>> libone.so needs libtwo.so, so libone.so has an RPATH of $ORIGIN.
>>>> If I run ldd on ~/lib/libone.so, libtwo.so is found and all is good.
>>>> If I create a symlink named ~/libone.so to ~/lib/libone.so, and run ldd on it
>>>> libtwo is not found, because $ORIGIN for the library is calculated as ~
>>>> instead of ~/lib.
>>>>
>>>> If I was to repeat the above test but instead of libone.so use an executable,
>>>> everything would work as expected, which is why I think the shared library
>>>> behavior is a bug.
>>>>
>>>> I ran into this in the real world when attempting to dynamically load a .so
>>>> for python SWIG bindings, because the .so which python was loading was
>>>> symlinked to the actual library directory where all of the .so's dependencies
>>>> were located.
>>>>
>>>> The attached patch fixes this behavior by having realname evaluated with
>>>> __realpath in _dl_map_object_from_fd, before _dl_new_object is called (which is
>>>> where l_origin is assigned). This also somewhat simplifies the code in
>>>> elf/dl-object.c, because realname is guaranteed to be an absolute path
>>>> generated by realpath, so I was able to remove the code dealing with relative
>>>> paths.
>>>> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
>>>> generic implementation, and extended it with lstat and readlink in the linux
>>>> version. This means the bug is still present on other systems than linux (the
>>>> generic implementation only returns an absolute path, it doesn't do anything
>>>> with symlinks), but if there is a way to get the generic version to follow
>>>> symlinks on all systems please let me know.
>>>>
>>>> I tested and ran the elf test suite on Gentoo Linux x86-64. If this change is
>>>> wanted, I will happily write a test to go along with it.
>>>> I don't have any sort of copyright attribution set up with the FSF, let me know
>>>> if it is necessary for this change.
>>>>
>>>> Thanks,
>>>> Brennan Shacklett
>>>>
>>>> 2014-08-12  Brennan Shacklett  <bpshacklett@gmail.com>
>>>>
>>>>         [BZ #17251]
>>>>         * elf/Makefile (rtld-routines): Add dl-realpath.
>>>>         * elf/dl-load.c (_dl_map_object_from_fd):
>>>>         Calculate realname with __realpath.
>>>>         * elf/dl-object.c (_dl_new_object): Remove relative path handling.
>>>>         * elf/dl-realpath.c: New file. Generic implementation of __realpath.
>>>>         * elf/tile/dl-runtime.c (_dl_after_load): Remove dl_realpath and
>>>>         change call to __realpath.
>>>>         * sysdeps/unix/sysv/linux/Makefile (sysdep-rtld-routines):
>>>>         Add dl-lxstat64.
>>>>         * sysdeps/unix/sysv/linux/dl-lxstat64.c: New file.
>>>>         * sysdeps/unix/sysv/linux/dl-realpath.c: New file. Handle symlinks
>>>>         unlike generic implementation.
>>>> ---
>>>>  elf/Makefile                          |   3 +-
>>>>  elf/dl-load.c                         |  18 +++++
>>>>  elf/dl-object.c                       |  54 ++-------------
>>>>  elf/dl-realpath.c                     |  85 ++++++++++++++++++++++++
>>>>  sysdeps/tile/dl-runtime.c             |  66 +------------------
>>>>  sysdeps/unix/sysv/linux/Makefile      |   2 +-
>>>>  sysdeps/unix/sysv/linux/dl-lxstat64.c |   1 +
>>>>  sysdeps/unix/sysv/linux/dl-realpath.c | 121 ++++++++++++++++++++++++++++++++++
>>>>  8 files changed, 236 insertions(+), 114 deletions(-)
>>>>  create mode 100644 elf/dl-realpath.c
>>>>  create mode 100644 sysdeps/unix/sysv/linux/dl-lxstat64.c
>>>>  create mode 100644 sysdeps/unix/sysv/linux/dl-realpath.c
>>>>
>>>> diff --git a/elf/Makefile b/elf/Makefile
>>>> index 25012cc..c130f21 100644
>>>> --- a/elf/Makefile
>>>> +++ b/elf/Makefile
>>>> @@ -43,7 +43,8 @@ shared-only-routines += dl-caller
>>>>
>>>>  # ld.so uses those routines, plus some special stuff for being the program
>>>>  # interpreter and operating independent of libc.
>>>> -rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal
>>>> +rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal \
>>>> +                  dl-realpath
>>>>  all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
>>>>
>>>>  CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
>>>> diff --git a/elf/dl-load.c b/elf/dl-load.c
>>>> index 016a99c..7353837 100644
>>>> --- a/elf/dl-load.c
>>>> +++ b/elf/dl-load.c
>>>> @@ -891,6 +891,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>>>>    int errval = 0;
>>>>    struct r_debug *r = _dl_debug_initialize (0, nsid);
>>>>    bool make_consistent = false;
>>>> +  char *absolutename = NULL;
>>>>
>>>>    /* Get file information.  */
>>>>    if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &st) < 0))
>>>> @@ -902,6 +903,23 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>>>>        lose (errval, fd, name, realname, l, errstring,
>>>>             make_consistent ? r : NULL, nsid);
>>>>      }
>>>> +  /* Find absolute pathname for object */
>>>> +  absolutename = (char *) malloc (PATH_MAX);
>>>> +  if (!absolutename)
>>>> +    {
>>>> +      errstring = N_("cannot allocate memory for absolute path");
>>>> +      goto call_lose_errno;
>>>> +    }
>>>> +
>>>> +  if (!__realpath (realname, absolutename))
>>>> +    {
>>>> +      free (absolutename);
>>>> +      errstring = N_("cannot find absolute path of shared object");
>>>> +      goto call_lose_errno;
>>>> +    }
>>>> +
>>>> +  free (realname);
>>>> +  realname = absolutename;
>>>>
>>>>    /* Look again to see if the real name matched another already loaded.  */
>>>>    for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
>>>> diff --git a/elf/dl-object.c b/elf/dl-object.c
>>>> index afd80a6..d93935b 100644
>>>> --- a/elf/dl-object.c
>>>> +++ b/elf/dl-object.c
>>>> @@ -158,55 +158,15 @@ _dl_new_object (char *realname, const char *libname, int type,
>>>>        char *origin;
>>>>        char *cp;
>>>>
>>>> -      if (realname[0] == '/')
>>>> +      /* It is an absolute path, calculated by realpath. Use it.
>>>> +       * But we have to make a copy since we strip out the trailing slash. */
>>>> +      assert (realname[0] == '/');
>>>> +      cp = origin = (char *) malloc (realname_len);
>>>> +      if (origin == NULL)
>>>>         {
>>>> -         /* It is an absolute path.  Use it.  But we have to make a
>>>> -            copy since we strip out the trailing slash.  */
>>>> -         cp = origin = (char *) malloc (realname_len);
>>>> -         if (origin == NULL)
>>>> -           {
>>>> -             origin = (char *) -1;
>>>> -             goto out;
>>>> -           }
>>>> +         origin = (char *) -1;
>>>> +         goto out;
>>>>         }
>>>> -      else
>>>> -       {
>>>> -         size_t len = realname_len;
>>>> -         char *result = NULL;
>>>> -
>>>> -         /* Get the current directory name.  */
>>>> -         origin = NULL;
>>>> -         do
>>>> -           {
>>>> -             char *new_origin;
>>>> -
>>>> -             len += 128;
>>>> -             new_origin = (char *) realloc (origin, len);
>>>> -             if (new_origin == NULL)
>>>> -               /* We exit the loop.  Note that result == NULL.  */
>>>> -               break;
>>>> -             origin = new_origin;
>>>> -           }
>>>> -         while ((result = __getcwd (origin, len - realname_len)) == NULL
>>>> -                && errno == ERANGE);
>>>> -
>>>> -         if (result == NULL)
>>>> -           {
>>>> -             /* We were not able to determine the current directory.
>>>> -                Note that free(origin) is OK if origin == NULL.  */
>>>> -             free (origin);
>>>> -             origin = (char *) -1;
>>>> -             goto out;
>>>> -           }
>>>> -
>>>> -         /* Find the end of the path and see whether we have to add a
>>>> -            slash.  We could use rawmemchr but this need not be
>>>> -            fast.  */
>>>> -         cp = (strchr) (origin, '\0');
>>>> -         if (cp[-1] != '/')
>>>> -           *cp++ = '/';
>>>> -       }
>>>> -
>>>>        /* Add the real file name.  */
>>>>        cp = __mempcpy (cp, realname, realname_len);
>>>>
>>>> diff --git a/elf/dl-realpath.c b/elf/dl-realpath.c
>>>> new file mode 100644
>>>> index 0000000..2549bb3
>>>> --- /dev/null
>>>> +++ b/elf/dl-realpath.c
>>>> @@ -0,0 +1,85 @@
>>>> +/* Dynamic loader version of realpath.
>>>> +   Copyright (C) 2014 Free Software Foundation, Inc.
>>>> +   This file is part of the GNU C Library.
>>>> +
>>>> +   The GNU C Library is free software; you can redistribute it and/or
>>>> +   modify it under the terms of the GNU Lesser General Public
>>>> +   License as published by the Free Software Foundation; either
>>>> +   version 2.1 of the License, or (at your option) any later version.
>>>> +
>>>> +   The GNU C Library is distributed in the hope that it will be useful,
>>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>>> +   Lesser General Public License for more details.
>>>> +
>>>> +   You should have received a copy of the GNU Lesser General Public
>>>> +   License along with the GNU C Library; if not, see
>>>> +   <http://www.gnu.org/licenses/>.  */
>>>> +
>>>> +#include <stdlib.h>
>>>> +#include <unistd.h>
>>>> +#include <limits.h>
>>>> +#include <ldsodefs.h>
>>>> +
>>>> +/* Simplified implementation of realpath(): no dynamic memory use, no lstat(),
>>>> +   no set_errno(), no valid "rpath" on error, etc.  This simplifies cases
>>>> +   involving relative paths, specifically where $ORIGIN needs to be
>>>> +   calculated. For this relatively rare case, one could also imagine using
>>>> +   link_map.l_origin to avoid the getcwd() here, but the simpler code
>>>> +   here seems like a better solution.  */
>>>> +char * internal_function
>>>> +__realpath (const char *name, char *rpath)
>>>> +{
>>>> +  char *dest;
>>>> +  const char *start, *end;
>>>> +
>>>> +  if (name[0] != '/')
>>>> +    {
>>>> +      if (!__getcwd (rpath, PATH_MAX))
>>>> +       return NULL;
>>>> +      dest = __rawmemchr (rpath, '\0');
>>>> +    }
>>>> +  else
>>>> +    {
>>>> +      rpath[0] = '/';
>>>> +      dest = rpath + 1;
>>>> +    }
>>>> +
>>>> +  for (start = end = name; *start; start = end)
>>>> +    {
>>>> +      /* Skip sequence of multiple path-separators.  */
>>>> +      while (*start == '/')
>>>> +       ++start;
>>>> +
>>>> +      /* Find end of path component.  */
>>>> +      for (end = start; *end && *end != '/'; ++end)
>>>> +       /* Nothing.  */;
>>>> +
>>>> +      if (end - start == 0)
>>>> +       break;
>>>> +      else if (end - start == 1 && start[0] == '.')
>>>> +       /* nothing */;
>>>> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>>> +       {
>>>> +         /* Back up to previous component, ignore if at root already.  */
>>>> +         if (dest > rpath + 1)
>>>> +           while ((--dest)[-1] != '/');
>>>> +       }
>>>> +      else
>>>> +       {
>>>> +         if (dest[-1] != '/')
>>>> +           *dest++ = '/';
>>>> +
>>>> +         if (dest + (end - start) >= rpath + PATH_MAX)
>>>> +           return NULL;
>>>> +
>>>> +         dest = __mempcpy (dest, start, end - start);
>>>> +         *dest = '\0';
>>>> +       }
>>>> +    }
>>>> +  if (dest > rpath + 1 && dest[-1] == '/')
>>>> +    --dest;
>>>> +  *dest = '\0';
>>>> +
>>>> +  return rpath;
>>>> +}
>>>> diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
>>>> index bcc00bc..45251d0 100644
>>>> --- a/sysdeps/tile/dl-runtime.c
>>>> +++ b/sysdeps/tile/dl-runtime.c
>>>> @@ -29,70 +29,6 @@
>>>>  #include <arch/sim.h>
>>>>  #include <dl-unmap-segments.h>
>>>>
>>>> -/* Like realpath(), but simplified: no dynamic memory use, no lstat(),
>>>> -   no set_errno(), no valid "rpath" on error, etc.  This handles some
>>>> -   simple cases where the simulator might not have a valid entry for
>>>> -   a loaded Elf object, in particular dlopen() with a relative path.
>>>> -   For this relatively rare case, one could also imagine using
>>>> -   link_map.l_origin to avoid the getcwd() here, but the simpler code
>>>> -   here seems like a better solution.  */
>>>> -static char *
>>>> -dl_realpath (const char *name, char *rpath)
>>>> -{
>>>> -  char *dest;
>>>> -  const char *start, *end;
>>>> -
>>>> -  if (name[0] != '/')
>>>> -    {
>>>> -      if (!__getcwd (rpath, PATH_MAX))
>>>> -        return NULL;
>>>> -      dest = __rawmemchr (rpath, '\0');
>>>> -    }
>>>> -  else
>>>> -    {
>>>> -      rpath[0] = '/';
>>>> -      dest = rpath + 1;
>>>> -    }
>>>> -
>>>> -  for (start = end = name; *start; start = end)
>>>> -    {
>>>> -      /* Skip sequence of multiple path-separators.  */
>>>> -      while (*start == '/')
>>>> -       ++start;
>>>> -
>>>> -      /* Find end of path component.  */
>>>> -      for (end = start; *end && *end != '/'; ++end)
>>>> -       /* Nothing.  */;
>>>> -
>>>> -      if (end - start == 0)
>>>> -       break;
>>>> -      else if (end - start == 1 && start[0] == '.')
>>>> -       /* nothing */;
>>>> -      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>>> -       {
>>>> -         /* Back up to previous component, ignore if at root already.  */
>>>> -         if (dest > rpath + 1)
>>>> -           while ((--dest)[-1] != '/');
>>>> -       }
>>>> -      else
>>>> -       {
>>>> -         if (dest[-1] != '/')
>>>> -           *dest++ = '/';
>>>> -
>>>> -         if (dest + (end - start) >= rpath + PATH_MAX)
>>>> -            return NULL;
>>>> -
>>>> -         dest = __mempcpy (dest, start, end - start);
>>>> -         *dest = '\0';
>>>> -       }
>>>> -    }
>>>> -  if (dest > rpath + 1 && dest[-1] == '/')
>>>> -    --dest;
>>>> -  *dest = '\0';
>>>> -
>>>> -  return rpath;
>>>> -}
>>>> -
>>>>  /* Support notifying the simulator about new objects.  */
>>>>  void internal_function
>>>>  _dl_after_load (struct link_map *l)
>>>> @@ -117,7 +53,7 @@ _dl_after_load (struct link_map *l)
>>>>    DLPUTC (':');
>>>>
>>>>    /* Write the library path, including the terminating '\0'.  */
>>>> -  path = dl_realpath (l->l_name, pathbuf) ?: l->l_name;
>>>> +  path = __realpath (l->l_name, pathbuf) ?: l->l_name;
>>>>    for (size_t i = 0;; i++)
>>>>      {
>>>>        DLPUTC (path[i]);
>>>> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
>>>> index 9ad6d22..4525429 100644
>>>> --- a/sysdeps/unix/sysv/linux/Makefile
>>>> +++ b/sysdeps/unix/sysv/linux/Makefile
>>>> @@ -176,7 +176,7 @@ endif
>>>>
>>>>  ifeq ($(subdir),elf)
>>>>  sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \
>>>> -                       dl-fxstatat64
>>>> +                       dl-fxstatat64 dl-lxstat64
>>>>
>>>>  CPPFLAGS-lddlibc4 += -DNOT_IN_libc
>>>>
>>>> diff --git a/sysdeps/unix/sysv/linux/dl-lxstat64.c b/sysdeps/unix/sysv/linux/dl-lxstat64.c
>>>> new file mode 100644
>>>> index 0000000..63e6800
>>>> --- /dev/null
>>>> +++ b/sysdeps/unix/sysv/linux/dl-lxstat64.c
>>>> @@ -0,0 +1 @@
>>>> +#include <lxstat64.c>
>>>> diff --git a/sysdeps/unix/sysv/linux/dl-realpath.c b/sysdeps/unix/sysv/linux/dl-realpath.c
>>>> new file mode 100644
>>>> index 0000000..91652dc
>>>> --- /dev/null
>>>> +++ b/sysdeps/unix/sysv/linux/dl-realpath.c
>>>> @@ -0,0 +1,121 @@
>>>> +/* Dynamic loader version of realpath for linux.
>>>> +   Copyright (C) 2014 Free Software Foundation, Inc.
>>>> +   This file is part of the GNU C Library.
>>>> +
>>>> +   The GNU C Library is free software; you can redistribute it and/or
>>>> +   modify it under the terms of the GNU Lesser General Public
>>>> +   License as published by the Free Software Foundation; either
>>>> +   version 2.1 of the License, or (at your option) any later version.
>>>> +
>>>> +   The GNU C Library is distributed in the hope that it will be useful,
>>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>>> +   Lesser General Public License for more details.
>>>> +
>>>> +   You should have received a copy of the GNU Lesser General Public
>>>> +   License along with the GNU C Library; if not, see
>>>> +   <http://www.gnu.org/licenses/>.  */
>>>> +
>>>> +#include <stdlib.h>
>>>> +#include <unistd.h>
>>>> +#include <limits.h>
>>>> +#include <ldsodefs.h>
>>>> +#include <sys/stat.h>
>>>> +#include <kernel-features.h>
>>>> +#include <sysdep.h>
>>>> +
>>>> +/* Simplified version of realpath which extends elf/dl-realpath.c to use
>>>> + * linux syscalls for handling symlinks */
>>>> +char * internal_function
>>>> +__realpath (const char *name, char *rpath)
>>>> +{
>>>> +  char *dest;
>>>> +  char extra_buf[PATH_MAX];
>>>> +  const char *start, *end;
>>>> +  int num_links = 0;
>>>> +
>>>> +  if (name[0] != '/')
>>>> +    {
>>>> +      if (!__getcwd (rpath, PATH_MAX))
>>>> +       return NULL;
>>>> +      dest = __rawmemchr (rpath, '\0');
>>>> +    }
>>>> +  else
>>>> +    {
>>>> +      rpath[0] = '/';
>>>> +      dest = rpath + 1;
>>>> +    }
>>>> +
>>>> +  for (start = end = name; *start; start = end)
>>>> +    {
>>>> +      struct stat64 st;
>>>> +      int n;
>>>> +      /* Skip sequence of multiple path-separators.  */
>>>> +      while (*start == '/')
>>>> +       ++start;
>>>> +
>>>> +      /* Find end of path component.  */
>>>> +      for (end = start; *end && *end != '/'; ++end)
>>>> +       /* Nothing.  */;
>>>> +
>>>> +      if (end - start == 0)
>>>> +       break;
>>>> +      else if (end - start == 1 && start[0] == '.')
>>>> +       /* nothing */;
>>>> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>>> +       {
>>>> +         /* Back up to previous component, ignore if at root already.  */
>>>> +         if (dest > rpath + 1)
>>>> +           while ((--dest)[-1] != '/');
>>>> +       }
>>>> +      else
>>>> +       {
>>>> +         if (dest[-1] != '/')
>>>> +           *dest++ = '/';
>>>> +
>>>> +         if (dest + (end - start) >= rpath + PATH_MAX)
>>>> +           return NULL;
>>>> +
>>>> +         dest = __mempcpy (dest, start, end - start);
>>>> +         *dest = '\0';
>>>> +         if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
>>>> +           return NULL;
>>>> +
>>>> +         if (S_ISLNK (st.st_mode))
>>>> +           {
>>>> +             char buf[PATH_MAX];
>>>> +             size_t len;
>>>> +
>>>> +             INTERNAL_SYSCALL_DECL (err);
>>>> +
>>>> +             n = INTERNAL_SYSCALL (readlink, err, 3, rpath, buf,
>>>> +                                   PATH_MAX - 1);
>>>> +
>>>> +             if (n <= 0 || buf[0] == '[')
>>>> +               return NULL;
>>>> +             buf[n] = '\0';
>>>> +
>>>> +             len = strlen (end);
>>>> +             if ((long int) (n + len) >= PATH_MAX)
>>>> +               return NULL;
>>>> +
>>>> +             /* Careful here, end may be a pointer into extra_buf... */
>>>> +             memmove (&extra_buf[n], end, len + 1);
>>>> +             name = end = memcpy (extra_buf, buf, n);
>>>> +
>>>> +             if (buf[0] == '/')
>>>> +               dest = rpath + 1;     /* Absolute symlink */
>>>> +             else
>>>> +               /* Back up to previous component, ignore if at root already: */
>>>> +               if (dest > rpath + 1)
>>>> +                 while ((--dest)[-1] != '/');
>>>> +           }
>>>> +       }
>>>> +    }
>>>> +  if (dest > rpath + 1 && dest[-1] == '/')
>>>> +    --dest;
>>>> +  *dest = '\0';
>>>> +
>>>> +  return rpath;
>>>> +
>>>> +}
>>>> --
>>>> 2.0.4
>>>>
>>>
>>> ping.
Brennan Shacklett Nov. 3, 2014, 11:31 p.m. UTC | #10
ping

On Sun, Oct 26, 2014 at 12:43 PM, Brennan Shacklett
<bpshacklett@gmail.com> wrote:
> ping
>
> On Mon, Oct 6, 2014 at 9:18 AM, Brennan Shacklett <bpshacklett@gmail.com> wrote:
>> ping
>>
>> On Wed, Oct 1, 2014 at 12:28 AM, Brennan Shacklett
>> <bpshacklett@gmail.com> wrote:
>>> ping
>>>
>>> On Tue, Sep 23, 2014 at 8:09 PM, Brennan Shacklett
>>> <bpshacklett@gmail.com> wrote:
>>>> On Tue, Aug 12, 2014 at 9:16 AM, Brennan Shacklett
>>>> <bpshacklett@gmail.com> wrote:
>>>>> Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
>>>>> for shared libraries. Currently $ORIGIN is calculated correctly for executables
>>>>> on linux, because readlink is called on /proc/self/exe, which means the
>>>>> resulting path is absolute and has no symlinks.
>>>>> Shared libraries with relative paths on the other hand are based
>>>>> off of appending the name / path of the library to the current working
>>>>> directory, which means if the library is a symlink, it is not followed, which
>>>>> breaks RPATH $ORIGIN in the following scenario:
>>>>>
>>>>> libone.so and libtwo.so are both in ~/lib
>>>>> libone.so needs libtwo.so, so libone.so has an RPATH of $ORIGIN.
>>>>> If I run ldd on ~/lib/libone.so, libtwo.so is found and all is good.
>>>>> If I create a symlink named ~/libone.so to ~/lib/libone.so, and run ldd on it
>>>>> libtwo is not found, because $ORIGIN for the library is calculated as ~
>>>>> instead of ~/lib.
>>>>>
>>>>> If I was to repeat the above test but instead of libone.so use an executable,
>>>>> everything would work as expected, which is why I think the shared library
>>>>> behavior is a bug.
>>>>>
>>>>> I ran into this in the real world when attempting to dynamically load a .so
>>>>> for python SWIG bindings, because the .so which python was loading was
>>>>> symlinked to the actual library directory where all of the .so's dependencies
>>>>> were located.
>>>>>
>>>>> The attached patch fixes this behavior by having realname evaluated with
>>>>> __realpath in _dl_map_object_from_fd, before _dl_new_object is called (which is
>>>>> where l_origin is assigned). This also somewhat simplifies the code in
>>>>> elf/dl-object.c, because realname is guaranteed to be an absolute path
>>>>> generated by realpath, so I was able to remove the code dealing with relative
>>>>> paths.
>>>>> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
>>>>> generic implementation, and extended it with lstat and readlink in the linux
>>>>> version. This means the bug is still present on other systems than linux (the
>>>>> generic implementation only returns an absolute path, it doesn't do anything
>>>>> with symlinks), but if there is a way to get the generic version to follow
>>>>> symlinks on all systems please let me know.
>>>>>
>>>>> I tested and ran the elf test suite on Gentoo Linux x86-64. If this change is
>>>>> wanted, I will happily write a test to go along with it.
>>>>> I don't have any sort of copyright attribution set up with the FSF, let me know
>>>>> if it is necessary for this change.
>>>>>
>>>>> Thanks,
>>>>> Brennan Shacklett
>>>>>
>>>>> 2014-08-12  Brennan Shacklett  <bpshacklett@gmail.com>
>>>>>
>>>>>         [BZ #17251]
>>>>>         * elf/Makefile (rtld-routines): Add dl-realpath.
>>>>>         * elf/dl-load.c (_dl_map_object_from_fd):
>>>>>         Calculate realname with __realpath.
>>>>>         * elf/dl-object.c (_dl_new_object): Remove relative path handling.
>>>>>         * elf/dl-realpath.c: New file. Generic implementation of __realpath.
>>>>>         * elf/tile/dl-runtime.c (_dl_after_load): Remove dl_realpath and
>>>>>         change call to __realpath.
>>>>>         * sysdeps/unix/sysv/linux/Makefile (sysdep-rtld-routines):
>>>>>         Add dl-lxstat64.
>>>>>         * sysdeps/unix/sysv/linux/dl-lxstat64.c: New file.
>>>>>         * sysdeps/unix/sysv/linux/dl-realpath.c: New file. Handle symlinks
>>>>>         unlike generic implementation.
>>>>> ---
>>>>>  elf/Makefile                          |   3 +-
>>>>>  elf/dl-load.c                         |  18 +++++
>>>>>  elf/dl-object.c                       |  54 ++-------------
>>>>>  elf/dl-realpath.c                     |  85 ++++++++++++++++++++++++
>>>>>  sysdeps/tile/dl-runtime.c             |  66 +------------------
>>>>>  sysdeps/unix/sysv/linux/Makefile      |   2 +-
>>>>>  sysdeps/unix/sysv/linux/dl-lxstat64.c |   1 +
>>>>>  sysdeps/unix/sysv/linux/dl-realpath.c | 121 ++++++++++++++++++++++++++++++++++
>>>>>  8 files changed, 236 insertions(+), 114 deletions(-)
>>>>>  create mode 100644 elf/dl-realpath.c
>>>>>  create mode 100644 sysdeps/unix/sysv/linux/dl-lxstat64.c
>>>>>  create mode 100644 sysdeps/unix/sysv/linux/dl-realpath.c
>>>>>
>>>>> diff --git a/elf/Makefile b/elf/Makefile
>>>>> index 25012cc..c130f21 100644
>>>>> --- a/elf/Makefile
>>>>> +++ b/elf/Makefile
>>>>> @@ -43,7 +43,8 @@ shared-only-routines += dl-caller
>>>>>
>>>>>  # ld.so uses those routines, plus some special stuff for being the program
>>>>>  # interpreter and operating independent of libc.
>>>>> -rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal
>>>>> +rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal \
>>>>> +                  dl-realpath
>>>>>  all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
>>>>>
>>>>>  CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
>>>>> diff --git a/elf/dl-load.c b/elf/dl-load.c
>>>>> index 016a99c..7353837 100644
>>>>> --- a/elf/dl-load.c
>>>>> +++ b/elf/dl-load.c
>>>>> @@ -891,6 +891,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>>>>>    int errval = 0;
>>>>>    struct r_debug *r = _dl_debug_initialize (0, nsid);
>>>>>    bool make_consistent = false;
>>>>> +  char *absolutename = NULL;
>>>>>
>>>>>    /* Get file information.  */
>>>>>    if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &st) < 0))
>>>>> @@ -902,6 +903,23 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>>>>>        lose (errval, fd, name, realname, l, errstring,
>>>>>             make_consistent ? r : NULL, nsid);
>>>>>      }
>>>>> +  /* Find absolute pathname for object */
>>>>> +  absolutename = (char *) malloc (PATH_MAX);
>>>>> +  if (!absolutename)
>>>>> +    {
>>>>> +      errstring = N_("cannot allocate memory for absolute path");
>>>>> +      goto call_lose_errno;
>>>>> +    }
>>>>> +
>>>>> +  if (!__realpath (realname, absolutename))
>>>>> +    {
>>>>> +      free (absolutename);
>>>>> +      errstring = N_("cannot find absolute path of shared object");
>>>>> +      goto call_lose_errno;
>>>>> +    }
>>>>> +
>>>>> +  free (realname);
>>>>> +  realname = absolutename;
>>>>>
>>>>>    /* Look again to see if the real name matched another already loaded.  */
>>>>>    for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
>>>>> diff --git a/elf/dl-object.c b/elf/dl-object.c
>>>>> index afd80a6..d93935b 100644
>>>>> --- a/elf/dl-object.c
>>>>> +++ b/elf/dl-object.c
>>>>> @@ -158,55 +158,15 @@ _dl_new_object (char *realname, const char *libname, int type,
>>>>>        char *origin;
>>>>>        char *cp;
>>>>>
>>>>> -      if (realname[0] == '/')
>>>>> +      /* It is an absolute path, calculated by realpath. Use it.
>>>>> +       * But we have to make a copy since we strip out the trailing slash. */
>>>>> +      assert (realname[0] == '/');
>>>>> +      cp = origin = (char *) malloc (realname_len);
>>>>> +      if (origin == NULL)
>>>>>         {
>>>>> -         /* It is an absolute path.  Use it.  But we have to make a
>>>>> -            copy since we strip out the trailing slash.  */
>>>>> -         cp = origin = (char *) malloc (realname_len);
>>>>> -         if (origin == NULL)
>>>>> -           {
>>>>> -             origin = (char *) -1;
>>>>> -             goto out;
>>>>> -           }
>>>>> +         origin = (char *) -1;
>>>>> +         goto out;
>>>>>         }
>>>>> -      else
>>>>> -       {
>>>>> -         size_t len = realname_len;
>>>>> -         char *result = NULL;
>>>>> -
>>>>> -         /* Get the current directory name.  */
>>>>> -         origin = NULL;
>>>>> -         do
>>>>> -           {
>>>>> -             char *new_origin;
>>>>> -
>>>>> -             len += 128;
>>>>> -             new_origin = (char *) realloc (origin, len);
>>>>> -             if (new_origin == NULL)
>>>>> -               /* We exit the loop.  Note that result == NULL.  */
>>>>> -               break;
>>>>> -             origin = new_origin;
>>>>> -           }
>>>>> -         while ((result = __getcwd (origin, len - realname_len)) == NULL
>>>>> -                && errno == ERANGE);
>>>>> -
>>>>> -         if (result == NULL)
>>>>> -           {
>>>>> -             /* We were not able to determine the current directory.
>>>>> -                Note that free(origin) is OK if origin == NULL.  */
>>>>> -             free (origin);
>>>>> -             origin = (char *) -1;
>>>>> -             goto out;
>>>>> -           }
>>>>> -
>>>>> -         /* Find the end of the path and see whether we have to add a
>>>>> -            slash.  We could use rawmemchr but this need not be
>>>>> -            fast.  */
>>>>> -         cp = (strchr) (origin, '\0');
>>>>> -         if (cp[-1] != '/')
>>>>> -           *cp++ = '/';
>>>>> -       }
>>>>> -
>>>>>        /* Add the real file name.  */
>>>>>        cp = __mempcpy (cp, realname, realname_len);
>>>>>
>>>>> diff --git a/elf/dl-realpath.c b/elf/dl-realpath.c
>>>>> new file mode 100644
>>>>> index 0000000..2549bb3
>>>>> --- /dev/null
>>>>> +++ b/elf/dl-realpath.c
>>>>> @@ -0,0 +1,85 @@
>>>>> +/* Dynamic loader version of realpath.
>>>>> +   Copyright (C) 2014 Free Software Foundation, Inc.
>>>>> +   This file is part of the GNU C Library.
>>>>> +
>>>>> +   The GNU C Library is free software; you can redistribute it and/or
>>>>> +   modify it under the terms of the GNU Lesser General Public
>>>>> +   License as published by the Free Software Foundation; either
>>>>> +   version 2.1 of the License, or (at your option) any later version.
>>>>> +
>>>>> +   The GNU C Library is distributed in the hope that it will be useful,
>>>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>>>> +   Lesser General Public License for more details.
>>>>> +
>>>>> +   You should have received a copy of the GNU Lesser General Public
>>>>> +   License along with the GNU C Library; if not, see
>>>>> +   <http://www.gnu.org/licenses/>.  */
>>>>> +
>>>>> +#include <stdlib.h>
>>>>> +#include <unistd.h>
>>>>> +#include <limits.h>
>>>>> +#include <ldsodefs.h>
>>>>> +
>>>>> +/* Simplified implementation of realpath(): no dynamic memory use, no lstat(),
>>>>> +   no set_errno(), no valid "rpath" on error, etc.  This simplifies cases
>>>>> +   involving relative paths, specifically where $ORIGIN needs to be
>>>>> +   calculated. For this relatively rare case, one could also imagine using
>>>>> +   link_map.l_origin to avoid the getcwd() here, but the simpler code
>>>>> +   here seems like a better solution.  */
>>>>> +char * internal_function
>>>>> +__realpath (const char *name, char *rpath)
>>>>> +{
>>>>> +  char *dest;
>>>>> +  const char *start, *end;
>>>>> +
>>>>> +  if (name[0] != '/')
>>>>> +    {
>>>>> +      if (!__getcwd (rpath, PATH_MAX))
>>>>> +       return NULL;
>>>>> +      dest = __rawmemchr (rpath, '\0');
>>>>> +    }
>>>>> +  else
>>>>> +    {
>>>>> +      rpath[0] = '/';
>>>>> +      dest = rpath + 1;
>>>>> +    }
>>>>> +
>>>>> +  for (start = end = name; *start; start = end)
>>>>> +    {
>>>>> +      /* Skip sequence of multiple path-separators.  */
>>>>> +      while (*start == '/')
>>>>> +       ++start;
>>>>> +
>>>>> +      /* Find end of path component.  */
>>>>> +      for (end = start; *end && *end != '/'; ++end)
>>>>> +       /* Nothing.  */;
>>>>> +
>>>>> +      if (end - start == 0)
>>>>> +       break;
>>>>> +      else if (end - start == 1 && start[0] == '.')
>>>>> +       /* nothing */;
>>>>> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>>>> +       {
>>>>> +         /* Back up to previous component, ignore if at root already.  */
>>>>> +         if (dest > rpath + 1)
>>>>> +           while ((--dest)[-1] != '/');
>>>>> +       }
>>>>> +      else
>>>>> +       {
>>>>> +         if (dest[-1] != '/')
>>>>> +           *dest++ = '/';
>>>>> +
>>>>> +         if (dest + (end - start) >= rpath + PATH_MAX)
>>>>> +           return NULL;
>>>>> +
>>>>> +         dest = __mempcpy (dest, start, end - start);
>>>>> +         *dest = '\0';
>>>>> +       }
>>>>> +    }
>>>>> +  if (dest > rpath + 1 && dest[-1] == '/')
>>>>> +    --dest;
>>>>> +  *dest = '\0';
>>>>> +
>>>>> +  return rpath;
>>>>> +}
>>>>> diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
>>>>> index bcc00bc..45251d0 100644
>>>>> --- a/sysdeps/tile/dl-runtime.c
>>>>> +++ b/sysdeps/tile/dl-runtime.c
>>>>> @@ -29,70 +29,6 @@
>>>>>  #include <arch/sim.h>
>>>>>  #include <dl-unmap-segments.h>
>>>>>
>>>>> -/* Like realpath(), but simplified: no dynamic memory use, no lstat(),
>>>>> -   no set_errno(), no valid "rpath" on error, etc.  This handles some
>>>>> -   simple cases where the simulator might not have a valid entry for
>>>>> -   a loaded Elf object, in particular dlopen() with a relative path.
>>>>> -   For this relatively rare case, one could also imagine using
>>>>> -   link_map.l_origin to avoid the getcwd() here, but the simpler code
>>>>> -   here seems like a better solution.  */
>>>>> -static char *
>>>>> -dl_realpath (const char *name, char *rpath)
>>>>> -{
>>>>> -  char *dest;
>>>>> -  const char *start, *end;
>>>>> -
>>>>> -  if (name[0] != '/')
>>>>> -    {
>>>>> -      if (!__getcwd (rpath, PATH_MAX))
>>>>> -        return NULL;
>>>>> -      dest = __rawmemchr (rpath, '\0');
>>>>> -    }
>>>>> -  else
>>>>> -    {
>>>>> -      rpath[0] = '/';
>>>>> -      dest = rpath + 1;
>>>>> -    }
>>>>> -
>>>>> -  for (start = end = name; *start; start = end)
>>>>> -    {
>>>>> -      /* Skip sequence of multiple path-separators.  */
>>>>> -      while (*start == '/')
>>>>> -       ++start;
>>>>> -
>>>>> -      /* Find end of path component.  */
>>>>> -      for (end = start; *end && *end != '/'; ++end)
>>>>> -       /* Nothing.  */;
>>>>> -
>>>>> -      if (end - start == 0)
>>>>> -       break;
>>>>> -      else if (end - start == 1 && start[0] == '.')
>>>>> -       /* nothing */;
>>>>> -      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>>>> -       {
>>>>> -         /* Back up to previous component, ignore if at root already.  */
>>>>> -         if (dest > rpath + 1)
>>>>> -           while ((--dest)[-1] != '/');
>>>>> -       }
>>>>> -      else
>>>>> -       {
>>>>> -         if (dest[-1] != '/')
>>>>> -           *dest++ = '/';
>>>>> -
>>>>> -         if (dest + (end - start) >= rpath + PATH_MAX)
>>>>> -            return NULL;
>>>>> -
>>>>> -         dest = __mempcpy (dest, start, end - start);
>>>>> -         *dest = '\0';
>>>>> -       }
>>>>> -    }
>>>>> -  if (dest > rpath + 1 && dest[-1] == '/')
>>>>> -    --dest;
>>>>> -  *dest = '\0';
>>>>> -
>>>>> -  return rpath;
>>>>> -}
>>>>> -
>>>>>  /* Support notifying the simulator about new objects.  */
>>>>>  void internal_function
>>>>>  _dl_after_load (struct link_map *l)
>>>>> @@ -117,7 +53,7 @@ _dl_after_load (struct link_map *l)
>>>>>    DLPUTC (':');
>>>>>
>>>>>    /* Write the library path, including the terminating '\0'.  */
>>>>> -  path = dl_realpath (l->l_name, pathbuf) ?: l->l_name;
>>>>> +  path = __realpath (l->l_name, pathbuf) ?: l->l_name;
>>>>>    for (size_t i = 0;; i++)
>>>>>      {
>>>>>        DLPUTC (path[i]);
>>>>> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
>>>>> index 9ad6d22..4525429 100644
>>>>> --- a/sysdeps/unix/sysv/linux/Makefile
>>>>> +++ b/sysdeps/unix/sysv/linux/Makefile
>>>>> @@ -176,7 +176,7 @@ endif
>>>>>
>>>>>  ifeq ($(subdir),elf)
>>>>>  sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \
>>>>> -                       dl-fxstatat64
>>>>> +                       dl-fxstatat64 dl-lxstat64
>>>>>
>>>>>  CPPFLAGS-lddlibc4 += -DNOT_IN_libc
>>>>>
>>>>> diff --git a/sysdeps/unix/sysv/linux/dl-lxstat64.c b/sysdeps/unix/sysv/linux/dl-lxstat64.c
>>>>> new file mode 100644
>>>>> index 0000000..63e6800
>>>>> --- /dev/null
>>>>> +++ b/sysdeps/unix/sysv/linux/dl-lxstat64.c
>>>>> @@ -0,0 +1 @@
>>>>> +#include <lxstat64.c>
>>>>> diff --git a/sysdeps/unix/sysv/linux/dl-realpath.c b/sysdeps/unix/sysv/linux/dl-realpath.c
>>>>> new file mode 100644
>>>>> index 0000000..91652dc
>>>>> --- /dev/null
>>>>> +++ b/sysdeps/unix/sysv/linux/dl-realpath.c
>>>>> @@ -0,0 +1,121 @@
>>>>> +/* Dynamic loader version of realpath for linux.
>>>>> +   Copyright (C) 2014 Free Software Foundation, Inc.
>>>>> +   This file is part of the GNU C Library.
>>>>> +
>>>>> +   The GNU C Library is free software; you can redistribute it and/or
>>>>> +   modify it under the terms of the GNU Lesser General Public
>>>>> +   License as published by the Free Software Foundation; either
>>>>> +   version 2.1 of the License, or (at your option) any later version.
>>>>> +
>>>>> +   The GNU C Library is distributed in the hope that it will be useful,
>>>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>>>> +   Lesser General Public License for more details.
>>>>> +
>>>>> +   You should have received a copy of the GNU Lesser General Public
>>>>> +   License along with the GNU C Library; if not, see
>>>>> +   <http://www.gnu.org/licenses/>.  */
>>>>> +
>>>>> +#include <stdlib.h>
>>>>> +#include <unistd.h>
>>>>> +#include <limits.h>
>>>>> +#include <ldsodefs.h>
>>>>> +#include <sys/stat.h>
>>>>> +#include <kernel-features.h>
>>>>> +#include <sysdep.h>
>>>>> +
>>>>> +/* Simplified version of realpath which extends elf/dl-realpath.c to use
>>>>> + * linux syscalls for handling symlinks */
>>>>> +char * internal_function
>>>>> +__realpath (const char *name, char *rpath)
>>>>> +{
>>>>> +  char *dest;
>>>>> +  char extra_buf[PATH_MAX];
>>>>> +  const char *start, *end;
>>>>> +  int num_links = 0;
>>>>> +
>>>>> +  if (name[0] != '/')
>>>>> +    {
>>>>> +      if (!__getcwd (rpath, PATH_MAX))
>>>>> +       return NULL;
>>>>> +      dest = __rawmemchr (rpath, '\0');
>>>>> +    }
>>>>> +  else
>>>>> +    {
>>>>> +      rpath[0] = '/';
>>>>> +      dest = rpath + 1;
>>>>> +    }
>>>>> +
>>>>> +  for (start = end = name; *start; start = end)
>>>>> +    {
>>>>> +      struct stat64 st;
>>>>> +      int n;
>>>>> +      /* Skip sequence of multiple path-separators.  */
>>>>> +      while (*start == '/')
>>>>> +       ++start;
>>>>> +
>>>>> +      /* Find end of path component.  */
>>>>> +      for (end = start; *end && *end != '/'; ++end)
>>>>> +       /* Nothing.  */;
>>>>> +
>>>>> +      if (end - start == 0)
>>>>> +       break;
>>>>> +      else if (end - start == 1 && start[0] == '.')
>>>>> +       /* nothing */;
>>>>> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>>>> +       {
>>>>> +         /* Back up to previous component, ignore if at root already.  */
>>>>> +         if (dest > rpath + 1)
>>>>> +           while ((--dest)[-1] != '/');
>>>>> +       }
>>>>> +      else
>>>>> +       {
>>>>> +         if (dest[-1] != '/')
>>>>> +           *dest++ = '/';
>>>>> +
>>>>> +         if (dest + (end - start) >= rpath + PATH_MAX)
>>>>> +           return NULL;
>>>>> +
>>>>> +         dest = __mempcpy (dest, start, end - start);
>>>>> +         *dest = '\0';
>>>>> +         if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
>>>>> +           return NULL;
>>>>> +
>>>>> +         if (S_ISLNK (st.st_mode))
>>>>> +           {
>>>>> +             char buf[PATH_MAX];
>>>>> +             size_t len;
>>>>> +
>>>>> +             INTERNAL_SYSCALL_DECL (err);
>>>>> +
>>>>> +             n = INTERNAL_SYSCALL (readlink, err, 3, rpath, buf,
>>>>> +                                   PATH_MAX - 1);
>>>>> +
>>>>> +             if (n <= 0 || buf[0] == '[')
>>>>> +               return NULL;
>>>>> +             buf[n] = '\0';
>>>>> +
>>>>> +             len = strlen (end);
>>>>> +             if ((long int) (n + len) >= PATH_MAX)
>>>>> +               return NULL;
>>>>> +
>>>>> +             /* Careful here, end may be a pointer into extra_buf... */
>>>>> +             memmove (&extra_buf[n], end, len + 1);
>>>>> +             name = end = memcpy (extra_buf, buf, n);
>>>>> +
>>>>> +             if (buf[0] == '/')
>>>>> +               dest = rpath + 1;     /* Absolute symlink */
>>>>> +             else
>>>>> +               /* Back up to previous component, ignore if at root already: */
>>>>> +               if (dest > rpath + 1)
>>>>> +                 while ((--dest)[-1] != '/');
>>>>> +           }
>>>>> +       }
>>>>> +    }
>>>>> +  if (dest > rpath + 1 && dest[-1] == '/')
>>>>> +    --dest;
>>>>> +  *dest = '\0';
>>>>> +
>>>>> +  return rpath;
>>>>> +
>>>>> +}
>>>>> --
>>>>> 2.0.4
>>>>>
>>>>
>>>> ping.
Brennan Shacklett Nov. 12, 2014, 11:34 p.m. UTC | #11
ping

On Mon, Nov 3, 2014 at 3:31 PM, Brennan Shacklett <bpshacklett@gmail.com> wrote:
> ping
>
> On Sun, Oct 26, 2014 at 12:43 PM, Brennan Shacklett
> <bpshacklett@gmail.com> wrote:
>> ping
>>
>> On Mon, Oct 6, 2014 at 9:18 AM, Brennan Shacklett <bpshacklett@gmail.com> wrote:
>>> ping
>>>
>>> On Wed, Oct 1, 2014 at 12:28 AM, Brennan Shacklett
>>> <bpshacklett@gmail.com> wrote:
>>>> ping
>>>>
>>>> On Tue, Sep 23, 2014 at 8:09 PM, Brennan Shacklett
>>>> <bpshacklett@gmail.com> wrote:
>>>>> On Tue, Aug 12, 2014 at 9:16 AM, Brennan Shacklett
>>>>> <bpshacklett@gmail.com> wrote:
>>>>>> Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
>>>>>> for shared libraries. Currently $ORIGIN is calculated correctly for executables
>>>>>> on linux, because readlink is called on /proc/self/exe, which means the
>>>>>> resulting path is absolute and has no symlinks.
>>>>>> Shared libraries with relative paths on the other hand are based
>>>>>> off of appending the name / path of the library to the current working
>>>>>> directory, which means if the library is a symlink, it is not followed, which
>>>>>> breaks RPATH $ORIGIN in the following scenario:
>>>>>>
>>>>>> libone.so and libtwo.so are both in ~/lib
>>>>>> libone.so needs libtwo.so, so libone.so has an RPATH of $ORIGIN.
>>>>>> If I run ldd on ~/lib/libone.so, libtwo.so is found and all is good.
>>>>>> If I create a symlink named ~/libone.so to ~/lib/libone.so, and run ldd on it
>>>>>> libtwo is not found, because $ORIGIN for the library is calculated as ~
>>>>>> instead of ~/lib.
>>>>>>
>>>>>> If I was to repeat the above test but instead of libone.so use an executable,
>>>>>> everything would work as expected, which is why I think the shared library
>>>>>> behavior is a bug.
>>>>>>
>>>>>> I ran into this in the real world when attempting to dynamically load a .so
>>>>>> for python SWIG bindings, because the .so which python was loading was
>>>>>> symlinked to the actual library directory where all of the .so's dependencies
>>>>>> were located.
>>>>>>
>>>>>> The attached patch fixes this behavior by having realname evaluated with
>>>>>> __realpath in _dl_map_object_from_fd, before _dl_new_object is called (which is
>>>>>> where l_origin is assigned). This also somewhat simplifies the code in
>>>>>> elf/dl-object.c, because realname is guaranteed to be an absolute path
>>>>>> generated by realpath, so I was able to remove the code dealing with relative
>>>>>> paths.
>>>>>> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
>>>>>> generic implementation, and extended it with lstat and readlink in the linux
>>>>>> version. This means the bug is still present on other systems than linux (the
>>>>>> generic implementation only returns an absolute path, it doesn't do anything
>>>>>> with symlinks), but if there is a way to get the generic version to follow
>>>>>> symlinks on all systems please let me know.
>>>>>>
>>>>>> I tested and ran the elf test suite on Gentoo Linux x86-64. If this change is
>>>>>> wanted, I will happily write a test to go along with it.
>>>>>> I don't have any sort of copyright attribution set up with the FSF, let me know
>>>>>> if it is necessary for this change.
>>>>>>
>>>>>> Thanks,
>>>>>> Brennan Shacklett
>>>>>>
>>>>>> 2014-08-12  Brennan Shacklett  <bpshacklett@gmail.com>
>>>>>>
>>>>>>         [BZ #17251]
>>>>>>         * elf/Makefile (rtld-routines): Add dl-realpath.
>>>>>>         * elf/dl-load.c (_dl_map_object_from_fd):
>>>>>>         Calculate realname with __realpath.
>>>>>>         * elf/dl-object.c (_dl_new_object): Remove relative path handling.
>>>>>>         * elf/dl-realpath.c: New file. Generic implementation of __realpath.
>>>>>>         * elf/tile/dl-runtime.c (_dl_after_load): Remove dl_realpath and
>>>>>>         change call to __realpath.
>>>>>>         * sysdeps/unix/sysv/linux/Makefile (sysdep-rtld-routines):
>>>>>>         Add dl-lxstat64.
>>>>>>         * sysdeps/unix/sysv/linux/dl-lxstat64.c: New file.
>>>>>>         * sysdeps/unix/sysv/linux/dl-realpath.c: New file. Handle symlinks
>>>>>>         unlike generic implementation.
>>>>>> ---
>>>>>>  elf/Makefile                          |   3 +-
>>>>>>  elf/dl-load.c                         |  18 +++++
>>>>>>  elf/dl-object.c                       |  54 ++-------------
>>>>>>  elf/dl-realpath.c                     |  85 ++++++++++++++++++++++++
>>>>>>  sysdeps/tile/dl-runtime.c             |  66 +------------------
>>>>>>  sysdeps/unix/sysv/linux/Makefile      |   2 +-
>>>>>>  sysdeps/unix/sysv/linux/dl-lxstat64.c |   1 +
>>>>>>  sysdeps/unix/sysv/linux/dl-realpath.c | 121 ++++++++++++++++++++++++++++++++++
>>>>>>  8 files changed, 236 insertions(+), 114 deletions(-)
>>>>>>  create mode 100644 elf/dl-realpath.c
>>>>>>  create mode 100644 sysdeps/unix/sysv/linux/dl-lxstat64.c
>>>>>>  create mode 100644 sysdeps/unix/sysv/linux/dl-realpath.c
>>>>>>
>>>>>> diff --git a/elf/Makefile b/elf/Makefile
>>>>>> index 25012cc..c130f21 100644
>>>>>> --- a/elf/Makefile
>>>>>> +++ b/elf/Makefile
>>>>>> @@ -43,7 +43,8 @@ shared-only-routines += dl-caller
>>>>>>
>>>>>>  # ld.so uses those routines, plus some special stuff for being the program
>>>>>>  # interpreter and operating independent of libc.
>>>>>> -rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal
>>>>>> +rtld-routines  := rtld $(dl-routines) dl-sysdep dl-environ dl-minimal \
>>>>>> +                  dl-realpath
>>>>>>  all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
>>>>>>
>>>>>>  CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
>>>>>> diff --git a/elf/dl-load.c b/elf/dl-load.c
>>>>>> index 016a99c..7353837 100644
>>>>>> --- a/elf/dl-load.c
>>>>>> +++ b/elf/dl-load.c
>>>>>> @@ -891,6 +891,7 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>>>>>>    int errval = 0;
>>>>>>    struct r_debug *r = _dl_debug_initialize (0, nsid);
>>>>>>    bool make_consistent = false;
>>>>>> +  char *absolutename = NULL;
>>>>>>
>>>>>>    /* Get file information.  */
>>>>>>    if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &st) < 0))
>>>>>> @@ -902,6 +903,23 @@ _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
>>>>>>        lose (errval, fd, name, realname, l, errstring,
>>>>>>             make_consistent ? r : NULL, nsid);
>>>>>>      }
>>>>>> +  /* Find absolute pathname for object */
>>>>>> +  absolutename = (char *) malloc (PATH_MAX);
>>>>>> +  if (!absolutename)
>>>>>> +    {
>>>>>> +      errstring = N_("cannot allocate memory for absolute path");
>>>>>> +      goto call_lose_errno;
>>>>>> +    }
>>>>>> +
>>>>>> +  if (!__realpath (realname, absolutename))
>>>>>> +    {
>>>>>> +      free (absolutename);
>>>>>> +      errstring = N_("cannot find absolute path of shared object");
>>>>>> +      goto call_lose_errno;
>>>>>> +    }
>>>>>> +
>>>>>> +  free (realname);
>>>>>> +  realname = absolutename;
>>>>>>
>>>>>>    /* Look again to see if the real name matched another already loaded.  */
>>>>>>    for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
>>>>>> diff --git a/elf/dl-object.c b/elf/dl-object.c
>>>>>> index afd80a6..d93935b 100644
>>>>>> --- a/elf/dl-object.c
>>>>>> +++ b/elf/dl-object.c
>>>>>> @@ -158,55 +158,15 @@ _dl_new_object (char *realname, const char *libname, int type,
>>>>>>        char *origin;
>>>>>>        char *cp;
>>>>>>
>>>>>> -      if (realname[0] == '/')
>>>>>> +      /* It is an absolute path, calculated by realpath. Use it.
>>>>>> +       * But we have to make a copy since we strip out the trailing slash. */
>>>>>> +      assert (realname[0] == '/');
>>>>>> +      cp = origin = (char *) malloc (realname_len);
>>>>>> +      if (origin == NULL)
>>>>>>         {
>>>>>> -         /* It is an absolute path.  Use it.  But we have to make a
>>>>>> -            copy since we strip out the trailing slash.  */
>>>>>> -         cp = origin = (char *) malloc (realname_len);
>>>>>> -         if (origin == NULL)
>>>>>> -           {
>>>>>> -             origin = (char *) -1;
>>>>>> -             goto out;
>>>>>> -           }
>>>>>> +         origin = (char *) -1;
>>>>>> +         goto out;
>>>>>>         }
>>>>>> -      else
>>>>>> -       {
>>>>>> -         size_t len = realname_len;
>>>>>> -         char *result = NULL;
>>>>>> -
>>>>>> -         /* Get the current directory name.  */
>>>>>> -         origin = NULL;
>>>>>> -         do
>>>>>> -           {
>>>>>> -             char *new_origin;
>>>>>> -
>>>>>> -             len += 128;
>>>>>> -             new_origin = (char *) realloc (origin, len);
>>>>>> -             if (new_origin == NULL)
>>>>>> -               /* We exit the loop.  Note that result == NULL.  */
>>>>>> -               break;
>>>>>> -             origin = new_origin;
>>>>>> -           }
>>>>>> -         while ((result = __getcwd (origin, len - realname_len)) == NULL
>>>>>> -                && errno == ERANGE);
>>>>>> -
>>>>>> -         if (result == NULL)
>>>>>> -           {
>>>>>> -             /* We were not able to determine the current directory.
>>>>>> -                Note that free(origin) is OK if origin == NULL.  */
>>>>>> -             free (origin);
>>>>>> -             origin = (char *) -1;
>>>>>> -             goto out;
>>>>>> -           }
>>>>>> -
>>>>>> -         /* Find the end of the path and see whether we have to add a
>>>>>> -            slash.  We could use rawmemchr but this need not be
>>>>>> -            fast.  */
>>>>>> -         cp = (strchr) (origin, '\0');
>>>>>> -         if (cp[-1] != '/')
>>>>>> -           *cp++ = '/';
>>>>>> -       }
>>>>>> -
>>>>>>        /* Add the real file name.  */
>>>>>>        cp = __mempcpy (cp, realname, realname_len);
>>>>>>
>>>>>> diff --git a/elf/dl-realpath.c b/elf/dl-realpath.c
>>>>>> new file mode 100644
>>>>>> index 0000000..2549bb3
>>>>>> --- /dev/null
>>>>>> +++ b/elf/dl-realpath.c
>>>>>> @@ -0,0 +1,85 @@
>>>>>> +/* Dynamic loader version of realpath.
>>>>>> +   Copyright (C) 2014 Free Software Foundation, Inc.
>>>>>> +   This file is part of the GNU C Library.
>>>>>> +
>>>>>> +   The GNU C Library is free software; you can redistribute it and/or
>>>>>> +   modify it under the terms of the GNU Lesser General Public
>>>>>> +   License as published by the Free Software Foundation; either
>>>>>> +   version 2.1 of the License, or (at your option) any later version.
>>>>>> +
>>>>>> +   The GNU C Library is distributed in the hope that it will be useful,
>>>>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>>>>> +   Lesser General Public License for more details.
>>>>>> +
>>>>>> +   You should have received a copy of the GNU Lesser General Public
>>>>>> +   License along with the GNU C Library; if not, see
>>>>>> +   <http://www.gnu.org/licenses/>.  */
>>>>>> +
>>>>>> +#include <stdlib.h>
>>>>>> +#include <unistd.h>
>>>>>> +#include <limits.h>
>>>>>> +#include <ldsodefs.h>
>>>>>> +
>>>>>> +/* Simplified implementation of realpath(): no dynamic memory use, no lstat(),
>>>>>> +   no set_errno(), no valid "rpath" on error, etc.  This simplifies cases
>>>>>> +   involving relative paths, specifically where $ORIGIN needs to be
>>>>>> +   calculated. For this relatively rare case, one could also imagine using
>>>>>> +   link_map.l_origin to avoid the getcwd() here, but the simpler code
>>>>>> +   here seems like a better solution.  */
>>>>>> +char * internal_function
>>>>>> +__realpath (const char *name, char *rpath)
>>>>>> +{
>>>>>> +  char *dest;
>>>>>> +  const char *start, *end;
>>>>>> +
>>>>>> +  if (name[0] != '/')
>>>>>> +    {
>>>>>> +      if (!__getcwd (rpath, PATH_MAX))
>>>>>> +       return NULL;
>>>>>> +      dest = __rawmemchr (rpath, '\0');
>>>>>> +    }
>>>>>> +  else
>>>>>> +    {
>>>>>> +      rpath[0] = '/';
>>>>>> +      dest = rpath + 1;
>>>>>> +    }
>>>>>> +
>>>>>> +  for (start = end = name; *start; start = end)
>>>>>> +    {
>>>>>> +      /* Skip sequence of multiple path-separators.  */
>>>>>> +      while (*start == '/')
>>>>>> +       ++start;
>>>>>> +
>>>>>> +      /* Find end of path component.  */
>>>>>> +      for (end = start; *end && *end != '/'; ++end)
>>>>>> +       /* Nothing.  */;
>>>>>> +
>>>>>> +      if (end - start == 0)
>>>>>> +       break;
>>>>>> +      else if (end - start == 1 && start[0] == '.')
>>>>>> +       /* nothing */;
>>>>>> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>>>>> +       {
>>>>>> +         /* Back up to previous component, ignore if at root already.  */
>>>>>> +         if (dest > rpath + 1)
>>>>>> +           while ((--dest)[-1] != '/');
>>>>>> +       }
>>>>>> +      else
>>>>>> +       {
>>>>>> +         if (dest[-1] != '/')
>>>>>> +           *dest++ = '/';
>>>>>> +
>>>>>> +         if (dest + (end - start) >= rpath + PATH_MAX)
>>>>>> +           return NULL;
>>>>>> +
>>>>>> +         dest = __mempcpy (dest, start, end - start);
>>>>>> +         *dest = '\0';
>>>>>> +       }
>>>>>> +    }
>>>>>> +  if (dest > rpath + 1 && dest[-1] == '/')
>>>>>> +    --dest;
>>>>>> +  *dest = '\0';
>>>>>> +
>>>>>> +  return rpath;
>>>>>> +}
>>>>>> diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
>>>>>> index bcc00bc..45251d0 100644
>>>>>> --- a/sysdeps/tile/dl-runtime.c
>>>>>> +++ b/sysdeps/tile/dl-runtime.c
>>>>>> @@ -29,70 +29,6 @@
>>>>>>  #include <arch/sim.h>
>>>>>>  #include <dl-unmap-segments.h>
>>>>>>
>>>>>> -/* Like realpath(), but simplified: no dynamic memory use, no lstat(),
>>>>>> -   no set_errno(), no valid "rpath" on error, etc.  This handles some
>>>>>> -   simple cases where the simulator might not have a valid entry for
>>>>>> -   a loaded Elf object, in particular dlopen() with a relative path.
>>>>>> -   For this relatively rare case, one could also imagine using
>>>>>> -   link_map.l_origin to avoid the getcwd() here, but the simpler code
>>>>>> -   here seems like a better solution.  */
>>>>>> -static char *
>>>>>> -dl_realpath (const char *name, char *rpath)
>>>>>> -{
>>>>>> -  char *dest;
>>>>>> -  const char *start, *end;
>>>>>> -
>>>>>> -  if (name[0] != '/')
>>>>>> -    {
>>>>>> -      if (!__getcwd (rpath, PATH_MAX))
>>>>>> -        return NULL;
>>>>>> -      dest = __rawmemchr (rpath, '\0');
>>>>>> -    }
>>>>>> -  else
>>>>>> -    {
>>>>>> -      rpath[0] = '/';
>>>>>> -      dest = rpath + 1;
>>>>>> -    }
>>>>>> -
>>>>>> -  for (start = end = name; *start; start = end)
>>>>>> -    {
>>>>>> -      /* Skip sequence of multiple path-separators.  */
>>>>>> -      while (*start == '/')
>>>>>> -       ++start;
>>>>>> -
>>>>>> -      /* Find end of path component.  */
>>>>>> -      for (end = start; *end && *end != '/'; ++end)
>>>>>> -       /* Nothing.  */;
>>>>>> -
>>>>>> -      if (end - start == 0)
>>>>>> -       break;
>>>>>> -      else if (end - start == 1 && start[0] == '.')
>>>>>> -       /* nothing */;
>>>>>> -      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>>>>> -       {
>>>>>> -         /* Back up to previous component, ignore if at root already.  */
>>>>>> -         if (dest > rpath + 1)
>>>>>> -           while ((--dest)[-1] != '/');
>>>>>> -       }
>>>>>> -      else
>>>>>> -       {
>>>>>> -         if (dest[-1] != '/')
>>>>>> -           *dest++ = '/';
>>>>>> -
>>>>>> -         if (dest + (end - start) >= rpath + PATH_MAX)
>>>>>> -            return NULL;
>>>>>> -
>>>>>> -         dest = __mempcpy (dest, start, end - start);
>>>>>> -         *dest = '\0';
>>>>>> -       }
>>>>>> -    }
>>>>>> -  if (dest > rpath + 1 && dest[-1] == '/')
>>>>>> -    --dest;
>>>>>> -  *dest = '\0';
>>>>>> -
>>>>>> -  return rpath;
>>>>>> -}
>>>>>> -
>>>>>>  /* Support notifying the simulator about new objects.  */
>>>>>>  void internal_function
>>>>>>  _dl_after_load (struct link_map *l)
>>>>>> @@ -117,7 +53,7 @@ _dl_after_load (struct link_map *l)
>>>>>>    DLPUTC (':');
>>>>>>
>>>>>>    /* Write the library path, including the terminating '\0'.  */
>>>>>> -  path = dl_realpath (l->l_name, pathbuf) ?: l->l_name;
>>>>>> +  path = __realpath (l->l_name, pathbuf) ?: l->l_name;
>>>>>>    for (size_t i = 0;; i++)
>>>>>>      {
>>>>>>        DLPUTC (path[i]);
>>>>>> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
>>>>>> index 9ad6d22..4525429 100644
>>>>>> --- a/sysdeps/unix/sysv/linux/Makefile
>>>>>> +++ b/sysdeps/unix/sysv/linux/Makefile
>>>>>> @@ -176,7 +176,7 @@ endif
>>>>>>
>>>>>>  ifeq ($(subdir),elf)
>>>>>>  sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \
>>>>>> -                       dl-fxstatat64
>>>>>> +                       dl-fxstatat64 dl-lxstat64
>>>>>>
>>>>>>  CPPFLAGS-lddlibc4 += -DNOT_IN_libc
>>>>>>
>>>>>> diff --git a/sysdeps/unix/sysv/linux/dl-lxstat64.c b/sysdeps/unix/sysv/linux/dl-lxstat64.c
>>>>>> new file mode 100644
>>>>>> index 0000000..63e6800
>>>>>> --- /dev/null
>>>>>> +++ b/sysdeps/unix/sysv/linux/dl-lxstat64.c
>>>>>> @@ -0,0 +1 @@
>>>>>> +#include <lxstat64.c>
>>>>>> diff --git a/sysdeps/unix/sysv/linux/dl-realpath.c b/sysdeps/unix/sysv/linux/dl-realpath.c
>>>>>> new file mode 100644
>>>>>> index 0000000..91652dc
>>>>>> --- /dev/null
>>>>>> +++ b/sysdeps/unix/sysv/linux/dl-realpath.c
>>>>>> @@ -0,0 +1,121 @@
>>>>>> +/* Dynamic loader version of realpath for linux.
>>>>>> +   Copyright (C) 2014 Free Software Foundation, Inc.
>>>>>> +   This file is part of the GNU C Library.
>>>>>> +
>>>>>> +   The GNU C Library is free software; you can redistribute it and/or
>>>>>> +   modify it under the terms of the GNU Lesser General Public
>>>>>> +   License as published by the Free Software Foundation; either
>>>>>> +   version 2.1 of the License, or (at your option) any later version.
>>>>>> +
>>>>>> +   The GNU C Library is distributed in the hope that it will be useful,
>>>>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>>>>> +   Lesser General Public License for more details.
>>>>>> +
>>>>>> +   You should have received a copy of the GNU Lesser General Public
>>>>>> +   License along with the GNU C Library; if not, see
>>>>>> +   <http://www.gnu.org/licenses/>.  */
>>>>>> +
>>>>>> +#include <stdlib.h>
>>>>>> +#include <unistd.h>
>>>>>> +#include <limits.h>
>>>>>> +#include <ldsodefs.h>
>>>>>> +#include <sys/stat.h>
>>>>>> +#include <kernel-features.h>
>>>>>> +#include <sysdep.h>
>>>>>> +
>>>>>> +/* Simplified version of realpath which extends elf/dl-realpath.c to use
>>>>>> + * linux syscalls for handling symlinks */
>>>>>> +char * internal_function
>>>>>> +__realpath (const char *name, char *rpath)
>>>>>> +{
>>>>>> +  char *dest;
>>>>>> +  char extra_buf[PATH_MAX];
>>>>>> +  const char *start, *end;
>>>>>> +  int num_links = 0;
>>>>>> +
>>>>>> +  if (name[0] != '/')
>>>>>> +    {
>>>>>> +      if (!__getcwd (rpath, PATH_MAX))
>>>>>> +       return NULL;
>>>>>> +      dest = __rawmemchr (rpath, '\0');
>>>>>> +    }
>>>>>> +  else
>>>>>> +    {
>>>>>> +      rpath[0] = '/';
>>>>>> +      dest = rpath + 1;
>>>>>> +    }
>>>>>> +
>>>>>> +  for (start = end = name; *start; start = end)
>>>>>> +    {
>>>>>> +      struct stat64 st;
>>>>>> +      int n;
>>>>>> +      /* Skip sequence of multiple path-separators.  */
>>>>>> +      while (*start == '/')
>>>>>> +       ++start;
>>>>>> +
>>>>>> +      /* Find end of path component.  */
>>>>>> +      for (end = start; *end && *end != '/'; ++end)
>>>>>> +       /* Nothing.  */;
>>>>>> +
>>>>>> +      if (end - start == 0)
>>>>>> +       break;
>>>>>> +      else if (end - start == 1 && start[0] == '.')
>>>>>> +       /* nothing */;
>>>>>> +      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
>>>>>> +       {
>>>>>> +         /* Back up to previous component, ignore if at root already.  */
>>>>>> +         if (dest > rpath + 1)
>>>>>> +           while ((--dest)[-1] != '/');
>>>>>> +       }
>>>>>> +      else
>>>>>> +       {
>>>>>> +         if (dest[-1] != '/')
>>>>>> +           *dest++ = '/';
>>>>>> +
>>>>>> +         if (dest + (end - start) >= rpath + PATH_MAX)
>>>>>> +           return NULL;
>>>>>> +
>>>>>> +         dest = __mempcpy (dest, start, end - start);
>>>>>> +         *dest = '\0';
>>>>>> +         if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
>>>>>> +           return NULL;
>>>>>> +
>>>>>> +         if (S_ISLNK (st.st_mode))
>>>>>> +           {
>>>>>> +             char buf[PATH_MAX];
>>>>>> +             size_t len;
>>>>>> +
>>>>>> +             INTERNAL_SYSCALL_DECL (err);
>>>>>> +
>>>>>> +             n = INTERNAL_SYSCALL (readlink, err, 3, rpath, buf,
>>>>>> +                                   PATH_MAX - 1);
>>>>>> +
>>>>>> +             if (n <= 0 || buf[0] == '[')
>>>>>> +               return NULL;
>>>>>> +             buf[n] = '\0';
>>>>>> +
>>>>>> +             len = strlen (end);
>>>>>> +             if ((long int) (n + len) >= PATH_MAX)
>>>>>> +               return NULL;
>>>>>> +
>>>>>> +             /* Careful here, end may be a pointer into extra_buf... */
>>>>>> +             memmove (&extra_buf[n], end, len + 1);
>>>>>> +             name = end = memcpy (extra_buf, buf, n);
>>>>>> +
>>>>>> +             if (buf[0] == '/')
>>>>>> +               dest = rpath + 1;     /* Absolute symlink */
>>>>>> +             else
>>>>>> +               /* Back up to previous component, ignore if at root already: */
>>>>>> +               if (dest > rpath + 1)
>>>>>> +                 while ((--dest)[-1] != '/');
>>>>>> +           }
>>>>>> +       }
>>>>>> +    }
>>>>>> +  if (dest > rpath + 1 && dest[-1] == '/')
>>>>>> +    --dest;
>>>>>> +  *dest = '\0';
>>>>>> +
>>>>>> +  return rpath;
>>>>>> +
>>>>>> +}
>>>>>> --
>>>>>> 2.0.4
>>>>>>
>>>>>
>>>>> ping.
Florian Weimer Dec. 12, 2014, 10:44 a.m. UTC | #12
On 08/12/2014 06:16 PM, Brennan Shacklett wrote:
> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as the
> generic implementation, and extended it with lstat and readlink in the linux
> version. This means the bug is still present on other systems than linux (the
> generic implementation only returns an absolute path, it doesn't do anything
> with symlinks), but if there is a way to get the generic version to follow
> symlinks on all systems please let me know.

You use PATH_MAX in the generic code which is, I believe, not available 
on Hurd.

I'm also not sure if the current behavior is buggy.  Maybe it does not 
compose well, but in some scenarios, it is exactly what is needed. 
Applying this patch could break existing programs.
Brennan Shacklett Jan. 13, 2015, 11:58 p.m. UTC | #13
On Fri, Dec 12, 2014 at 2:44 AM, Florian Weimer <fweimer@redhat.com> wrote:
> On 08/12/2014 06:16 PM, Brennan Shacklett wrote:
>>
>> I used the implementation of dl_realpath from sysdeps/tile/dl-runtime.c as
>> the
>> generic implementation, and extended it with lstat and readlink in the
>> linux
>> version. This means the bug is still present on other systems than linux
>> (the
>> generic implementation only returns an absolute path, it doesn't do
>> anything
>> with symlinks), but if there is a way to get the generic version to follow
>> symlinks on all systems please let me know.
>
>
> You use PATH_MAX in the generic code which is, I believe, not available on
> Hurd.
>
> I'm also not sure if the current behavior is buggy.  Maybe it does not
> compose well, but in some scenarios, it is exactly what is needed. Applying
> this patch could break existing programs.
>
> --
> Florian Weimer / Red Hat Product Security

Sorry for the late reply, got caught up in the holidays and the
beginning of the quarter. I can't deny that changing the behavior
could potentially break existing applications, so would it possible to
have glibc search both locations, and document the deprecation of the
current (no patch) behavior? If that isn't possible some documentation
should probably be added regardless, because when I ran into this I
found it extremely confusing and there are no resources to dictate the
correct behavior. I think the biggest problem with the current
behavior is that symlinking the library from another directory means a
choice has to be made between a correct RPATH for the symlink or a
correct RPATH for the original library, because if $ORIGIN is used one
RPATH cannot work for both. If you can see a way forward for working
this into glibc I'll gladly fix the PATH_MAX issue.

Thanks,
Brennan
Mike Frysinger March 2, 2015, 7:21 a.m. UTC | #14
On 12 Aug 2014 09:16, Brennan Shacklett wrote:
> Bug 17251 deals with (what I believe is) the incorrect calculation of $ORIGIN
> for shared libraries. Currently $ORIGIN is calculated correctly for executables
> on linux, because readlink is called on /proc/self/exe, which means the
> resulting path is absolute and has no symlinks. 

i'm not sure this behavior is even correct (see below), but at the same time, 
i'm not sure there's a reasonable way to implement the correct behavior.

> Shared libraries with relative paths on the other hand are based 
> off of appending the name / path of the library to the current working 
> directory, which means if the library is a symlink, it is not followed, which 
> breaks RPATH $ORIGIN in the following scenario:

using the current working directory to fill in $ORIGIN surely is incorrect.  the 
ELF spec states:
	When the dynamic linker loads an object that uses $ORIGIN, it must calculate 
	the pathname of the directory containing the object.

which means i'm not sure that's justification for doing a full path resolution.  
if the library is located in ~/, then $ORIGIN inside that lib means it should 
search in ~/ (and not wherever getcwd returns).  although the spec does provide 
enough wiggle room to say either implementation is conforming ...

you do have a case that $ORIGIN should behave the same regardless of the object 
using it (the main executable or a shared lib).  i wonder if the $ORIGIN usage 
in the main ELF breaks if you utilize the ld.so directly if it's utilizing the 
/proc/self/exe symlink ...
$ /lib64/ld-linux-x86-64.so.2 /bin/sh
sh-4.3$ ls -l /proc/$$/exe
lrwxrwxrwx 1 vapier users 0 Mar  2 02:19 /proc/8304/exe -> /lib64/ld-2.21.so
-mike
diff mbox

Patch

diff --git a/elf/Makefile b/elf/Makefile
index 25012cc..c130f21 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -43,7 +43,8 @@  shared-only-routines += dl-caller
 
 # ld.so uses those routines, plus some special stuff for being the program
 # interpreter and operating independent of libc.
-rtld-routines	:= rtld $(dl-routines) dl-sysdep dl-environ dl-minimal
+rtld-routines	:= rtld $(dl-routines) dl-sysdep dl-environ dl-minimal \
+		   dl-realpath
 all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
 
 CFLAGS-dl-runtime.c = -fexceptions -fasynchronous-unwind-tables
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 016a99c..7353837 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -891,6 +891,7 @@  _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
   int errval = 0;
   struct r_debug *r = _dl_debug_initialize (0, nsid);
   bool make_consistent = false;
+  char *absolutename = NULL;
 
   /* Get file information.  */
   if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &st) < 0))
@@ -902,6 +903,23 @@  _dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
       lose (errval, fd, name, realname, l, errstring,
 	    make_consistent ? r : NULL, nsid);
     }
+  /* Find absolute pathname for object */
+  absolutename = (char *) malloc (PATH_MAX);
+  if (!absolutename)
+    {
+      errstring = N_("cannot allocate memory for absolute path");
+      goto call_lose_errno;
+    }
+
+  if (!__realpath (realname, absolutename))
+    {
+      free (absolutename);
+      errstring = N_("cannot find absolute path of shared object");
+      goto call_lose_errno;
+    }
+
+  free (realname);
+  realname = absolutename;
 
   /* Look again to see if the real name matched another already loaded.  */
   for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
diff --git a/elf/dl-object.c b/elf/dl-object.c
index afd80a6..d93935b 100644
--- a/elf/dl-object.c
+++ b/elf/dl-object.c
@@ -158,55 +158,15 @@  _dl_new_object (char *realname, const char *libname, int type,
       char *origin;
       char *cp;
 
-      if (realname[0] == '/')
+      /* It is an absolute path, calculated by realpath. Use it.  
+       * But we have to make a copy since we strip out the trailing slash. */
+      assert (realname[0] == '/');
+      cp = origin = (char *) malloc (realname_len);
+      if (origin == NULL)
 	{
-	  /* It is an absolute path.  Use it.  But we have to make a
-	     copy since we strip out the trailing slash.  */
-	  cp = origin = (char *) malloc (realname_len);
-	  if (origin == NULL)
-	    {
-	      origin = (char *) -1;
-	      goto out;
-	    }
+	  origin = (char *) -1;
+	  goto out;
 	}
-      else
-	{
-	  size_t len = realname_len;
-	  char *result = NULL;
-
-	  /* Get the current directory name.  */
-	  origin = NULL;
-	  do
-	    {
-	      char *new_origin;
-
-	      len += 128;
-	      new_origin = (char *) realloc (origin, len);
-	      if (new_origin == NULL)
-		/* We exit the loop.  Note that result == NULL.  */
-		break;
-	      origin = new_origin;
-	    }
-	  while ((result = __getcwd (origin, len - realname_len)) == NULL
-		 && errno == ERANGE);
-
-	  if (result == NULL)
-	    {
-	      /* We were not able to determine the current directory.
-		 Note that free(origin) is OK if origin == NULL.  */
-	      free (origin);
-	      origin = (char *) -1;
-	      goto out;
-	    }
-
-	  /* Find the end of the path and see whether we have to add a
-	     slash.  We could use rawmemchr but this need not be
-	     fast.  */
-	  cp = (strchr) (origin, '\0');
-	  if (cp[-1] != '/')
-	    *cp++ = '/';
-	}
-
       /* Add the real file name.  */
       cp = __mempcpy (cp, realname, realname_len);
 
diff --git a/elf/dl-realpath.c b/elf/dl-realpath.c
new file mode 100644
index 0000000..2549bb3
--- /dev/null
+++ b/elf/dl-realpath.c
@@ -0,0 +1,85 @@ 
+/* Dynamic loader version of realpath.
+   Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+#include <ldsodefs.h>
+
+/* Simplified implementation of realpath(): no dynamic memory use, no lstat(),
+   no set_errno(), no valid "rpath" on error, etc.  This simplifies cases
+   involving relative paths, specifically where $ORIGIN needs to be 
+   calculated. For this relatively rare case, one could also imagine using
+   link_map.l_origin to avoid the getcwd() here, but the simpler code
+   here seems like a better solution.  */
+char * internal_function
+__realpath (const char *name, char *rpath)
+{
+  char *dest;
+  const char *start, *end;
+
+  if (name[0] != '/')
+    {
+      if (!__getcwd (rpath, PATH_MAX))
+	return NULL;
+      dest = __rawmemchr (rpath, '\0');
+    }
+  else
+    {
+      rpath[0] = '/';
+      dest = rpath + 1;
+    }
+
+  for (start = end = name; *start; start = end)
+    {
+      /* Skip sequence of multiple path-separators.  */
+      while (*start == '/')
+	++start;
+
+      /* Find end of path component.  */
+      for (end = start; *end && *end != '/'; ++end)
+	/* Nothing.  */;
+
+      if (end - start == 0)
+	break;
+      else if (end - start == 1 && start[0] == '.')
+	/* nothing */;
+      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
+	{
+	  /* Back up to previous component, ignore if at root already.  */
+	  if (dest > rpath + 1)
+	    while ((--dest)[-1] != '/');
+	}
+      else
+	{
+	  if (dest[-1] != '/')
+	    *dest++ = '/';
+
+	  if (dest + (end - start) >= rpath + PATH_MAX)
+	    return NULL;
+
+	  dest = __mempcpy (dest, start, end - start);
+	  *dest = '\0';
+	}
+    }
+  if (dest > rpath + 1 && dest[-1] == '/')
+    --dest;
+  *dest = '\0';
+
+  return rpath;
+}
diff --git a/sysdeps/tile/dl-runtime.c b/sysdeps/tile/dl-runtime.c
index bcc00bc..45251d0 100644
--- a/sysdeps/tile/dl-runtime.c
+++ b/sysdeps/tile/dl-runtime.c
@@ -29,70 +29,6 @@ 
 #include <arch/sim.h>
 #include <dl-unmap-segments.h>
 
-/* Like realpath(), but simplified: no dynamic memory use, no lstat(),
-   no set_errno(), no valid "rpath" on error, etc.  This handles some
-   simple cases where the simulator might not have a valid entry for
-   a loaded Elf object, in particular dlopen() with a relative path.
-   For this relatively rare case, one could also imagine using
-   link_map.l_origin to avoid the getcwd() here, but the simpler code
-   here seems like a better solution.  */
-static char *
-dl_realpath (const char *name, char *rpath)
-{
-  char *dest;
-  const char *start, *end;
-
-  if (name[0] != '/')
-    {
-      if (!__getcwd (rpath, PATH_MAX))
-        return NULL;
-      dest = __rawmemchr (rpath, '\0');
-    }
-  else
-    {
-      rpath[0] = '/';
-      dest = rpath + 1;
-    }
-
-  for (start = end = name; *start; start = end)
-    {
-      /* Skip sequence of multiple path-separators.  */
-      while (*start == '/')
-	++start;
-
-      /* Find end of path component.  */
-      for (end = start; *end && *end != '/'; ++end)
-	/* Nothing.  */;
-
-      if (end - start == 0)
-	break;
-      else if (end - start == 1 && start[0] == '.')
-	/* nothing */;
-      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
-	{
-	  /* Back up to previous component, ignore if at root already.  */
-	  if (dest > rpath + 1)
-	    while ((--dest)[-1] != '/');
-	}
-      else
-	{
-	  if (dest[-1] != '/')
-	    *dest++ = '/';
-
-	  if (dest + (end - start) >= rpath + PATH_MAX)
-            return NULL;
-
-	  dest = __mempcpy (dest, start, end - start);
-	  *dest = '\0';
-	}
-    }
-  if (dest > rpath + 1 && dest[-1] == '/')
-    --dest;
-  *dest = '\0';
-
-  return rpath;
-}
-
 /* Support notifying the simulator about new objects.  */
 void internal_function
 _dl_after_load (struct link_map *l)
@@ -117,7 +53,7 @@  _dl_after_load (struct link_map *l)
   DLPUTC (':');
 
   /* Write the library path, including the terminating '\0'.  */
-  path = dl_realpath (l->l_name, pathbuf) ?: l->l_name;
+  path = __realpath (l->l_name, pathbuf) ?: l->l_name;
   for (size_t i = 0;; i++)
     {
       DLPUTC (path[i]);
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 9ad6d22..4525429 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -176,7 +176,7 @@  endif
 
 ifeq ($(subdir),elf)
 sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \
-			dl-fxstatat64
+			dl-fxstatat64 dl-lxstat64
 
 CPPFLAGS-lddlibc4 += -DNOT_IN_libc
 
diff --git a/sysdeps/unix/sysv/linux/dl-lxstat64.c b/sysdeps/unix/sysv/linux/dl-lxstat64.c
new file mode 100644
index 0000000..63e6800
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-lxstat64.c
@@ -0,0 +1 @@ 
+#include <lxstat64.c>
diff --git a/sysdeps/unix/sysv/linux/dl-realpath.c b/sysdeps/unix/sysv/linux/dl-realpath.c
new file mode 100644
index 0000000..91652dc
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-realpath.c
@@ -0,0 +1,121 @@ 
+/* Dynamic loader version of realpath for linux.
+   Copyright (C) 2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+#include <ldsodefs.h>
+#include <sys/stat.h>
+#include <kernel-features.h>
+#include <sysdep.h>
+
+/* Simplified version of realpath which extends elf/dl-realpath.c to use
+ * linux syscalls for handling symlinks */
+char * internal_function
+__realpath (const char *name, char *rpath)
+{
+  char *dest;
+  char extra_buf[PATH_MAX];
+  const char *start, *end;
+  int num_links = 0;
+
+  if (name[0] != '/')
+    {
+      if (!__getcwd (rpath, PATH_MAX))
+	return NULL;
+      dest = __rawmemchr (rpath, '\0');
+    }
+  else
+    {
+      rpath[0] = '/';
+      dest = rpath + 1;
+    }
+
+  for (start = end = name; *start; start = end)
+    {
+      struct stat64 st;
+      int n;
+      /* Skip sequence of multiple path-separators.  */
+      while (*start == '/')
+	++start;
+
+      /* Find end of path component.  */
+      for (end = start; *end && *end != '/'; ++end)
+	/* Nothing.  */;
+
+      if (end - start == 0)
+	break;
+      else if (end - start == 1 && start[0] == '.')
+	/* nothing */;
+      else if (end - start == 2 && start[0] == '.' && start[1] == '.')
+	{
+	  /* Back up to previous component, ignore if at root already.  */
+	  if (dest > rpath + 1)
+	    while ((--dest)[-1] != '/');
+	}
+      else
+	{
+	  if (dest[-1] != '/')
+	    *dest++ = '/';
+
+	  if (dest + (end - start) >= rpath + PATH_MAX)
+	    return NULL;
+
+	  dest = __mempcpy (dest, start, end - start);
+	  *dest = '\0';
+	  if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
+	    return NULL;
+
+	  if (S_ISLNK (st.st_mode))
+	    {
+	      char buf[PATH_MAX];
+	      size_t len;
+
+	      INTERNAL_SYSCALL_DECL (err);
+
+	      n = INTERNAL_SYSCALL (readlink, err, 3, rpath, buf, 
+				    PATH_MAX - 1);
+
+	      if (n <= 0 || buf[0] == '[')
+		return NULL;
+	      buf[n] = '\0';
+
+	      len = strlen (end);
+	      if ((long int) (n + len) >= PATH_MAX)
+		return NULL;
+
+	      /* Careful here, end may be a pointer into extra_buf... */
+	      memmove (&extra_buf[n], end, len + 1);
+	      name = end = memcpy (extra_buf, buf, n);
+
+	      if (buf[0] == '/')
+		dest = rpath + 1;     /* Absolute symlink */
+	      else 
+		/* Back up to previous component, ignore if at root already: */
+		if (dest > rpath + 1)
+		  while ((--dest)[-1] != '/');
+	    }
+	}
+    }
+  if (dest > rpath + 1 && dest[-1] == '/')
+    --dest;
+  *dest = '\0';
+
+  return rpath;
+
+}