]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
elf: Support dlvsym within libc.so
authorFlorian Weimer <fweimer@redhat.com>
Thu, 4 Jan 2018 17:46:17 +0000 (18:46 +0100)
committerFlorian Weimer <fweimer@redhat.com>
Thu, 4 Jan 2018 17:46:24 +0000 (18:46 +0100)
This commit adds a new _dl_open_hook entry for dlvsym and implements the
function using the existing dl_lookup_symbol_x function supplied by the
dynamic loader.

A new hook variable, _dl_open_hook2, is introduced, which should make
this change suitable for backporting: For old statically linked
binaries, __libc_dlvsym will always return NULL.

ChangeLog
elf/Makefile
elf/Versions
elf/dl-libc.c
elf/tst-libc_dlvsym-dso.c [new file with mode: 0644]
elf/tst-libc_dlvsym-static.c [new file with mode: 0644]
elf/tst-libc_dlvsym.c [new file with mode: 0644]
elf/tst-libc_dlvsym.h [new file with mode: 0644]
include/dlfcn.h

index 878a738a32e8758f615438e3ad344b425c5fb9a1..7f0c2ebb0e0263140957fdc082453cf5f808a092 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,28 @@
+2018-01-04  Florian Weimer  <fweimer@redhat.com>
+
+       Add support for calling dlvsym from libc.so.
+       * include/dlfcn.h (__libc_dlvsym): Declare.
+       * elf/Makefile (tests-static-internal): Add
+       tst-libc_dlvsym-static.
+       (tests-internal): Add tst-libc_dlvsym.
+       (modules-names): Add tst-libc_dlvsym-dso.
+       (tst-libc_dlvsym, tst-libc_dlvsym-static): Link with libdl.
+       (tst-libc_dlvsym-dso.so): Link with libdl, libsupport.
+       (tst-libc_dlvsym.out, tst-libc_dlvsym-static.out): The shared
+       object tst-libc_dlvsym-dso.so needs to be built before running
+       these tests.
+       (tst-libc_dlvsym-static-ENV): Set LD_LIBRARY_PATH.
+       * elf/Versions: Export __libc_dlvsym.
+       * elf/dl-libc.c (struct do_dlvsym_args): New.
+       (do_dlvsym, __libc_dlvsym): New functions.
+       (struct dl_open_hook, _dl_open_hook): Add dlvsym member.
+       (_dl_open_hook2): New variable.
+       (__libc_register_dl_open_hook): Set it.
+       * elf/tst-libc_dlvsym-dso.c: New file.
+       * elf/tst-libc_dlvsym-static.c: Likewise.
+       * elf/tst-libc_dlvsym.c: Likewise.
+       * elf/tst-libc_dlvsym.h: Likewise.
+
 2018-01-03  Samuel Thibault  <samuel.thibault@ens-lyon.org>
 
        * support/support_enter_mount_namespace.c [!CLONE_NEWNS]: Do not
index 8967ac2685b0eaba82c612567f394d6175aaa605..2a432d8beebcd207af7643af67379f0a9f527f3a 100644 (file)
@@ -151,7 +151,7 @@ tests-static-normal := tst-leaks1-static tst-array1-static tst-array5-static \
               tst-linkall-static tst-env-setuid tst-env-setuid-tunables
 tests-static-internal := tst-tls1-static tst-tls2-static \
               tst-ptrguard1-static tst-stackguard1-static \
-              tst-tls1-static-non-pie
+              tst-tls1-static-non-pie tst-libc_dlvsym-static
 
 CRT-tst-tls1-static-non-pie := $(csu-objpfx)crt1.o
 tst-tls1-static-non-pie-no-pie = yes
@@ -192,7 +192,7 @@ tests += restest1 preloadtest loadfail multiload origtest resolvfail \
 tests-internal += loadtest unload unload2 circleload1 \
         neededtest neededtest2 neededtest3 neededtest4 \
         tst-tls3 tst-tls6 tst-tls7 tst-tls8 tst-dlmopen2 \
-        tst-ptrguard1 tst-stackguard1
+        tst-ptrguard1 tst-stackguard1 tst-libc_dlvsym
 ifeq ($(build-hardcoded-path-in-tests),yes)
 tests += tst-dlopen-aout
 tst-dlopen-aout-no-pie = yes
@@ -272,7 +272,7 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
                tst-audit12mod1 tst-audit12mod2 tst-audit12mod3 tst-auditmod12 \
                tst-latepthreadmod $(tst-tls-many-dynamic-modules) \
                tst-nodelete-dlclose-dso tst-nodelete-dlclose-plugin \
