diff mbox series

[v3] elf: Canonicalize $ORIGIN in an explicit ld.so invocation [BZ 25263]

Message ID 20250311201720.2794859-1-adhemerval.zanella@linaro.org
State Accepted
Commit 9b646f5dc933dfa019f2ed7f80b6198b43e31f62
Headers show
Series [v3] elf: Canonicalize $ORIGIN in an explicit ld.so invocation [BZ 25263] | expand

Commit Message

Adhemerval Zanella March 11, 2025, 8:17 p.m. UTC
When an executable is invoked directly, we calculate $ORIGIN by calling
readlink on /proc/self/exe, which the Linux kernel resolves to the
target of any symlinks.  However, if an executable is run through ld.so,
we cannot use /proc/self/exe and instead use the path given as an
argument.  This leads to a different calculation of $ORIGIN, which is
most notable in that it causes ldd to behave differently (e.g., by not
finding a library) from directly running the program.

To make the behavior consistent, take advantage of the fact that the
kernel also resolves /proc/self/fd/ symlinks to the target of any
symlinks in the same manner, so once we have opened the main executable
in order to load it, replace the user-provided path with the result of
calling readlink("/proc/self/fd/N").

(On non-Linux platforms this resolution does not happen and so no
behavior change is needed.)

The __fd_to_filename usage on loader (through dl-origin.c) pulls
_itoa.c, which in turn defines a lot of

The __fd_to_filename requires _fitoa_word and _itoa_word, which for
32-bits pulls a lot of definitions from _itoa.c (due _ITOA_NEEDED
debing defined).  To simplify the build move the required function
to a new file, _fitoa_word.c.

Checked on x86_64-linux-gnu and i686-linux-gnu.

Co-authored-by: Geoffrey Thomas <geofft@ldpreload.com>
---
 elf/Makefile                        | 23 +++++++++++
 elf/dl-load.c                       |  6 +++
 elf/dl-origin.c                     |  6 +++
 elf/liborigin-mod.c                 |  1 +
 elf/tst-origin.c                    | 26 +++++++++++++
 elf/tst-origin.sh                   | 60 +++++++++++++++++++++++++++++
 stdio-common/Makefile               |  3 ++
 stdio-common/_fitoa_word.c          | 60 +++++++++++++++++++++++++++++
 stdio-common/_itoa.c                | 43 ---------------------
 sysdeps/generic/_itoa.h             | 31 ---------------
 sysdeps/generic/ldsodefs.h          |  4 ++
 sysdeps/mach/hurd/Makefile          |  2 +
 sysdeps/unix/sysv/linux/dl-origin.c | 23 +++++++++++
 13 files changed, 214 insertions(+), 74 deletions(-)
 create mode 100644 elf/liborigin-mod.c
 create mode 100644 elf/tst-origin.c
 create mode 100755 elf/tst-origin.sh
 create mode 100644 stdio-common/_fitoa_word.c

Comments

Geoffrey Thomas March 12, 2025, 8:21 p.m. UTC | #1
On Tue, Mar 11, 2025, at 4:17 PM, Adhemerval Zanella wrote:
> When an executable is invoked directly, we calculate $ORIGIN by calling
> readlink on /proc/self/exe, which the Linux kernel resolves to the
> target of any symlinks.  However, if an executable is run through ld.so,
> we cannot use /proc/self/exe and instead use the path given as an
> argument.  This leads to a different calculation of $ORIGIN, which is
> most notable in that it causes ldd to behave differently (e.g., by not
> finding a library) from directly running the program.
>
> To make the behavior consistent, take advantage of the fact that the
> kernel also resolves /proc/self/fd/ symlinks to the target of any
> symlinks in the same manner, so once we have opened the main executable
> in order to load it, replace the user-provided path with the result of
> calling readlink("/proc/self/fd/N").
>
> (On non-Linux platforms this resolution does not happen and so no
> behavior change is needed.)
>
> The __fd_to_filename usage on loader (through dl-origin.c) pulls
> _itoa.c, which in turn defines a lot of
>
> The __fd_to_filename requires _fitoa_word and _itoa_word, which for

duplicate paragraph

> 32-bits pulls a lot of definitions from _itoa.c (due _ITOA_NEEDED
> debing defined).  To simplify the build move the required function

"due to _ITOA_NEEDED being defined"

> to a new file, _fitoa_word.c.
>
> Checked on x86_64-linux-gnu and i686-linux-gnu.

I have also just tested this version on aarch64-linux-gnu and it works.

Reviewed-by: Geoffrey Thomas <geofft@ldpreload.com>
(and Tested-by and Signed-off-by if you need them)

Thanks again for your help in getting this through!

