]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
elf: Canonicalize $ORIGIN in an explicit ld.so invocation [BZ 25263]
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Tue, 18 Feb 2025 20:58:16 +0000 (15:58 -0500)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Thu, 13 Mar 2025 19:50:16 +0000 (16:50 -0300)
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 requires _fitoa_word and _itoa_word, which for
32-bits pulls a lot of definitions from _itoa.c (due _ITOA_NEEDED
being 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>
Reviewed-by: Geoffrey Thomas <geofft@ldpreload.com>
Tested-by: Geoffrey Thomas <geofft@ldpreload.com>
13 files changed:
elf/Makefile
elf/dl-load.c
elf/dl-origin.c
elf/liborigin-mod.c [new file with mode: 0644]
elf/tst-origin.c [new file with mode: 0644]
elf/tst-origin.sh [new file with mode: 0755]
stdio-common/Makefile
stdio-common/_fitoa_word.c [new file with mode: 0644]
stdio-common/_itoa.c
sysdeps/generic/_itoa.h
sysdeps/generic/ldsodefs.h
sysdeps/mach/hurd/Makefile
sysdeps/unix/sysv/linux/dl-origin.c

index 77a76f214269aea7940a2895d2d3b8a34ce5f9a8..2bce1ed4867248c2ec70e49ddc53bca23f17d7ec 100644 (file)
@@ -3442,3 +3442,27 @@ $(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
+
+ifeq ($(run-built-tests),yes)
+tests-special += $(objpfx)tst-origin.out
+endif
+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)
index 4998652adff36568d517d402ffc198e87b3d22fa..6b7e9799f323d04bf47a672bb28c99e477808e85 100644 (file)
@@ -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
     {
index 9f6b921b0185b12b017e83fecdb92a03e7e19733..812f5dbb28fd1ba84e2e5bd81ee583475ff255ce 100644 (file)
@@ -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 (file)
index 0000000..aa6d4c2
--- /dev/null
@@ -0,0 +1 @@
+void foo (void) {}
diff --git a/elf/tst-origin.c b/elf/tst-origin.c
new file mode 100644 (file)
index 0000000..734b2e8
--- /dev/null
@@ -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 (executable)
index 0000000..a033810
--- /dev/null
@@ -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
index b68e9223c6fe581f4b544518ecbe5d839f8e5e6a..d3733d0c3d145151ee23c3fdd4c7442f2456fb7e 100644 (file)
@@ -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 (file)
index 0000000..8064b1e
--- /dev/null
@@ -0,0 +1,59 @@
+/* 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;
+}
index 51c3ab9c1478fd1636471aef9dcaca525e044cc2..08859f0dd086b5295ceeff8edcd852419e3ca580 100644 (file)
@@ -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)
index d7e300738919d3f8c5f2bba1e873f28c98f518a6..2f170d3bf2342b9b066db85ad049d71ca7b2413b 100644 (file)
@@ -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.  */
index 8465cbaa9b2aacf61838dc55a435d6cd7cffdf5a..19494b82eee217c3eb9ee3a2325ad716b4f450ca 100644 (file)
@@ -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;
 
index 13e5cea4c2b0e7fb9506eff8fe8679c771ad8d2c..4b69b400656ad6323639ab590ae6b7b2548e31af 100644 (file)
@@ -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
index decdd8ae9e2304ab6c4cb8353ff49721654fb95d..3c52ba51a666cc17556348356e3983c39afb80ec 100644 (file)
@@ -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;
+}