-               tst-main1mod
+               tst-main1mod tst-libc_dlvsym-dso
 ifeq (yes,$(have-mtls-dialect-gnu2))
 tests += tst-gnu2-tls1
 modules-names += tst-gnu2-tls1mod
@@ -1436,3 +1436,13 @@ CRT-tst-main1 := $(csu-objpfx)crt1.o
 tst-main1-no-pie = yes
 LDLIBS-tst-main1 = $(libsupport)
 tst-main1mod.so-no-z-defs = yes
+
+# Both the main program and the DSO for tst-libc_dlvsym need to link
+# against libdl.
+$(objpfx)tst-libc_dlvsym: $(libdl)
+$(objpfx)tst-libc_dlvsym-dso.so: $(libsupport) $(libdl)
+$(objpfx)tst-libc_dlvsym.out: $(objpfx)tst-libc_dlvsym-dso.so
+$(objpfx)tst-libc_dlvsym-static: $(common-objpfx)dlfcn/libdl.a
+tst-libc_dlvsym-static-ENV = \
+  LD_LIBRARY_PATH=$(objpfx):$(common-objpfx):$(common-objpfx)dlfcn
+$(objpfx)tst-libc_dlvsym-static.out: $(objpfx)tst-libc_dlvsym-dso.so
index 79ffaf73d26aab3f5b1925b44cce414161c118c9..3b09901f6c31e3d41c926c0b35246cb7db3c4f10 100644 (file)
@@ -23,9 +23,9 @@ libc {
   GLIBC_PRIVATE {
     # functions used in other libraries
     _dl_addr;
-    _dl_open_hook;
+    _dl_open_hook; _dl_open_hook2;
     _dl_sym; _dl_vsym;
-    __libc_dlclose; __libc_dlopen_mode; __libc_dlsym;
+    __libc_dlclose; __libc_dlopen_mode; __libc_dlsym; __libc_dlvsym;
 
     # Internal error handling support.  Interposes the functions in ld.so.
     _dl_signal_exception; _dl_catch_exception;
index c6818e03132b240cab1bd883b09f8850e6e3be15..fc01f5514d4f656c250a6dcd8c3f1f496d2eb662 100644 (file)
@@ -20,6 +20,7 @@
 #include <dlfcn.h>
 #include <stdlib.h>
 #include <ldsodefs.h>
+#include <dl-hash.h>
 
 extern int __libc_argc attribute_hidden;
 extern char **__libc_argv attribute_hidden;
@@ -78,6 +79,15 @@ struct do_dlsym_args
   const ElfW(Sym) *ref;
 };
 
+struct do_dlvsym_args
+{
+  /* dlvsym is like dlsym.  */
+  struct do_dlsym_args dlsym;
+
+  /* But dlvsym needs a version  as well.  */
+  struct r_found_version version;
+};
+
 static void
 do_dlopen (void *ptr)
 {
@@ -98,6 +108,18 @@ do_dlsym (void *ptr)
                                             DL_LOOKUP_RETURN_NEWEST, NULL);
 }
 
+static void
+do_dlvsym (void *ptr)
+{
+  struct do_dlvsym_args *args = ptr;
+  args->dlsym.ref = NULL;
+  args->dlsym.loadbase
+    = GLRO(dl_lookup_symbol_x) (args->dlsym.name, args->dlsym.map,
+                               &args->dlsym.ref,
+                               args->dlsym.map->l_local_scope,
+                               &args->version, 0, 0, NULL);
+}
+
 static void
 do_dlclose (void *ptr)
 {
@@ -112,6 +134,7 @@ struct dl_open_hook
   void *(*dlopen_mode) (const char *name, int mode);
   void *(*dlsym) (void *map, const char *name);
   int (*dlclose) (void *map);
+  void *(*dlvsym) (void *map, const char *name, const char *version);
 };
 
 #ifdef SHARED
@@ -119,6 +142,15 @@ extern struct dl_open_hook *_dl_open_hook;
 libc_hidden_proto (_dl_open_hook);
 struct dl_open_hook *_dl_open_hook __attribute__ ((nocommon));
 libc_hidden_data_def (_dl_open_hook);
+
+/* The dlvsym member was added retroactively to struct dl_open_hook.
+   Static applications which have it will set _dl_open_hook2 in
+   addition to _dl_open_hook.  */
+extern struct dl_open_hook *_dl_open_hook2;
+libc_hidden_proto (_dl_open_hook2);
+struct dl_open_hook *_dl_open_hook2 __attribute__ ((nocommon));
+libc_hidden_data_def (_dl_open_hook2);
+
 #else
 static void
 do_dlsym_private (void *ptr)