>
> Co-authored-by: Geoffrey Thomas <geofft@ldpreload.com>
> ---
>  elf/Makefile                        | 23 +++++++++++
>  elf/dl-load.c                       |  6 +++
>  elf/dl-origin.c                     |  6 +++
>  elf/liborigin-mod.c                 |  1 +
>  elf/tst-origin.c                    | 26 +++++++++++++
>  elf/tst-origin.sh                   | 60 +++++++++++++++++++++++++++++
>  stdio-common/Makefile               |  3 ++
>  stdio-common/_fitoa_word.c          | 60 +++++++++++++++++++++++++++++
>  stdio-common/_itoa.c                | 43 ---------------------
>  sysdeps/generic/_itoa.h             | 31 ---------------
>  sysdeps/generic/ldsodefs.h          |  4 ++
>  sysdeps/mach/hurd/Makefile          |  2 +
>  sysdeps/unix/sysv/linux/dl-origin.c | 23 +++++++++++
>  13 files changed, 214 insertions(+), 74 deletions(-)
>  create mode 100644 elf/liborigin-mod.c
>  create mode 100644 elf/tst-origin.c
>  create mode 100755 elf/tst-origin.sh
>  create mode 100644 stdio-common/_fitoa_word.c
>
> diff --git a/elf/Makefile b/elf/Makefile
> index 77a76f2142..8055d9ffbb 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -456,6 +456,7 @@ tests += \
>    tst-noload \
>    tst-non-directory-path \
>    tst-null-argv \
> +  tst-origin \
>    tst-p_align1 \
>    tst-p_align2 \
>    tst-p_align3 \
> @@ -763,6 +764,7 @@ modules-names += \
>    libmarkermod5-3 \
>    libmarkermod5-4 \
>    libmarkermod5-5 \
> +  liborigin-mod \
>    libtracemod1-1 \
>    libtracemod2-1 \
>    libtracemod3-1 \
> @@ -3442,3 +3444,24 @@ $(objpfx)tst-dlopen-constructor-null: \
>    $(objpfx)tst-dlopen-constructor-null-mod2.so
>  $(objpfx)tst-dlopen-constructor-null-mod2.so: \
>    $(objpfx)tst-dlopen-constructor-null-mod1.so
> +
> +CFLAGS-tst-origin.c += $(no-stack-protector)
> +$(objpfx)tst-origin: $(objpfx)tst-origin.o $(objpfx)liborigin-mod.so
> +	$(LINK.o) -o $@ -B$(csu-objpfx) $(LDFLAGS.so) $< \
> +		-Wl,-rpath,\$$ORIGIN \
> +		-L$(subst :, -L,$(rpath-link)) -Wl,--no-as-needed -lorigin-mod
> +$(objpfx)liborigin-mod.so: $(objpfx)liborigin-mod.os
> +	$(LINK.o) -shared -o $@ -B$(csu-objpfx) $(LDFLAGS.so) \
> +		$(LDFLAGS-soname-fname) \
> +		$<
> +$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)tst-origin
> +	$(SHELL) \
> +		$< \
> +		'$(common-objpfx)' \
> +		'$(test-wrapper-env)' \
> +		'$(run-program-env)' \
> +		'$(rpath-link)' \
> +		tst-origin \
> +		liborigin-mod.so \
> +		> $@; \
> +	$(evaluate-test)
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> index 4998652adf..6b7e9799f3 100644
> --- a/elf/dl-load.c
> +++ b/elf/dl-load.c
> @@ -965,6 +965,12 @@ _dl_map_object_from_fd (const char *name, const 
> char *origname, int fd,
>      {
>        assert (nsid == LM_ID_BASE);
>        memset (&id, 0, sizeof (id));
> +      char *realname_can = _dl_canonicalize (fd);
> +      if (realname_can != NULL)
> +	{
> +	  free (realname);
> +	  realname = realname_can;
> +	}
>      }
>    else
>      {
> diff --git a/elf/dl-origin.c b/elf/dl-origin.c
> index 9f6b921b01..812f5dbb28 100644
> --- a/elf/dl-origin.c
> +++ b/elf/dl-origin.c
> @@ -47,3 +47,9 @@ _dl_get_origin (void)
> 
>    return result;
>  }
> +
> +char *
> +_dl_canonicalize (int fd)
> +{
> +  return NULL;
> +}
> diff --git a/elf/liborigin-mod.c b/elf/liborigin-mod.c
> new file mode 100644
> index 0000000000..aa6d4c27df
> --- /dev/null
> +++ b/elf/liborigin-mod.c
> @@ -0,0 +1 @@
> +void foo (void) {}
> diff --git a/elf/tst-origin.c b/elf/tst-origin.c
> new file mode 100644
> index 0000000000..734b2e81f6
> --- /dev/null
> +++ b/elf/tst-origin.c
> @@ -0,0 +1,26 @@
> +/* Test if $ORIGIN works correctly with symlinks (BZ 25263)
> +   Copyright (C) 2025 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
> +   <https://www.gnu.org/licenses/>.  */
> +
> +extern void foo (void);
> +
> +int
> +main (int argc, char *argv[])
> +{
> +  foo ();
> +  return 0;
> +}
> diff --git a/elf/tst-origin.sh b/elf/tst-origin.sh
> new file mode 100755
> index 0000000000..2555d3ed9e
> --- /dev/null
> +++ b/elf/tst-origin.sh
> @@ -0,0 +1,60 @@
> +#!/bin/sh
> +# Test if $ORIGIN works correctly with symlinks (BZ 25263)
> +# Copyright (C) 2025 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
> +# <https://www.gnu.org/licenses/>.
> +
> +set -e
> +
> +objpfx=$1
> +test_wrapper_env=$2
> +run_program_env=$3
> +library_path=$4
> +test_program=$5
> +test_library=$6
> +
> +cleanup()
> +{
> +  # Move the binary and library back to build directory
> +  mv $tmpdir/sub/$test_program ${objpfx}elf
> +  mv $tmpdir/sub/$test_library ${objpfx}elf
> +
> +  rm -rf $tmpdir
> +}
> +
> +tmpdir=$(mktemp -d "${objpfx}elf/tst-origin.XXXXXXXXXX")
> +#trap cleanup 0
> +
> +mkdir ${tmpdir}/sub
> +
> +# Remove the dependency from $library_path
> +mv ${objpfx}elf/$test_program  $tmpdir/sub
> +mv ${objpfx}elf/$test_library  $tmpdir/sub
> +
> +cd ${tmpdir}
> +ln -s sub/$test_program $test_program
> +
> +${test_wrapper_env} \
> +${run_program_env} \
> +${objpfx}elf/ld.so --library-path "$library_path" \
> +  ./$test_program 2>&1 && rc=0 || rc=$?
> +
> +# Also check if ldd resolves the dependency
> +LD_TRACE_LOADED_OBJECTS=1 \
> +${objpfx}elf/ld.so --library-path "$library_path" \
> +  ./$test_program 2>&1 | grep 'not found' && rc=1 || rc=0
> +
> +exit $rc
> diff --git a/stdio-common/Makefile b/stdio-common/Makefile
> index b68e9223c6..d3733d0c3d 100644
> --- a/stdio-common/Makefile
> +++ b/stdio-common/Makefile
> @@ -59,6 +59,7 @@ headers := \
>    # headers
> 
>  routines := \
> +  _fitoa_word \
>    _itoa \
>    _itowa \
>    asprintf \
> @@ -663,6 +664,8 @@ CFLAGS-dprintf.c += $(config-cflags-wno-ignored-attributes)
>  # off for non-shared builds.
>  CFLAGS-_itoa.o = $(no-stack-protector)
>  CFLAGS-_itoa.op = $(no-stack-protector)
> +CFLAGS-_fitoa_word.o = $(no-stack-protector)
> +CFLAGS-_fitoa_word.op = $(no-stack-protector)
> 
>  CFLAGS-scanf13.c += $(test-config-cflags-wno-fortify-source)
> 
> diff --git a/stdio-common/_fitoa_word.c b/stdio-common/_fitoa_word.c
> new file mode 100644
> index 0000000000..f0b2707173
> --- /dev/null
> +++ b/stdio-common/_fitoa_word.c
> @@ -0,0 +1,60 @@
> +/* Internal function for converting integers to ASCII.
> +   Copyright (C) 1994-2025 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
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <_itoa.h>
> +
> +char *
> +_itoa_word (_ITOA_WORD_TYPE value, char *buflim,
> +	    unsigned int base, int upper_case)
> +{
> +  const char *digits = (upper_case
> +			? _itoa_upper_digits
> +			: _itoa_lower_digits);
> +
> +  switch (base)
> +    {
> +#define SPECIAL(Base)							      \
> +    case Base:								      \
> +      do								      \
> +	*--buflim = digits[value % Base];				      \
> +      while ((value /= Base) != 0);					      \
> +      break
> +
> +      SPECIAL (10);
> +      SPECIAL (16);
> +      SPECIAL (8);
> +    default:
> +      do
> +	*--buflim = digits[value % base];
> +      while ((value /= base) != 0);
> +    }
> +  return buflim;
> +}
> +#undef SPECIAL
> +
> +char *
> +_fitoa_word (_ITOA_WORD_TYPE value, char *buf, unsigned int base,
> +	     int upper_case)
> +{
> +  char tmpbuf[sizeof (value) * 4];	      /* Worst case length: base 2. 
>  */
> +  char *cp = _itoa_word (value, tmpbuf + sizeof (value) * 4, base, 
> upper_case);
> +  while (cp < tmpbuf + sizeof (value) * 4)
> +    *buf++ = *cp++;
> +  return buf;
> +}
> +
> diff --git a/stdio-common/_itoa.c b/stdio-common/_itoa.c
> index 51c3ab9c14..08859f0dd0 100644
> --- a/stdio-common/_itoa.c
> +++ b/stdio-common/_itoa.c
> @@ -162,38 +162,6 @@ const struct base_table_t _itoa_base_table[] 
> attribute_hidden =
>  };
>  #endif
> 
> -#if IS_IN (libc)
> -char *
> -_itoa_word (_ITOA_WORD_TYPE value, char *buflim,
> -	    unsigned int base, int upper_case)
> -{
> -  const char *digits = (upper_case
> -			? _itoa_upper_digits
> -			: _itoa_lower_digits);
> -
> -  switch (base)
> -    {
> -#define SPECIAL(Base)							      \
> -    case Base:								      \
> -      do								      \
> -	*--buflim = digits[value % Base];				      \
> -      while ((value /= Base) != 0);					      \
> -      break
> -
> -      SPECIAL (10);
> -      SPECIAL (16);
> -      SPECIAL (8);
> -    default:
> -      do
> -	*--buflim = digits[value % base];
> -      while ((value /= base) != 0);
> -    }
> -  return buflim;
> -}
> -#undef SPECIAL
> -#endif /* IS_IN (libc) */
> -
> -
>  #if _ITOA_NEEDED
>  char *
>  _itoa (unsigned long long int value, char *buflim, unsigned int base,
> @@ -460,17 +428,6 @@ _itoa (unsigned long long int value, char *buflim, 
> unsigned int base,
>  }
>  #endif
> 
> -char *
> -_fitoa_word (_ITOA_WORD_TYPE value, char *buf, unsigned int base,
> -	     int upper_case)
> -{
> -  char tmpbuf[sizeof (value) * 4];	      /* Worst case length: base 2.  */
> -  char *cp = _itoa_word (value, tmpbuf + sizeof (value) * 4, base, upper_case);
> -  while (cp < tmpbuf + sizeof (value) * 4)
> -    *buf++ = *cp++;
> -  return buf;
> -}
> -
>  #if _ITOA_NEEDED
>  char *
>  _fitoa (unsigned long long value, char *buf, unsigned int base, int upper_case)
> diff --git a/sysdeps/generic/_itoa.h b/sysdeps/generic/_itoa.h
> index d7e3007389..2f170d3bf2 100644
> --- a/sysdeps/generic/_itoa.h
> +++ b/sysdeps/generic/_itoa.h
> @@ -51,40 +51,9 @@ hidden_proto (_itoa_upper_digits)
>  hidden_proto (_itoa_lower_digits)
>  #endif
> 
> -#if IS_IN (libc)
>  extern char *_itoa_word (_ITOA_WORD_TYPE value, char *buflim,
>  			 unsigned int base,
>  			 int upper_case) attribute_hidden;
> -#else
> -static inline char * __attribute__ ((unused, always_inline))
> -_itoa_word (_ITOA_WORD_TYPE value, char *buflim,
> -	    unsigned int base, int upper_case)
> -{
> -  const char *digits = (upper_case
> -			? _itoa_upper_digits
> -			: _itoa_lower_digits);
> -
> -  switch (base)
> -    {
> -# define SPECIAL(Base)							      \
> -    case Base:								      \
> -      do								      \
> -	*--buflim = digits[value % Base];				      \
> -      while ((value /= Base) != 0);					      \
> -      break
> -
> -      SPECIAL (10);
> -      SPECIAL (16);
> -      SPECIAL (8);
> -    default:
> -      do
> -	*--buflim = digits[value % base];
> -      while ((value /= base) != 0);
> -    }
> -  return buflim;
> -}
> -# undef SPECIAL
> -#endif
> 
>  /* Similar to the _itoa functions, but output starts at buf and pointer
>     after the last written character is returned.  */
> diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> index 8465cbaa9b..19494b82ee 100644
> --- a/sysdeps/generic/ldsodefs.h
> +++ b/sysdeps/generic/ldsodefs.h
> @@ -1223,6 +1223,10 @@ extern struct link_map * _dl_get_dl_main_map 
> (void) attribute_hidden;
>  /* Find origin of the executable.  */
>  extern const char *_dl_get_origin (void) attribute_hidden;
> 
> +/* Return the canonalized path name from the opened file descriptor FD,
> +   or NULL otherwise.  */
> +extern char * _dl_canonicalize (int fd) attribute_hidden;
> +
>  /* Count DSTs.  */
>  extern size_t _dl_dst_count (const char *name) attribute_hidden;
> 
> diff --git a/sysdeps/mach/hurd/Makefile b/sysdeps/mach/hurd/Makefile
> index 13e5cea4c2..4b69b40065 100644
> --- a/sysdeps/mach/hurd/Makefile
> +++ b/sysdeps/mach/hurd/Makefile
> @@ -300,6 +300,8 @@ ifeq ($(subdir),elf)
>  check-execstack-xfail += ld.so libc.so libpthread.so
>  # We always create a thread for signals
>  test-xfail-tst-single_threaded-pthread-static = yes
> +# Bug 25263
> +test-xfail-tst-origin = yes
> 
>  CFLAGS-tst-execstack.c += -DDEFAULT_RWX_STACK=1
>  endif
> diff --git a/sysdeps/unix/sysv/linux/dl-origin.c 
> b/sysdeps/unix/sysv/linux/dl-origin.c
> index decdd8ae9e..3c52ba51a6 100644
> --- a/sysdeps/unix/sysv/linux/dl-origin.c
> +++ b/sysdeps/unix/sysv/linux/dl-origin.c
> @@ -21,6 +21,7 @@
>  #include <fcntl.h>
>  #include <ldsodefs.h>
>  #include <sysdep.h>
> +#include <fd_to_filename.h>
> 
>  /* On Linux >= 2.1 systems which have the dcache implementation we can get
>     the path of the application from the /proc/self/exe symlink.  Try this
> @@ -72,3 +73,25 @@ _dl_get_origin (void)
> 
>    return result;
>  }
> +
> +/* On Linux, readlink on the magic symlinks in /proc/self/fd also has
> +   the same behavior of returning the canonical path from the dcache.
> +   If it does not work, we do not bother to canonicalize. */
> +
> +char *
> +_dl_canonicalize (int fd)
> +{
> +  struct fd_to_filename fdfilename;
> +  char canonical[PATH_MAX];
> +  char *path = __fd_to_filename (fd, &fdfilename);
> +  int size = INTERNAL_SYSCALL_CALL (readlinkat, AT_FDCWD, path,
> +                                    canonical, PATH_MAX - 1);
> +
> +  /* Check if the path was truncated.  */
> +  if (size >= 0 && size < PATH_MAX - 1)
> +    {
> +      canonical[size] = '\0';
> +      return __strdup (canonical);
> +    }
> +  return NULL;
> +}
> -- 
> 2.43.0
Florian Weimer March 18, 2025, 11:34 a.m. UTC | #2
* Adhemerval Zanella:

> When an executable is invoked directly, we calculate $ORIGIN by calling
> readlink on /proc/self/exe, which the Linux kernel resolves to the
> target of any symlinks.  However, if an executable is run through ld.so,
> we cannot use /proc/self/exe and instead use the path given as an
> argument.  This leads to a different calculation of $ORIGIN, which is
> most notable in that it causes ldd to behave differently (e.g., by not
> finding a library) from directly running the program.
>
> To make the behavior consistent, take advantage of the fact that the
> kernel also resolves /proc/self/fd/ symlinks to the target of any
> symlinks in the same manner, so once we have opened the main executable
> in order to load it, replace the user-provided path with the result of
> calling readlink("/proc/self/fd/N").
>
> (On non-Linux platforms this resolution does not happen and so no
> behavior change is needed.)
>
> The __fd_to_filename usage on loader (through dl-origin.c) pulls
> _itoa.c, which in turn defines a lot of
>
> The __fd_to_filename requires _fitoa_word and _itoa_word, which for
> 32-bits pulls a lot of definitions from _itoa.c (due _ITOA_NEEDED
> debing defined).  To simplify the build move the required function
> to a new file, _fitoa_word.c.
>
> Checked on x86_64-linux-gnu and i686-linux-gnu.
>
> Co-authored-by: Geoffrey Thomas <geofft@ldpreload.com>

This

or

commit 997f49fbadf892136c77115edd537c832fb8074d
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date:   Mon Mar 17 19:08:22 2025 +0000

    elf: Fix tst-origin make rules
    
    Add tests-special before include Rules and compile liborigin.os with
    MODULE_NAME set to testsuite instead of libc.

caused:

/usr/bin/ld: /home/fweimer/src/gnu/glibc/build/libc.so: undefined reference to `__tunable_is_initialized@GLIBC_PRIVATE'

Failing the “make check” build.  I think this happens because the linker
invocation links against system ld.so.

Is there a reason why we can't use the usual test DSO linker invocation?

Thanks,
Florian
Adhemerval Zanella March 18, 2025, 12:27 p.m. UTC | #3
On 18/03/25 08:34, Florian Weimer wrote:
> * Adhemerval Zanella:
> 
>> When an executable is invoked directly, we calculate $ORIGIN by calling
>> readlink on /proc/self/exe, which the Linux kernel resolves to the
>> target of any symlinks.  However, if an executable is run through ld.so,
>> we cannot use /proc/self/exe and instead use the path given as an
>> argument.  This leads to a different calculation of $ORIGIN, which is
>> most notable in that it causes ldd to behave differently (e.g., by not
>> finding a library) from directly running the program.
>>
>> To make the behavior consistent, take advantage of the fact that the
>> kernel also resolves /proc/self/fd/ symlinks to the target of any
>> symlinks in the same manner, so once we have opened the main executable
>> in order to load it, replace the user-provided path with the result of
>> calling readlink("/proc/self/fd/N").
>>
>> (On non-Linux platforms this resolution does not happen and so no
>> behavior change is needed.)
>>
>> The __fd_to_filename usage on loader (through dl-origin.c) pulls
>> _itoa.c, which in turn defines a lot of
>>
>> The __fd_to_filename requires _fitoa_word and _itoa_word, which for
>> 32-bits pulls a lot of definitions from _itoa.c (due _ITOA_NEEDED
>> debing defined).  To simplify the build move the required function
>> to a new file, _fitoa_word.c.
>>
>> Checked on x86_64-linux-gnu and i686-linux-gnu.
>>
>> Co-authored-by: Geoffrey Thomas <geofft@ldpreload.com>
> 
> This
> 
> or
> 
> commit 997f49fbadf892136c77115edd537c832fb8074d
> Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> Date:   Mon Mar 17 19:08:22 2025 +0000
> 
>     elf: Fix tst-origin make rules
>     
>     Add tests-special before include Rules and compile liborigin.os with
>     MODULE_NAME set to testsuite instead of libc.
> 
> caused:
> 
> /usr/bin/ld: /home/fweimer/src/gnu/glibc/build/libc.so: undefined reference to `__tunable_is_initialized@GLIBC_PRIVATE'
> 
> Failing the “make check” build.  I think this happens because the linker
> invocation links against system ld.so.
> 
> Is there a reason why we can't use the usual test DSO linker invocation?

The only requirement is to avoid a the liborigin.so without a full path,
which is the default for --enable-hardcoded-path-in-tests. I also tried
to mimic the original provided testcase and avoid the extra RPATH entries,
it is not really required.

I only tested on system with recent glibc, so I think that's why I have
not see the this build issue. Does the following fix the build issue?

diff --git a/elf/Makefile b/elf/Makefile
index 5a50c7d50c..3d60000ec9 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -456,6 +456,7 @@ tests += \
   tst-noload \
   tst-non-directory-path \
   tst-null-argv \
+  tst-origin \
   tst-p_align1 \
   tst-p_align2 \
   tst-p_align3 \
@@ -1195,7 +1196,6 @@ extra-test-objs += $(addsuffix .os,$(strip $(modules-names)))
 # filtmod1.so, tst-big-note-lib.so, tst-ro-dynamic-mod.so have special
 # rules.
 modules-names-nobuild += \
-  liborigin-mod \
   filtmod1 \
   tst-audit24bmod1 \
   tst-audit24bmod2 \
@@ -3451,15 +3451,8 @@ $(objpfx)tst-dlopen-constructor-null-mod2.so: \

 CFLAGS-tst-origin.c += $(no-stack-protector)
 CFLAGS-liborigin-mod.c += $(no-stack-protector)
-$(objpfx)tst-origin: $(objpfx)tst-origin.o $(objpfx)liborigin-mod.so
-       $(LINK.o) -o $@ -B$(csu-objpfx) $(LDFLAGS.so) $< \
-               -Wl,-rpath,\$$ORIGIN \
-               -L$(subst :, -L,$(rpath-link)) -Wl,--no-as-needed -lorigin-mod
-$(objpfx)liborigin-mod.so: $(objpfx)liborigin-mod.os
-       $(LINK.o) -shared -o $@ -B$(csu-objpfx) $(LDFLAGS.so) \
-               $(LDFLAGS-soname-fname) \
-               $<
-$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)tst-origin
+LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
+$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)liborigin-mod.so $(objpfx)tst-origin
        $(SHELL) \
                $< \
                '$(common-objpfx)' \
