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.
This commit is contained in:
Florian Weimer 2018-01-04 18:46:17 +01:00
parent 7abf02f3ee
commit 82eef55f8f
9 changed files with 330 additions and 6 deletions

View File

@ -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

View 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

View 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;

View 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)
{

25
elf/tst-libc_dlvsym-dso.c Normal file
View File

@ -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 ();
}

View File

@ -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>

34
elf/tst-libc_dlvsym.c Normal file
View File

@ -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>

125
elf/tst-libc_dlvsym.h Normal file
View File

@ -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);
}

View 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. */