@@ -142,7 +174,8 @@ static struct dl_open_hook _dl_open_hook =
   {
     .dlopen_mode = __libc_dlopen_mode,
     .dlsym = __libc_dlsym,
-    .dlclose = __libc_dlclose
+    .dlclose = __libc_dlclose,
+    .dlvsym = __libc_dlvsym,
   };
 #endif
 
@@ -192,6 +225,11 @@ __libc_register_dl_open_hook (struct link_map *map)
   hook = (struct dl_open_hook **) __libc_dlsym_private (map, "_dl_open_hook");
   if (hook != NULL)
     *hook = &_dl_open_hook;
+
+  /* For dlvsym support.  */
+  hook = (struct dl_open_hook **) __libc_dlsym_private (map, "_dl_open_hook2");
+  if (hook != NULL)
+    *hook = &_dl_open_hook;
 }
 #endif
 
@@ -211,6 +249,39 @@ __libc_dlsym (void *map, const char *name)
 }
 libc_hidden_def (__libc_dlsym)
 
+/* Replacement for dlvsym.  MAP must be a real map.  This function
+   returns NULL without setting the dlerror value in case of static
+   dlopen from an old binary.  */
+void *
+__libc_dlvsym (void *map, const char *name, const char *version)
+{
+#ifdef SHARED
+  if (!rtld_active ())
+    {
+      /* The static application is too old and does not provide the
+        dlvsym hook.  */
+      if (_dl_open_hook2 == NULL)
+       return NULL;
+      return _dl_open_hook2->dlvsym (map, name, version);
+    }
+#endif
+
+  struct do_dlvsym_args args;
+  args.dlsym.map = map;
+  args.dlsym.name = name;
+
+  /* See _dl_vsym in dl-sym.c.  */
+  args.version.name = version;
+  args.version.hidden = 1;
+  args.version.hash = _dl_elf_hash (version);
+  args.version.filename = NULL;
+
+  return (dlerror_run (do_dlvsym, &args) ? NULL
+         : (void *) (DL_SYMBOL_ADDRESS (args.dlsym.loadbase,
+                                        args.dlsym.ref)));
+}
+libc_hidden_def (__libc_dlvsym)
+
 int
 __libc_dlclose (void *map)
 {
diff --git a/elf/tst-libc_dlvsym-dso.c b/elf/tst-libc_dlvsym-dso.c
new file mode 100644 (file)
index 0000000..d74c1ea
--- /dev/null
@@ -0,0 +1,25 @@
+/* Compare dlvsym and __libc_dlvsym results.  Shared object code.
+   Copyright (C) 2017 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include "tst-libc_dlvsym.h"
+
+void
+compare_vsyms_global (void)
+{
+  compare_vsyms ();
+}
diff --git a/elf/tst-libc_dlvsym-static.c b/elf/tst-libc_dlvsym-static.c
new file mode 100644 (file)
index 0000000..955f28d
--- /dev/null
@@ -0,0 +1,32 @@
+/* Compare dlvsym and __libc_dlvsym results.  Static version.
+   Copyright (C) 2017 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/xdlfcn.h>
+
+static int
+do_test (void)
+{
+  void *handle = xdlopen ("tst-libc_dlvsym-dso.so", RTLD_LAZY);
+  void (*compare) (void) = xdlsym (handle, "compare_vsyms_global");
+  compare ();
+  xdlclose (handle);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-libc_dlvsym.c b/elf/tst-libc_dlvsym.c
new file mode 100644 (file)
index 0000000..864db4e
--- /dev/null
@@ -0,0 +1,34 @@
+/* Compare dlvsym and __libc_dlvsym results.  Dynamic version.
+   Copyright (C) 2017 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include "tst-libc_dlvsym.h"
+
+static int
+do_test (void)
+{
+  compare_vsyms ();
+
+  void *handle = xdlopen ("tst-libc_dlvsym-dso.so", RTLD_LAZY);
+  void (*compare) (void) = xdlsym (handle, "compare_vsyms_global");
+  compare ();
+  xdlclose (handle);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-libc_dlvsym.h b/elf/tst-libc_dlvsym.h
new file mode 100644 (file)
index 0000000..6b1d8d5
--- /dev/null
@@ -0,0 +1,125 @@
+/* Compare dlvsym and __libc_dlvsym results.  Common code.
+   Copyright (C) 2017 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
+   <http://www.gnu.org/licenses/>.  */
+
+/* compare_vsyms is the main entry point for these tests.
+
+   Indirectly, It calls __libc_dlvsym (from libc.so; internal
+   interface) and dlvsym (from libdl.so; public interface) to compare
+   the results for a selected set of symbols in libc.so which
+   typically have more than one symbol version.  The two functions are
+   implemented by somewhat different code, and this test checks that
+   their results are the same.
+
+   The versions are generated to range from GLIBC_2.0 to GLIBC_2.Y,
+   with Y being the current __GLIBC_MINOR__ version plus two.  In
+   addition, there is a list of special symbol versions of the form
+   GLIBC_2.Y.Z, which were used for some releases.
+
+   Comparing the two dlvsym results at versions which do not actually
+   exist does not test much, but it will not contribute to false test
+   failures, either.  */
+
+#include <array_length.h>
+#include <gnu/lib-names.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+
+/* Run consistency check for versioned symbol NAME@VERSION.  NB: We
+   may execute in a shared object, so exit on error for proper error
+   reporting.  */
+static void
+compare_vsyms_0 (void *libc_handle, const char *name, const char *version,
+                 bool *pfound)
+{
+  void *dlvsym_address = dlvsym (libc_handle, name, version);
+  void *libc_dlvsym_address
+    = __libc_dlvsym (libc_handle, name, version);
+  if (dlvsym_address != libc_dlvsym_address)
+    FAIL_EXIT1 ("%s@%s mismatch: %p != %p",
+                name, version, dlvsym_address, libc_dlvsym_address);
+  if (dlvsym_address != NULL)
+    *pfound = true;
+}
+
+
+/* Run consistency check for versioned symbol NAME at multiple symbol
+   version.  */
+static void
+compare_vsyms_1 (void *libc_handle, const char *name)
+{
+  bool found = false;
+
+  /* Historic versions which do not follow the usual GLIBC_2.Y
+     pattern, to increase test coverage.  Not all architectures have
+     those, but probing additional versions does not hurt.  */
+  static const char special_versions[][12] =
+    {
+      "GLIBC_2.1.1",
+      "GLIBC_2.1.2",
+      "GLIBC_2.1.3",
+      "GLIBC_2.1.4",
+      "GLIBC_2.2.1",
+      "GLIBC_2.2.2",
+      "GLIBC_2.2.3",
+      "GLIBC_2.2.4",
+      "GLIBC_2.2.5",
+      "GLIBC_2.2.6",
+      "GLIBC_2.3.2",
+      "GLIBC_2.3.3",
+      "GLIBC_2.3.4",
+    };
+  for (int i = 0; i < array_length (special_versions); ++i)
+    compare_vsyms_0 (libc_handle, name, special_versions[i], &found);
+
+  /* Iterate to an out-of-range version, to cover some unused symbols
+     as well.  */
+  for (int minor_version = 0; minor_version <= __GLIBC_MINOR__ + 2;
+       ++minor_version)
+    {
+      char version[30];
+      snprintf (version, sizeof (version), "GLIBC_%d.%d",
+                __GLIBC__, minor_version);
+      compare_vsyms_0 (libc_handle, name, version, &found);
+    }
+
+  if (!found)
+    FAIL_EXIT1 ("symbol %s not found at any version", name);
+}
+
+/* Run consistency checks for various symbols which usually have
+   multiple versions.  */
+static void
+compare_vsyms (void)
+{
+  /* The minor version loop in compare_vsyms_1 needs updating in case
+     we ever switch to glibc 3.0.  */
+  if (__GLIBC__ != 2)
+    FAIL_EXIT1 ("unexpected glibc major version: %d", __GLIBC__);
+
+  /* __libc_dlvsym does not recognize the special RTLD_* handles, so
+     obtain an explicit handle for libc.so.  */
+  void *libc_handle = xdlopen (LIBC_SO, RTLD_LAZY | RTLD_NOLOAD);
+
+  compare_vsyms_1 (libc_handle, "_sys_errlist");
+  compare_vsyms_1 (libc_handle, "_sys_siglist");
+  compare_vsyms_1 (libc_handle, "quick_exit");
+
+  xdlclose (libc_handle);
+}
index 526086f1a09c9662c9d303dd99df761db4ec6814..12ef913e1965d57117e76fdcdb1bd14d9c35ce07 100644 (file)
@@ -35,9 +35,11 @@ extern char **__libc_argv attribute_hidden;
   __libc_dlopen_mode (name, RTLD_LAZY | __RTLD_DLOPEN)
 extern void *__libc_dlopen_mode  (const char *__name, int __mode);
 extern void *__libc_dlsym   (void *__map, const char *__name);
+extern void *__libc_dlvsym (void *map, const char *name, const char *version);
 extern int   __libc_dlclose (void *__map);
 libc_hidden_proto (__libc_dlopen_mode)
 libc_hidden_proto (__libc_dlsym)
+libc_hidden_proto (__libc_dlvsym)
 libc_hidden_proto (__libc_dlclose)
 
 /* Locate shared object containing the given address.  */