Florian Weimer March 18, 2025, 6:34 p.m. UTC | #4
* Adhemerval Zanella Netto:

> The only requirement is to avoid a the liborigin.so without a full path,
> which is the default for --enable-hardcoded-path-in-tests. I also tried
> to mimic the original provided testcase and avoid the extra RPATH entries,
> it is not really required.
>
> I only tested on system with recent glibc, so I think that's why I have
> not see the this build issue. Does the following fix the build issue?
>
> diff --git a/elf/Makefile b/elf/Makefile
> index 5a50c7d50c..3d60000ec9 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -456,6 +456,7 @@ tests += \
>    tst-noload \
>    tst-non-directory-path \
>    tst-null-argv \
> +  tst-origin \
>    tst-p_align1 \
>    tst-p_align2 \
>    tst-p_align3 \
> @@ -1195,7 +1196,6 @@ extra-test-objs += $(addsuffix .os,$(strip $(modules-names)))
>  # filtmod1.so, tst-big-note-lib.so, tst-ro-dynamic-mod.so have special
>  # rules.
>  modules-names-nobuild += \
> -  liborigin-mod \
>    filtmod1 \
>    tst-audit24bmod1 \
>    tst-audit24bmod2 \
> @@ -3451,15 +3451,8 @@ $(objpfx)tst-dlopen-constructor-null-mod2.so: \
>
>  CFLAGS-tst-origin.c += $(no-stack-protector)
>  CFLAGS-liborigin-mod.c += $(no-stack-protector)
> -$(objpfx)tst-origin: $(objpfx)tst-origin.o $(objpfx)liborigin-mod.so
> -       $(LINK.o) -o $@ -B$(csu-objpfx) $(LDFLAGS.so) $< \
> -               -Wl,-rpath,\$$ORIGIN \
> -               -L$(subst :, -L,$(rpath-link)) -Wl,--no-as-needed -lorigin-mod
> -$(objpfx)liborigin-mod.so: $(objpfx)liborigin-mod.os
> -       $(LINK.o) -shared -o $@ -B$(csu-objpfx) $(LDFLAGS.so) \
> -               $(LDFLAGS-soname-fname) \
> -               $<
> -$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)tst-origin
> +LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
> +$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)liborigin-mod.so $(objpfx)tst-origin
>         $(SHELL) \
>                 $< \
>                 '$(common-objpfx)' \

