[PATCH 1/1] elf: Canonicalize $ORIGIN in an explicit ld.so invocation [BZ #25263]
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Thu Feb 27 20:26:20 GMT 2025
On 18/02/25 17:58, Geoffrey Thomas 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.)
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).
And I think the main issue is how we initialize the link_map on _dl_new_object,
where l_origin is set to current directly where it should be the realpath from
the realname.
Something like:
diff --git a/elf/dl-object.c b/elf/dl-object.c
index 51d3704edc..0288d049d4 100644
--- a/elf/dl-object.c
+++ b/elf/dl-object.c
@@ -24,7 +24,6 @@
#include <assert.h>
-
/* Add the new link_map NEW to the end of the namespace list. */
void
_dl_add_to_namespace_list (struct link_map *new, Lmid_t nsid)
@@ -189,7 +188,7 @@ _dl_new_object (char *realname, const char *libname, int type,
if (realname[0] != '\0')
{
size_t realname_len = strlen (realname) + 1;
- char *origin;
+ char *origin = NULL;
char *cp;
if (realname[0] == '/')
@@ -202,48 +201,21 @@ _dl_new_object (char *realname, const char *libname, int type,
origin = (char *) -1;
goto out;
}
+
+ /* Add the real file name. */
+ cp = __mempcpy (cp, realname, realname_len);
}
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);
-
+ char *result = realpath_test (realname, NULL);
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');
+ goto out;
+ origin = dirname_test (result);
+ cp = (strchr) (result, '\0');
if (cp[-1] != '/')
*cp++ = '/';
}
- /* Add the real file name. */
- cp = __mempcpy (cp, realname, realname_len);
-
/* Now remove the filename and the slash. Leave the slash if
the name is something like "/foo". */
do
I have to use some mockup implementation for realpath_test and
dirname_test because canocanlize is quite complex and it pulls too
much code. But the idea is have the same semantic as the symbol provided
by the libc (it would most likely have to change the stdlib/canonicalize.c
to meet the loader requirements).
With this patch applied, I see now:
$ LD_DEBUG=libs $GLIBCBUILDDIR/elf/ld.so --library-path $GLIBCBUILDDIR ./main
3159143: decompose_rpath
3159143: find library=lib1.so [0]; searching
3159143: search path=$GLIBCBUILDDIR/glibc-hwcaps/x86-64-v3:$GLIBCBUILDDIR/glibc-hwcaps/x86-64-v2:$GLIBCBUILDDIR (--library-path)
3159143: trying file=$GLIBCBUILDDIR/glibc-hwcaps/x86-64-v3/lib1.so
3159143: trying file=$GLIBCBUILDDIR/glibc-hwcaps/x86-64-v2/lib1.so
3159143: trying file=$GLIBCBUILDDIR/lib1.so
3159143: search path=$GLIBCBUILDDIR/tst-bz25263/test/glibc-hwcaps/x86-64-v3:$GLIBCBUILDDIR/tst-bz25263/test/glibc-hwcaps/x86-64-v2:$GLIBCBUILDDIR/tst-bz25263/test (RUNPATH from file ./main)
3159143: trying file=$GLIBCBUILDDIR/tst-bz25263/test/glibc-hwcaps/x86-64-v3/lib1.so
3159143: trying file=$GLIBCBUILDDIR/tst-bz25263/test/glibc-hwcaps/x86-64-v2/lib1.so
3159143: trying file=$GLIBCBUILDDIR/tst-bz25263/test/lib1.so
So I think we should move to a fix that works for the loader not only
for LD_TRACE_LOADED_OBJECTS.
Besides this, this issue would also require a proper regression test.
> ---
> elf/dl-load.c | 1 +
> elf/dl-origin.c | 5 +++++
> sysdeps/generic/ldsodefs.h | 5 +++++
> sysdeps/unix/sysv/linux/dl-origin.c | 27 +++++++++++++++++++++++++++
> 4 files changed, 38 insertions(+)
>
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> index f905578a65..2ef585bc5c 100644
> --- a/elf/dl-load.c
> +++ b/elf/dl-load.c
> @@ -965,6 +965,7 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
> {
> assert (nsid == LM_ID_BASE);
> memset (&id, 0, sizeof (id));
> + _dl_canonicalize (&realname, fd);
> }
> else
> {
> diff --git a/elf/dl-origin.c b/elf/dl-origin.c
> index 9f6b921b01..d942f3f0bb 100644
> --- a/elf/dl-origin.c
> +++ b/elf/dl-origin.c
> @@ -47,3 +47,8 @@ _dl_get_origin (void)
>
> return result;
> }
> +
> +void
> +_dl_canonicalize (char **filename, int fd)
> +{
> +}
> diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> index e871f27ff2..36b472e19c 100644
> --- a/sysdeps/generic/ldsodefs.h
> +++ b/sysdeps/generic/ldsodefs.h
> @@ -1198,6 +1198,11 @@ 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;
>
> +/* Canonicalize the path to an open file. FILENAME is a pointer to a
> + string allocated with malloc; it may be freed and replaced with
> + another string allocated with malloc. */
> +extern void _dl_canonicalize (char **filename, int fd) attribute_hidden;
> +
> /* Count DSTs. */
> extern size_t _dl_dst_count (const char *name) attribute_hidden;
>
> diff --git a/sysdeps/unix/sysv/linux/dl-origin.c b/sysdeps/unix/sysv/linux/dl-origin.c
> index decdd8ae9e..b12eff4315 100644
> --- a/sysdeps/unix/sysv/linux/dl-origin.c
> +++ b/sysdeps/unix/sysv/linux/dl-origin.c
> @@ -72,3 +72,30 @@ _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. */
> +
> +void
> +_dl_canonicalize (char **filename, int fd)
> +{
> + char *canonical = (char *) malloc (PATH_MAX + 1);
> + char buf[25];
> + buf[24] = '\0';
> + char *path = _itoa (fd, buf + 24, 10, 0);
> + path = memcpy (path - 14, "/proc/self/fd/", 14);
> +
> + int size = INTERNAL_SYSCALL_CALL (readlinkat, AT_FDCWD, path,
> + canonical, PATH_MAX );
> + if (size >= 0)
> + {
> + free (*filename);
> + canonical[size] = '\0';
> + *filename = canonical;
> + }
> + else
> + {
> + free (canonical);
> + }
> +}
More information about the Libc-alpha
mailing list