[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