The patch did not apply to me.  This is how I fixed it up:

diff --git a/elf/Makefile b/elf/Makefile
index 5a50c7d50c..3d60000ec9 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -456,6 +456,7 @@ tests += \
   tst-noload \
   tst-non-directory-path \
   tst-null-argv \
+  tst-origin \
   tst-p_align1 \
   tst-p_align2 \
   tst-p_align3 \
@@ -1195,7 +1196,6 @@ extra-test-objs += $(addsuffix .os,$(strip $(modules-names)))
 # filtmod1.so, tst-big-note-lib.so, tst-ro-dynamic-mod.so have special
 # rules.
 modules-names-nobuild += \
-  liborigin-mod \
   filtmod1 \
   tst-audit24bmod1 \
   tst-audit24bmod2 \
@@ -3451,15 +3451,8 @@ $(objpfx)tst-dlopen-constructor-null-mod2.so: \
 
 CFLAGS-tst-origin.c += $(no-stack-protector)
 CFLAGS-liborigin-mod.c += $(no-stack-protector)
-$(objpfx)tst-origin: $(objpfx)tst-origin.o $(objpfx)liborigin-mod.so
-	$(LINK.o) -o $@ -B$(csu-objpfx) $(LDFLAGS.so) $< \
-		-Wl,-rpath,\$$ORIGIN \
-		-L$(subst :, -L,$(rpath-link)) -Wl,--no-as-needed -lorigin-mod
-$(objpfx)liborigin-mod.so: $(objpfx)liborigin-mod.os
-	$(LINK.o) -shared -o $@ -B$(csu-objpfx) $(LDFLAGS.so) \
-		$(LDFLAGS-soname-fname) \
-		$<
-$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)tst-origin
+LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
+$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)liborigin-mod.so $(objpfx)tst-origin
 	$(SHELL) \
 		$< \
 		'$(common-objpfx)' \

This variant worked for me.

