[PATCH 1/1] elf: Canonicalize $ORIGIN in an explicit ld.so invocation [BZ #25263]

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Mon Mar 3 20:08:32 GMT 2025



On 28/02/25 22:27, Adhemerval Zanella Netto wrote:
> 
> 
> On 28/02/25 16:02, Geoffrey Thomas wrote:
>> On Thu, Feb 27, 2025, at 3:26 PM, Adhemerval Zanella Netto wrote:
>>> Although this change solves the LD_TRACE_LOADED_OBJECTS=1 (ldd) issue, I
>>> think it still misses the underlying issue for the case user want to also 
>>> run the binary with a direct loader invocation (as Florian has hinted on 
>>> comment #4).
>>>
>>> For instance, with this testcase (similar to what electron is doing from
>>> the bug report):
>>>
>>> $ cat test/main.c
>>> int main (int argc, char *argv[]) { return 0; }
>>> $ cat test/lib1.c
>>> int foo (void) { return 42; }
>>> tst-bz25263 $ mkdir test && cd test
>>> test $ gcc -Wall -shared -Wl,-soname,lib1.so lib1.c -o lib1.so
>>> test $ gcc -no-pie -Wall main.c -o main -Wl,-rpath,\$ORIGIN -L`pwd` 
>>> -Wl,-no-as-needed -l1
>>> test $ gcc -no-pie -Wall main.c -o main-hcp 
>>> -Wl,-rpath,\$ORIGIN:$GLIBCBUILDDIR 
>>> -Wl,-dynamic-linker,$GLIBCBUILDDIR/elf/ld.so -L`pwd` -Wl,-no-as-needed 
>>> -l1
>>> main $ cd ..
>>> $ ln -s test/main main
>>> $ ln -s test/main-hcp main-hcp
>>>
>>> So 'main' is a binary without just one RUNPATH with $ORIGIN, while 'main-hcp'
>>> is binary that will use a built glibc (along with the RUNPATH).
>>>
>>> Checking the library path resolution for both cases:
>>>
>>> $ LD_DEBUG=libs ./main-hcp
>>>    3151277:     find library=lib1.so [0]; searching
>>>    3151277:      search 
>>> path=$GLIBCBUILDDIR/tst-bz25263/test/glibc-hwcaps/x86-64-v3:/home/azanella/Projects/glibc/build/
>>> x86_64-linux-gnu-work/tst-bz25263/test/glibc-hwcaps/x86-64-v2:$GLIBCBUILDDIR/tst-bz25263/test:$GLIBCBUILDDIR/glibc-hwcaps/x86-64-v3:$GLIBCBUILDDIR/glibc-hwcaps/x86-64-v2:$GLIBCBUILDDIR 
>>>          (RUNPATH from file ./main-hcp)
>>>    3151277:       trying 
>>> file=$GLIBCBUILDDIR/tst-bz25263/test/glibc-hwcaps/x86-64-v3/lib1.so
>>>    3151277:       trying 
>>> file=$GLIBCBUILDDIR/tst-bz25263/test/glibc-hwcaps/x86-64-v2/lib1.so
>>>    3151277:       trying file=$GLIBCBUILDDIR/tst-bz25263/test/lib1.so
>>>
>>> LD_DEBUG=libs $GLIBCBUILDDIR/elf/ld.so --library-path $GLIBCBUILDDIR 
>>> ./main
>>>    3151286:	find library=lib1.so [0]; searching
>>>    3151286:	 search 
>>> path=$GLIBCBUILDDIR/glibc-hwcaps/x86-64-v3:$GLIBCBUILDDIR/glibc-hwcaps/x86-64-v2:$GLIBCBUILDDIR		(--library-path)
>>>    3151286:	  trying file=$GLIBCBUILDDIR/glibc-hwcaps/x86-64-v3/lib1.so
>>>    3151286:	  trying file=$GLIBCBUILDDIR/glibc-hwcaps/x86-64-v2/lib1.so
>>>    3151286:	  trying file=$GLIBCBUILDDIR/lib1.so
>>>    3151286:	 search 
>>> path=$GLIBCBUILDDIR/tst-bz25263/./glibc-hwcaps/x86-64-v3:$GLIBCBUILDDIR/tst-bz25263/./glibc-hwcaps/x86-64-v2:$GLIBCBUILDDIR/tst-bz25263/.		(RUNPATH 
>>> from file ./main)
>>>    3151286:	  trying 
>>> file=$GLIBCBUILDDIR/tst-bz25263/./glibc-hwcaps/x86-64-v3/lib1.so
>>>    3151286:	  trying 
>>> file=$GLIBCBUILDDIR/tst-bz25263/./glibc-hwcaps/x86-64-v2/lib1.so
>>>    3151286:	  trying file=$GLIBCBUILDDIR/tst-bz25263/./lib1.so
>>>    3151286:	 search cache=/etc/ld.so.cache
>>>    3151286:	 search 
>>> path=/lib64/glibc-hwcaps/x86-64-v3:/lib64/glibc-hwcaps/x86-64-v2:/lib64:/usr/lib64/glibc-hwcaps/x86-64-v3:/usr/lib64/glibc-hwcaps/x86-64-v2:/usr/lib64		(system 
>>> search path)
>>>    3151286:	  trying file=/lib64/glibc-hwcaps/x86-64-v3/lib1.so
>>>    3151286:	  trying file=/lib64/glibc-hwcaps/x86-64-v2/lib1.so
>>>    3151286:	  trying file=/lib64/lib1.so
>>>    3151286:	  trying file=/usr/lib64/glibc-hwcaps/x86-64-v3/lib1.so
>>>    3151286:	  trying file=/usr/lib64/glibc-hwcaps/x86-64-v2/lib1.so
>>>    3151286:	  trying file=/usr/lib64/lib1.so
>>>
>>> You can see that if the binary is not issued directly by the loader 
>>> (main-hcp),
>>> it will have the full realpath on the search path 
>>> (GLIBCBUILDDIR/tst-bz25263/test);
>>> different that binary issued by the loader which is has the current 
>>> directory
>>> (GLIBCBUILDDIR/tst-bz25263).
>>
>> This is with my patch? Directly on top of glibc-2.41 (commit db71c940
>> from github.com/geofft/glibc), with a checkout at /glibc and so
>> GLIBCBUILDDIR=/glibc/build, I get
> 
> Yes, this your patch on top of 9e51ae3cd0c7f65bdeba93b7f1d780cdb21fc269
> 
>>
>> [root@nixos:/glibc/build/tst-bz25263]# ls -l
>> total 0
>> lrwxr-xr-x 1 root root   9 Feb 28 13:37 main -> test/main
>> lrwxr-xr-x 1 root root  13 Feb 28 13:37 main-hcp -> test/main-hcp
>> drwxr-xr-x 7 root root 224 Feb 28 13:37 test
>> [root@nixos:/glibc/build/tst-bz25263]# readelf -d main | grep NEEDED
>>  0x0000000000000001 (NEEDED)             Shared library: [lib1.so]
>>  0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
>> [root@nixos:/glibc/build/tst-bz25263]# LD_DEBUG=libs /glibc/build/elf/ld.so --library-path /glibc/build ./main
>>      49044:     find library=lib1.so [0]; searching
>>      49044:      search path=/glibc/build               (--library-path)
>>      49044:       trying file=/glibc/build/lib1.so
>>      49044:      search path=/glibc/build/tst-bz25263/test:/nix/store/6j2qcdi8axcl91v08blc3vjn39y117z3-shell/lib:/nix/store/q6kvdfhlp251n8rx183lg1n245kwg6m8-glibc-2.39-52/lib:/nix/store/fxh2cg0sfgygfya6qjr11gzj4sbjx0h9-gcc-13.2.0-lib/lib         (RUNPATH from file ./main)
>>      49044:       trying file=/glibc/build/tst-bz25263/test/lib1.so
>>      49044:
>>      49044:     find library=libc.so.6 [0]; searching
>>      49044:      search path=/glibc/build               (--library-path)
>>      49044:       trying file=/glibc/build/libc.so.6
>> [...]
>>
>> which, if I'm following correctly, is the fixed behavior.
> 
> Sigh, for some reason I did not use git-pw on your patch and applied
> directly from mailbox it was malformated with the wrong path for the
> procfs. With 'git-pw patch apply 106751' it now seems to work.
> 
> You might want to use this instead (we have a helper to access the
> procfs).
> 
>   struct fd_to_filename fdfilename;
>   char *canonical = (char *) malloc (PATH_MAX + 1);
>   char *path = __fd_to_filename (fd, &fdfilename);
>   int size = INTERNAL_SYSCALL_CALL (readlinkat, AT_FDCWD, path,
>                                     canonical, PATH_MAX);
> 
> It does seems to work.

I like your approach better in fact, I was trying to make it generic
with realpath and it pulls too many dependencies on Hurd that I would
need to make it Linux specific anyway.

However I think we should make the _dl_canonicalize simpler and not
mess with the input argument since all the logic in the caller 
(_dl_map_object_from_fd).  Also, limit the maximum path to PATH_MAX
(and not PATH_MAX + 1) similar to _dl_get_origin and optimize the
memory allocation a bit with __strdup:

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);
  if (size >= 0)
    {
      canonical[size] = '\0';
      return __strdup (canonical);
    }
  return NULL;
}

I added this suggestion along with a testcase [1], what do you
think?

[1] https://sourceware.org/git/?p=glibc.git;a=commit;h=372c632ce7c78471dbda69ca33625d1ecb6fb2f7

[1] https://sourceware.org/git?p=glibc.git;a=commit;h=372c632ce7c78471dbda69ca33625d1ecb6fb2f7


More information about the Libc-alpha mailing list