summaryrefslogtreecommitdiff
path: root/elf
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2018-01-04 18:46:17 +0100
committerFlorian Weimer <fweimer@redhat.com>2018-01-04 18:46:24 +0100
commit82eef55f8fad3e00c53050de5d6ebea08df488b3 (patch)
treee0e1ed86b2296703182789d7f5ea607bf066ffe4 /elf
parent7abf02f3ee7627d617def77d0d6e7bea2da8d98c (diff)
elf: Support dlvsym within libc.so
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.
Diffstat (limited to 'elf')
-rw-r--r--elf/Makefile16
-rw-r--r--elf/Versions4
-rw-r--r--elf/dl-libc.c73
-rw-r--r--elf/tst-libc_dlvsym-dso.c25
-rw-r--r--elf/tst-libc_dlvsym-static.c32
-rw-r--r--elf/tst-libc_dlvsym.c34
-rw-r--r--elf/tst-libc_dlvsym.h125
7 files changed, 303 insertions, 6 deletions
diff --git a/elf/Makefile b/elf/Makefile
index 8967ac2685..2a432d8bee 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -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
diff --git a/elf/Versions b/elf/Versions
index 79ffaf73d2..3b09901f6c 100644
--- a/elf/Versions
+++ b/elf/Versions
@@ -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;
diff --git a/elf/dl-libc.c b/elf/dl-libc.c
index c6818e0313..fc01f5514d 100644
--- a/elf/dl-libc.c
+++ b/elf/dl-libc.c
@@ -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)
{
@@ -99,6 +109,18 @@ do_dlsym (void *ptr)
}
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)
{
GLRO(dl_close) ((struct link_map *) 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
index 0000000000..d74c1ead61
--- /dev/null
+++ b/elf/tst-libc_dlvsym-dso.c
@@ -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
index 0000000000..955f28d754
--- /dev/null
+++ b/elf/tst-libc_dlvsym-static.c
@@ -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
index 0000000000..864db4ee6a
--- /dev/null
+++ b/elf/tst-libc_dlvsym.c
@@ -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
index 0000000000..6b1d8d5b3a
--- /dev/null
+++ b/elf/tst-libc_dlvsym.h
@@ -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);
+}