Thanks,
Florian
Adhemerval Zanella March 18, 2025, 6:37 p.m. UTC | #5
On 18/03/25 15:34, Florian Weimer wrote:
> * Adhemerval Zanella Netto:
> 
>> The only requirement is to avoid a the liborigin.so without a full path,
>> which is the default for --enable-hardcoded-path-in-tests. I also tried
>> to mimic the original provided testcase and avoid the extra RPATH entries,
>> it is not really required.
>>
>> I only tested on system with recent glibc, so I think that's why I have
>> not see the this build issue. Does the following fix the build issue?
>>
>> diff --git a/elf/Makefile b/elf/Makefile
>> index 5a50c7d50c..3d60000ec9 100644
>> --- a/elf/Makefile
>> +++ b/elf/Makefile
>> @@ -456,6 +456,7 @@ tests += \
>>    tst-noload \
>>    tst-non-directory-path \
>>    tst-null-argv \
>> +  tst-origin \
>>    tst-p_align1 \
>>    tst-p_align2 \
>>    tst-p_align3 \
>> @@ -1195,7 +1196,6 @@ extra-test-objs += $(addsuffix .os,$(strip $(modules-names)))
>>  # filtmod1.so, tst-big-note-lib.so, tst-ro-dynamic-mod.so have special
>>  # rules.
>>  modules-names-nobuild += \
>> -  liborigin-mod \
>>    filtmod1 \
>>    tst-audit24bmod1 \
>>    tst-audit24bmod2 \
>> @@ -3451,15 +3451,8 @@ $(objpfx)tst-dlopen-constructor-null-mod2.so: \
>>
>>  CFLAGS-tst-origin.c += $(no-stack-protector)
>>  CFLAGS-liborigin-mod.c += $(no-stack-protector)
>> -$(objpfx)tst-origin: $(objpfx)tst-origin.o $(objpfx)liborigin-mod.so
>> -       $(LINK.o) -o $@ -B$(csu-objpfx) $(LDFLAGS.so) $< \
>> -               -Wl,-rpath,\$$ORIGIN \
>> -               -L$(subst :, -L,$(rpath-link)) -Wl,--no-as-needed -lorigin-mod
>> -$(objpfx)liborigin-mod.so: $(objpfx)liborigin-mod.os
>> -       $(LINK.o) -shared -o $@ -B$(csu-objpfx) $(LDFLAGS.so) \
>> -               $(LDFLAGS-soname-fname) \
>> -               $<
>> -$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)tst-origin
>> +LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
>> +$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)liborigin-mod.so $(objpfx)tst-origin
>>         $(SHELL) \
>>                 $< \
>>                 '$(common-objpfx)' \
> 
> The patch did not apply to me.  This is how I fixed it up:
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index 5a50c7d50c..3d60000ec9 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -456,6 +456,7 @@ tests += \
>    tst-noload \
>    tst-non-directory-path \
>    tst-null-argv \
> +  tst-origin \
>    tst-p_align1 \
>    tst-p_align2 \
>    tst-p_align3 \
> @@ -1195,7 +1196,6 @@ extra-test-objs += $(addsuffix .os,$(strip $(modules-names)))
>  # filtmod1.so, tst-big-note-lib.so, tst-ro-dynamic-mod.so have special
>  # rules.
>  modules-names-nobuild += \
> -  liborigin-mod \
>    filtmod1 \
>    tst-audit24bmod1 \
>    tst-audit24bmod2 \
> @@ -3451,15 +3451,8 @@ $(objpfx)tst-dlopen-constructor-null-mod2.so: \
>  
>  CFLAGS-tst-origin.c += $(no-stack-protector)
>  CFLAGS-liborigin-mod.c += $(no-stack-protector)
> -$(objpfx)tst-origin: $(objpfx)tst-origin.o $(objpfx)liborigin-mod.so
> -	$(LINK.o) -o $@ -B$(csu-objpfx) $(LDFLAGS.so) $< \
> -		-Wl,-rpath,\$$ORIGIN \
> -		-L$(subst :, -L,$(rpath-link)) -Wl,--no-as-needed -lorigin-mod
> -$(objpfx)liborigin-mod.so: $(objpfx)liborigin-mod.os
> -	$(LINK.o) -shared -o $@ -B$(csu-objpfx) $(LDFLAGS.so) \
> -		$(LDFLAGS-soname-fname) \
> -		$<
> -$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)tst-origin
> +LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
> +$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)liborigin-mod.so $(objpfx)tst-origin
>  	$(SHELL) \
>  		$< \
>  		'$(common-objpfx)' \
> 
> This variant worked for me.

Thanks, I will fix this upstream.
Joseph Myers March 19, 2025, 12:42 p.m. UTC | #6
This still isn't working for me; the build fails with "cannot find 
-lorigin-mod".  (Before 5291d9f1e274dd869bc0b3d044fd4cbae486893d it was 
also broken with a lint-makefiles failure.)
Adhemerval Zanella March 19, 2025, 2:02 p.m. UTC | #7
On 19/03/25 09:42, Joseph Myers wrote:
> This still isn't working for me; the build fails with "cannot find 
> -lorigin-mod".  (Before 5291d9f1e274dd869bc0b3d044fd4cbae486893d it was 
> also broken with a lint-makefiles failure.)
> 

I forgot that rules dependencies are evaluated in no defined order. I am
trying to come up a fix, the main issue is making the build with
--enable-hardcoded-path-in-tests.
Adhemerval Zanella March 19, 2025, 2:47 p.m. UTC | #8
On 19/03/25 11:02, Adhemerval Zanella Netto wrote:
> 
> 
> On 19/03/25 09:42, Joseph Myers wrote:
>> This still isn't working for me; the build fails with "cannot find 
>> -lorigin-mod".  (Before 5291d9f1e274dd869bc0b3d044fd4cbae486893d it was 
>> also broken with a lint-makefiles failure.)
>>
> 
> I forgot that rules dependencies are evaluated in no defined order. I am
> trying to come up a fix, the main issue is making the build with
> --enable-hardcoded-path-in-tests.

I had to reinstate a custom rule to avoid the DT_NEEDED with a full path
and also link tst-origin with built libc:

diff --git a/elf/Makefile b/elf/Makefile
index 3d60000ec9..fd73b68308 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -456,7 +456,6 @@ tests += \
   tst-noload \
   tst-non-directory-path \
   tst-null-argv \
-  tst-origin \
   tst-p_align1 \
   tst-p_align2 \
   tst-p_align3 \
@@ -3451,8 +3450,15 @@ $(objpfx)tst-dlopen-constructor-null-mod2.so: \

 CFLAGS-tst-origin.c += $(no-stack-protector)
 CFLAGS-liborigin-mod.c += $(no-stack-protector)
-LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
-$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)liborigin-mod.so $(objpfx)tst-origin
+# The test requires liborigin-mod.so as dependency without a path, which is
+# added by default.
+$(objpfx)tst-origin: $(objpfx)tst-origin.o $(objpfx)liborigin-mod.so
+       $(LINK.o) $(sysdep-LDFLAGS) $(rtld-LDFLAGS) -B$(csu-objpfx) \
+               $(link-test-modules-rpath-link) \
+               $(objpfx)tst-origin.o \
+               -o $@ \
+               -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
+$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)tst-origin
        $(SHELL) \
                $< \
                '$(common-objpfx)' \
Florian Weimer March 19, 2025, 5:28 p.m. UTC | #9
* Adhemerval Zanella Netto:

> On 19/03/25 11:02, Adhemerval Zanella Netto wrote:
>> 
>> 
>> On 19/03/25 09:42, Joseph Myers wrote:
>>> This still isn't working for me; the build fails with "cannot find 
>>> -lorigin-mod".  (Before 5291d9f1e274dd869bc0b3d044fd4cbae486893d it was 
>>> also broken with a lint-makefiles failure.)
>>>
>> 
>> I forgot that rules dependencies are evaluated in no defined order. I am
>> trying to come up a fix, the main issue is making the build with
>> --enable-hardcoded-path-in-tests.
>
> I had to reinstate a custom rule to avoid the DT_NEEDED with a full path
> and also link tst-origin with built libc:

This looks like a job for .EXTRA_PREREQS, I think:

  <https://www.gnu.org/software/make/manual/html_node/Special-Variables.html#index-_002eEXTRA_005fPREREQS-_0028prerequisites-not-added-to-automatic-variables_0029>

Unfortunately that was only added in make 4.3, which is quite recent.

I think we could add this:

 # sofini.os must be placed last since it terminates .eh_frame section.
 build-module-helper-objlist = \
 	$(patsubst %_pic.a,$(whole-archive) %_pic.a $(no-whole-archive),\
 		   $(filter-out %.lds $(map-file) $(+preinit) $(+postinit) \
+ 				$(build-module-nolink) \
 				$(elf-objpfx)sofini.os \
 				$(link-libc-deps),$^))


and then use:

LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
$(objpfx)tst-origin: $(objpfx)liborigin-mod.so
$(objpfx)tst-origin: build-module-nolink = $(objpfx)liborigin-mod.so

But I haven't tested it.

Thanks,
Florian
Adhemerval Zanella March 19, 2025, 6:32 p.m. UTC | #10
On 19/03/25 14:28, Florian Weimer wrote:
> * Adhemerval Zanella Netto:
> 
>> On 19/03/25 11:02, Adhemerval Zanella Netto wrote:
>>>
>>>
>>> On 19/03/25 09:42, Joseph Myers wrote:
>>>> This still isn't working for me; the build fails with "cannot find 
>>>> -lorigin-mod".  (Before 5291d9f1e274dd869bc0b3d044fd4cbae486893d it was 
>>>> also broken with a lint-makefiles failure.)
>>>>
>>>
>>> I forgot that rules dependencies are evaluated in no defined order. I am
>>> trying to come up a fix, the main issue is making the build with
>>> --enable-hardcoded-path-in-tests.
>>
>> I had to reinstate a custom rule to avoid the DT_NEEDED with a full path
>> and also link tst-origin with built libc:
> 
> This looks like a job for .EXTRA_PREREQS, I think:
> 
>   <https://www.gnu.org/software/make/manual/html_node/Special-Variables.html#index-_002eEXTRA_005fPREREQS-_0028prerequisites-not-added-to-automatic-variables_0029>
> 
> Unfortunately that was only added in make 4.3, which is quite recent.
> 
> I think we could add this:
> 
>  # sofini.os must be placed last since it terminates .eh_frame section.
>  build-module-helper-objlist = \
>  	$(patsubst %_pic.a,$(whole-archive) %_pic.a $(no-whole-archive),\
>  		   $(filter-out %.lds $(map-file) $(+preinit) $(+postinit) \
> + 				$(build-module-nolink) \
>  				$(elf-objpfx)sofini.os \
>  				$(link-libc-deps),$^))
> 
> 
> and then use:
> 
> LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
> $(objpfx)tst-origin: $(objpfx)liborigin-mod.so
> $(objpfx)tst-origin: build-module-nolink = $(objpfx)liborigin-mod.so
> 
> But I haven't tested it.

It did not work, the $(objpfx)liborigin-mod.so still adds the full path DT_NEEDED.
It works if I just use the build-module-nolink trick, but it does back to prerequisite
order issue (even if I add $(objpfx)liborigin-mod.so in the same rule).
Adhemerval Zanella March 20, 2025, 2:11 p.m. UTC | #11
On 19/03/25 11:47, Adhemerval Zanella Netto wrote:
> 
> 
> On 19/03/25 11:02, Adhemerval Zanella Netto wrote:
>>
>>
>> On 19/03/25 09:42, Joseph Myers wrote:
>>> This still isn't working for me; the build fails with "cannot find 
>>> -lorigin-mod".  (Before 5291d9f1e274dd869bc0b3d044fd4cbae486893d it was 
>>> also broken with a lint-makefiles failure.)
>>>
>>
>> I forgot that rules dependencies are evaluated in no defined order. I am
>> trying to come up a fix, the main issue is making the build with
>> --enable-hardcoded-path-in-tests.
> 
> I had to reinstate a custom rule to avoid the DT_NEEDED with a full path
> and also link tst-origin with built libc:
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index 3d60000ec9..fd73b68308 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -456,7 +456,6 @@ tests += \
>    tst-noload \
>    tst-non-directory-path \
>    tst-null-argv \
> -  tst-origin \
>    tst-p_align1 \
>    tst-p_align2 \
>    tst-p_align3 \
> @@ -3451,8 +3450,15 @@ $(objpfx)tst-dlopen-constructor-null-mod2.so: \
> 
>  CFLAGS-tst-origin.c += $(no-stack-protector)
>  CFLAGS-liborigin-mod.c += $(no-stack-protector)
> -LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
> -$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)liborigin-mod.so $(objpfx)tst-origin
> +# The test requires liborigin-mod.so as dependency without a path, which is
> +# added by default.
> +$(objpfx)tst-origin: $(objpfx)tst-origin.o $(objpfx)liborigin-mod.so
> +       $(LINK.o) $(sysdep-LDFLAGS) $(rtld-LDFLAGS) -B$(csu-objpfx) \
> +               $(link-test-modules-rpath-link) \
> +               $(objpfx)tst-origin.o \
> +               -o $@ \
> +               -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
> +$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)tst-origin
>         $(SHELL) \
>                 $< \
>                 '$(common-objpfx)' \

Is this ok to fix this issue Florian? I have tested with and without
--enable-hardcoded-path-in-tests and it seems to work as expected
by fixing the prerequisite rule and creating a dependency without
the full path.
Adhemerval Zanella March 21, 2025, 1:42 p.m. UTC | #12
On 20/03/25 11:11, Adhemerval Zanella Netto wrote:
> 
> 
> On 19/03/25 11:47, Adhemerval Zanella Netto wrote:
>>
>>
>> On 19/03/25 11:02, Adhemerval Zanella Netto wrote:
>>>
>>>
>>> On 19/03/25 09:42, Joseph Myers wrote:
>>>> This still isn't working for me; the build fails with "cannot find 
>>>> -lorigin-mod".  (Before 5291d9f1e274dd869bc0b3d044fd4cbae486893d it was 
>>>> also broken with a lint-makefiles failure.)
>>>>
>>>
>>> I forgot that rules dependencies are evaluated in no defined order. I am
>>> trying to come up a fix, the main issue is making the build with
>>> --enable-hardcoded-path-in-tests.
>>
>> I had to reinstate a custom rule to avoid the DT_NEEDED with a full path
>> and also link tst-origin with built libc:
>>
>> diff --git a/elf/Makefile b/elf/Makefile
>> index 3d60000ec9..fd73b68308 100644
>> --- a/elf/Makefile
>> +++ b/elf/Makefile
>> @@ -456,7 +456,6 @@ tests += \
>>    tst-noload \
>>    tst-non-directory-path \
>>    tst-null-argv \
>> -  tst-origin \
>>    tst-p_align1 \
>>    tst-p_align2 \
>>    tst-p_align3 \
>> @@ -3451,8 +3450,15 @@ $(objpfx)tst-dlopen-constructor-null-mod2.so: \
>>
>>  CFLAGS-tst-origin.c += $(no-stack-protector)
>>  CFLAGS-liborigin-mod.c += $(no-stack-protector)
>> -LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
>> -$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)liborigin-mod.so $(objpfx)tst-origin
>> +# The test requires liborigin-mod.so as dependency without a path, which is
>> +# added by default.
>> +$(objpfx)tst-origin: $(objpfx)tst-origin.o $(objpfx)liborigin-mod.so
>> +       $(LINK.o) $(sysdep-LDFLAGS) $(rtld-LDFLAGS) -B$(csu-objpfx) \

It is missing a -nostdlib -nostartfiles here to avoid linking with the
toochain sysroot one.

>> +               $(link-test-modules-rpath-link) \
>> +               $(objpfx)tst-origin.o \
>> +               -o $@ \
>> +               -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
>> +$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)tst-origin
>>         $(SHELL) \
>>                 $< \
>>                 '$(common-objpfx)' \
> 
> Is this ok to fix this issue Florian? I have tested with and without
> --enable-hardcoded-path-in-tests and it seems to work as expected
> by fixing the prerequisite rule and creating a dependency without
> the full path.

I have tested it with different gcc (9, 10, 11, 12, 13) built with 
different glibcs (2.32, 2.33, 2.34, 2.35, 2.38) and I saw no issue.

I will install this patch to avoid the breakage of tst-origin.
Florian Weimer March 21, 2025, 3:41 p.m. UTC | #13
* Adhemerval Zanella Netto:

>> Is this ok to fix this issue Florian? I have tested with and without
>> --enable-hardcoded-path-in-tests and it seems to work as expected
>> by fixing the prerequisite rule and creating a dependency without
>> the full path.
>
> I have tested it with different gcc (9, 10, 11, 12, 13) built with 
> different glibcs (2.32, 2.33, 2.34, 2.35, 2.38) and I saw no issue.
>
> I will install this patch to avoid the breakage of tst-origin. 

Yes, please go ahead.  We'll see how it works in my environment.

Thanks,
Florian
Florian Weimer March 21, 2025, 5:45 p.m. UTC | #14
* Florian Weimer:

> * Adhemerval Zanella Netto:
>
>> On 19/03/25 11:02, Adhemerval Zanella Netto wrote:
>>> 
>>> 
>>> On 19/03/25 09:42, Joseph Myers wrote:
>>>> This still isn't working for me; the build fails with "cannot find 
>>>> -lorigin-mod".  (Before 5291d9f1e274dd869bc0b3d044fd4cbae486893d it was 
>>>> also broken with a lint-makefiles failure.)
>>>>
>>> 
>>> I forgot that rules dependencies are evaluated in no defined order. I am
>>> trying to come up a fix, the main issue is making the build with
>>> --enable-hardcoded-path-in-tests.
>>
>> I had to reinstate a custom rule to avoid the DT_NEEDED with a full path
>> and also link tst-origin with built libc:
>
> This looks like a job for .EXTRA_PREREQS, I think:
>
>   <https://www.gnu.org/software/make/manual/html_node/Special-Variables.html#index-_002eEXTRA_005fPREREQS-_0028prerequisites-not-added-to-automatic-variables_0029>
>
> Unfortunately that was only added in make 4.3, which is quite recent.
>
> I think we could add this:
>
>  # sofini.os must be placed last since it terminates .eh_frame section.
>  build-module-helper-objlist = \
>  	$(patsubst %_pic.a,$(whole-archive) %_pic.a $(no-whole-archive),\
>  		   $(filter-out %.lds $(map-file) $(+preinit) $(+postinit) \
> + 				$(build-module-nolink) \
>  				$(elf-objpfx)sofini.os \
>  				$(link-libc-deps),$^))
>
>
> and then use:
>
> LDFLAGS-tst-origin += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) -lorigin-mod
> $(objpfx)tst-origin: $(objpfx)liborigin-mod.so
> $(objpfx)tst-origin: build-module-nolink = $(objpfx)liborigin-mod.so
>
> But I haven't tested it.

I found out why this didn't work.  The link is for the main program, so
build-module-helper-objlist is unused.  I'm testing a more complete
approach.

Thanks,
Florian
diff mbox series

Patch

diff --git a/elf/Makefile b/elf/Makefile
index 77a76f2142..8055d9ffbb 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -456,6 +456,7 @@  tests += \
   tst-noload \
   tst-non-directory-path \
   tst-null-argv \
+  tst-origin \
   tst-p_align1 \
   tst-p_align2 \
   tst-p_align3 \
@@ -763,6 +764,7 @@  modules-names += \
   libmarkermod5-3 \
   libmarkermod5-4 \
   libmarkermod5-5 \
+  liborigin-mod \
   libtracemod1-1 \
   libtracemod2-1 \
   libtracemod3-1 \
@@ -3442,3 +3444,24 @@  $(objpfx)tst-dlopen-constructor-null: \
   $(objpfx)tst-dlopen-constructor-null-mod2.so
 $(objpfx)tst-dlopen-constructor-null-mod2.so: \
   $(objpfx)tst-dlopen-constructor-null-mod1.so
+
+CFLAGS-tst-origin.c += $(no-stack-protector)
+$(objpfx)tst-origin: $(objpfx)tst-origin.o $(objpfx)liborigin-mod.so
+	$(LINK.o) -o $@ -B$(csu-objpfx) $(LDFLAGS.so) $< \
+		-Wl,-rpath,\$$ORIGIN \
+		-L$(subst :, -L,$(rpath-link)) -Wl,--no-as-needed -lorigin-mod
+$(objpfx)liborigin-mod.so: $(objpfx)liborigin-mod.os
+	$(LINK.o) -shared -o $@ -B$(csu-objpfx) $(LDFLAGS.so) \
+		$(LDFLAGS-soname-fname) \
+		$<
+$(objpfx)tst-origin.out: tst-origin.sh $(objpfx)tst-origin
+	$(SHELL) \
+		$< \
+		'$(common-objpfx)' \
+		'$(test-wrapper-env)' \
+		'$(run-program-env)' \
+		'$(rpath-link)' \
+		tst-origin \
+		liborigin-mod.so \
+		> $@; \
+	$(evaluate-test)
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 4998652adf..6b7e9799f3 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -965,6 +965,12 @@  _dl_map_object_from_fd (const char *name, const char *origname, int fd,
     {
       assert (nsid == LM_ID_BASE);
       memset (&id, 0, sizeof (id));
+      char *realname_can = _dl_canonicalize (fd);
+      if (realname_can != NULL)
+	{
+	  free (realname);
+	  realname = realname_can;
+	}
     }
   else
     {
diff --git a/elf/dl-origin.c b/elf/dl-origin.c
index 9f6b921b01..812f5dbb28 100644
--- a/elf/dl-origin.c
+++ b/elf/dl-origin.c
@@ -47,3 +47,9 @@  _dl_get_origin (void)
 
   return result;
 }
+
+char *
+_dl_canonicalize (int fd)
+{
+  return NULL;
+}
diff --git a/elf/liborigin-mod.c b/elf/liborigin-mod.c
new file mode 100644
index 0000000000..aa6d4c27df
--- /dev/null
+++ b/elf/liborigin-mod.c
@@ -0,0 +1 @@ 
+void foo (void) {}
diff --git a/elf/tst-origin.c b/elf/tst-origin.c
new file mode 100644
index 0000000000..734b2e81f6
--- /dev/null
+++ b/elf/tst-origin.c
@@ -0,0 +1,26 @@ 
+/* Test if $ORIGIN works correctly with symlinks (BZ 25263)
+   Copyright (C) 2025 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
+   <https://www.gnu.org/licenses/>.  */
+
+extern void foo (void);
+
+int
+main (int argc, char *argv[])
+{
+  foo ();
+  return 0;
+}
diff --git a/elf/tst-origin.sh b/elf/tst-origin.sh
new file mode 100755
index 0000000000..2555d3ed9e
--- /dev/null
+++ b/elf/tst-origin.sh
@@ -0,0 +1,60 @@ 
+#!/bin/sh
+# Test if $ORIGIN works correctly with symlinks (BZ 25263)
+# Copyright (C) 2025 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
+# <https://www.gnu.org/licenses/>.
+
+set -e
+
+objpfx=$1
+test_wrapper_env=$2
+run_program_env=$3
+library_path=$4
+test_program=$5
+test_library=$6
+
+cleanup()
+{
+  # Move the binary and library back to build directory
+  mv $tmpdir/sub/$test_program ${objpfx}elf
+  mv $tmpdir/sub/$test_library ${objpfx}elf
+
+  rm -rf $tmpdir
+}
+
+tmpdir=$(mktemp -d "${objpfx}elf/tst-origin.XXXXXXXXXX")
+#trap cleanup 0
+
+mkdir ${tmpdir}/sub
+
+# Remove the dependency from $library_path
+mv ${objpfx}elf/$test_program  $tmpdir/sub
+mv ${objpfx}elf/$test_library  $tmpdir/sub
+
+cd ${tmpdir}
+ln -s sub/$test_program $test_program
+
+${test_wrapper_env} \
+${run_program_env} \
+${objpfx}elf/ld.so --library-path "$library_path" \
+  ./$test_program 2>&1 && rc=0 || rc=$?
+
+# Also check if ldd resolves the dependency
+LD_TRACE_LOADED_OBJECTS=1 \
+${objpfx}elf/ld.so --library-path "$library_path" \
+  ./$test_program 2>&1 | grep 'not found' && rc=1 || rc=0
+
+exit $rc
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index b68e9223c6..d3733d0c3d 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -59,6 +59,7 @@  headers := \
   # headers
 
 routines := \
+  _fitoa_word \
   _itoa \
   _itowa \
   asprintf \
@@ -663,6 +664,8 @@  CFLAGS-dprintf.c += $(config-cflags-wno-ignored-attributes)
 # off for non-shared builds.
 CFLAGS-_itoa.o = $(no-stack-protector)
 CFLAGS-_itoa.op = $(no-stack-protector)
+CFLAGS-_fitoa_word.o = $(no-stack-protector)
+CFLAGS-_fitoa_word.op = $(no-stack-protector)
 
 CFLAGS-scanf13.c += $(test-config-cflags-wno-fortify-source)
 
diff --git a/stdio-common/_fitoa_word.c b/stdio-common/_fitoa_word.c
new file mode 100644
index 0000000000..f0b2707173
--- /dev/null
+++ b/stdio-common/_fitoa_word.c
@@ -0,0 +1,60 @@ 
+/* Internal function for converting integers to ASCII.
+   Copyright (C) 1994-2025 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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <_itoa.h>
+
+char *
+_itoa_word (_ITOA_WORD_TYPE value, char *buflim,
+	    unsigned int base, int upper_case)
+{
+  const char *digits = (upper_case
+			? _itoa_upper_digits
+			: _itoa_lower_digits);
+
+  switch (base)
+    {
+#define SPECIAL(Base)							      \
+    case Base:								      \
+      do								      \
+	*--buflim = digits[value % Base];				      \
+      while ((value /= Base) != 0);					      \
+      break
+
+      SPECIAL (10);
+      SPECIAL (16);
+      SPECIAL (8);
+    default:
+      do
+	*--buflim = digits[value % base];
+      while ((value /= base) != 0);
+    }
+  return buflim;
+}
+#undef SPECIAL
+
+char *
+_fitoa_word (_ITOA_WORD_TYPE value, char *buf, unsigned int base,
+	     int upper_case)
+{
+  char tmpbuf[sizeof (value) * 4];	      /* Worst case length: base 2.  */
+  char *cp = _itoa_word (value, tmpbuf + sizeof (value) * 4, base, upper_case);
+  while (cp < tmpbuf + sizeof (value) * 4)
+    *buf++ = *cp++;
+  return buf;
+}
+
diff --git a/stdio-common/_itoa.c b/stdio-common/_itoa.c
index 51c3ab9c14..08859f0dd0 100644
--- a/stdio-common/_itoa.c
+++ b/stdio-common/_itoa.c
@@ -162,38 +162,6 @@  const struct base_table_t _itoa_base_table[] attribute_hidden =
 };
 #endif
 
-#if IS_IN (libc)
-char *
-_itoa_word (_ITOA_WORD_TYPE value, char *buflim,
-	    unsigned int base, int upper_case)
-{
-  const char *digits = (upper_case
-			? _itoa_upper_digits
-			: _itoa_lower_digits);
-
-  switch (base)
-    {
-#define SPECIAL(Base)							      \
-    case Base:								      \
-      do								      \
-	*--buflim = digits[value % Base];				      \
-      while ((value /= Base) != 0);					      \
-      break
-
-      SPECIAL (10);
-      SPECIAL (16);
-      SPECIAL (8);
-    default:
-      do
-	*--buflim = digits[value % base];
-      while ((value /= base) != 0);
-    }
-  return buflim;
-}
-#undef SPECIAL
-#endif /* IS_IN (libc) */
-
-
 #if _ITOA_NEEDED
 char *
 _itoa (unsigned long long int value, char *buflim, unsigned int base,
@@ -460,17 +428,6 @@  _itoa (unsigned long long int value, char *buflim, unsigned int base,
 }
 #endif
 
-char *
-_fitoa_word (_ITOA_WORD_TYPE value, char *buf, unsigned int base,
-	     int upper_case)
-{
-  char tmpbuf[sizeof (value) * 4];	      /* Worst case length: base 2.  */
-  char *cp = _itoa_word (value, tmpbuf + sizeof (value) * 4, base, upper_case);
-  while (cp < tmpbuf + sizeof (value) * 4)
-    *buf++ = *cp++;
-  return buf;
-}
-
 #if _ITOA_NEEDED
 char *
 _fitoa (unsigned long long value, char *buf, unsigned int base, int upper_case)
diff --git a/sysdeps/generic/_itoa.h b/sysdeps/generic/_itoa.h
index d7e3007389..2f170d3bf2 100644
--- a/sysdeps/generic/_itoa.h
+++ b/sysdeps/generic/_itoa.h
@@ -51,40 +51,9 @@  hidden_proto (_itoa_upper_digits)
 hidden_proto (_itoa_lower_digits)
 #endif
 
-#if IS_IN (libc)
 extern char *_itoa_word (_ITOA_WORD_TYPE value, char *buflim,
 			 unsigned int base,
 			 int upper_case) attribute_hidden;
-#else
-static inline char * __attribute__ ((unused, always_inline))
-_itoa_word (_ITOA_WORD_TYPE value, char *buflim,
-	    unsigned int base, int upper_case)
-{
-  const char *digits = (upper_case
-			? _itoa_upper_digits
-			: _itoa_lower_digits);
-
-  switch (base)
-    {
-# define SPECIAL(Base)							      \
-    case Base:								      \
-      do								      \
-	*--buflim = digits[value % Base];				      \
-      while ((value /= Base) != 0);					      \
-      break
-
-      SPECIAL (10);
-      SPECIAL (16);
-      SPECIAL (8);
-    default:
-      do
-	*--buflim = digits[value % base];
-      while ((value /= base) != 0);
-    }
-  return buflim;
-}
-# undef SPECIAL
-#endif
 
 /* Similar to the _itoa functions, but output starts at buf and pointer
    after the last written character is returned.  */
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 8465cbaa9b..19494b82ee 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -1223,6 +1223,10 @@  extern struct link_map * _dl_get_dl_main_map (void) attribute_hidden;
 /* Find origin of the executable.  */
 extern const char *_dl_get_origin (void) attribute_hidden;
 
+/* Return the canonalized path name from the opened file descriptor FD,
+   or NULL otherwise.  */
+extern char * _dl_canonicalize (int fd) attribute_hidden;
+
 /* Count DSTs.  */
 extern size_t _dl_dst_count (const char *name) attribute_hidden;
 
diff --git a/sysdeps/mach/hurd/Makefile b/sysdeps/mach/hurd/Makefile
index 13e5cea4c2..4b69b40065 100644
--- a/sysdeps/mach/hurd/Makefile
+++ b/sysdeps/mach/hurd/Makefile
@@ -300,6 +300,8 @@  ifeq ($(subdir),elf)
 check-execstack-xfail += ld.so libc.so libpthread.so
 # We always create a thread for signals
 test-xfail-tst-single_threaded-pthread-static = yes
+# Bug 25263
+test-xfail-tst-origin = yes
 
 CFLAGS-tst-execstack.c += -DDEFAULT_RWX_STACK=1
 endif
diff --git a/sysdeps/unix/sysv/linux/dl-origin.c b/sysdeps/unix/sysv/linux/dl-origin.c
index decdd8ae9e..3c52ba51a6 100644
--- a/sysdeps/unix/sysv/linux/dl-origin.c
+++ b/sysdeps/unix/sysv/linux/dl-origin.c
@@ -21,6 +21,7 @@ 
 #include <fcntl.h>
 #include <ldsodefs.h>
 #include <sysdep.h>
+#include <fd_to_filename.h>
 
 /* On Linux >= 2.1 systems which have the dcache implementation we can get
    the path of the application from the /proc/self/exe symlink.  Try this
@@ -72,3 +73,25 @@  _dl_get_origin (void)
 
   return result;
 }
+
+/* On Linux, readlink on the magic symlinks in /proc/self/fd also has
+   the same behavior of returning the canonical path from the dcache.
+   If it does not work, we do not bother to canonicalize. */
+
+char *
+_dl_canonicalize (int fd)
+{
+  struct fd_to_filename fdfilename;
+  char canonical[PATH_MAX];
+  char *path = __fd_to_filename (fd, &fdfilename);
+  int size = INTERNAL_SYSCALL_CALL (readlinkat, AT_FDCWD, path,
+                                    canonical, PATH_MAX - 1);
+
+  /* Check if the path was truncated.  */
+  if (size >= 0 && size < PATH_MAX - 1)
+    {
+      canonical[size] = '\0';
+      return __strdup (canonical);
+    }
+  return NULL;
+}