elf: Partially initialize ld.so after static dlopen (bug 20802)

After static dlopen, a copy of ld.so is loaded into the inner
namespace, but that copy is not initialized at all.  Some
architectures run into serious problems as result, which is why the
_dl_var_init mechanism was invented.  With libpthread moving into
libc and parts into ld.so, more architectures impacted, so it makes
sense to switch to a generic mechanism which performs the partial
initialization.

As a result, getauxval now works after static dlopen (bug 20802).

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
This commit is contained in:
Florian Weimer 2021-05-17 09:59:14 +02:00
parent 23ce1cf35a
commit 78b31cc834
8 changed files with 208 additions and 13 deletions

View File

@ -25,14 +25,9 @@
mapped from a static executable.
On targets that support different page sizes, the kernel communicates
the size currently in use via the auxiliary vector. This vector is
available to initial startup, but not any DSOs loaded later on. As
static executables do not export their symbols a DSO cannot access
the value obtained by initial startup and the value therefore has to
be passed on to the DSO and stored within its data area explicitly.
This is performed by a call to DL_STATIC_INIT that is defined in a
target-dependent way, and that on variable page size targets stores
it in the GLRO(dl_pagesize) variable of the DSO's dynamic linker. */
the size currently in use via the auxiliary vector. The auxiliary
vector and HWCAP/HWCAP2 bits are copied across the static dlopen
boundary in __rtld_static_init. */
static int
do_test (void)
{

View File

@ -25,7 +25,7 @@ headers = elf.h bits/elfclass.h link.h bits/link.h
routines = $(all-dl-routines) dl-support dl-iteratephdr \
dl-addr dl-addr-obj enbl-secure dl-profstub \
dl-origin dl-libc dl-sym dl-sysdep dl-error \
dl-reloc-static-pie libc_early_init
dl-reloc-static-pie libc_early_init rtld_static_init
# The core dynamic linking functions are in libc for the static and
# profiled libraries.
@ -60,7 +60,7 @@ all-dl-routines = $(dl-routines) $(sysdep-dl-routines)
# But they are absent from the shared libc, because that code is in ld.so.
elide-routines.os = $(all-dl-routines) dl-support enbl-secure dl-origin \
dl-sysdep dl-exception dl-reloc-static-pie \
thread_gscope_wait
thread_gscope_wait rtld_static_init
# ld.so uses those routines, plus some special stuff for being the program
# interpreter and operating independent of libc.
@ -161,7 +161,7 @@ tests-static-normal := tst-leaks1-static tst-array1-static tst-array5-static \
tst-tlsalign-static tst-tlsalign-extern-static \
tst-linkall-static tst-env-setuid tst-env-setuid-tunables \
tst-single_threaded-static tst-single_threaded-pthread-static \
tst-dst-static
tst-dst-static tst-getauxval-static
tests-static-internal := tst-tls1-static tst-tls2-static \
tst-ptrguard1-static tst-stackguard1-static \
@ -346,6 +346,7 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
libmarkermod3-1 libmarkermod3-2 libmarkermod3-3 \
libmarkermod4-1 libmarkermod4-2 libmarkermod4-3 libmarkermod4-4 \
tst-tls20mod-bad tst-tls21mod tst-dlmopen-dlerror-mod \
tst-auxvalmod \
# Most modules build with _ISOMAC defined, but those filtered out
# depend on internal headers.
@ -1942,3 +1943,7 @@ $(objpfx)tst-tls20.out: $(objpfx)tst-tls20mod-bad.so \
$(objpfx)tst-tls21: $(libdl) $(shared-thread-library)
$(objpfx)tst-tls21.out: $(objpfx)tst-tls21mod.so
$(objpfx)tst-tls21mod.so: $(tst-tls-many-dynamic-modules:%=$(objpfx)%.so)
$(objpfx)tst-getauxval-static: $(common-objpfx)dlfcn/libdl.a
$(objpfx)tst-getauxval-static.out: $(objpfx)tst-auxvalmod.so
tst-getauxval-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)

View File

@ -35,6 +35,7 @@
#include <libc-internal.h>
#include <array_length.h>
#include <libc-early-init.h>
#include <gnu/lib-names.h>
#include <dl-dst.h>
#include <dl-prop.h>
@ -590,8 +591,20 @@ dl_open_worker (void *a)
/* So far, so good. Now check the versions. */
for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
if (new->l_searchlist.r_list[i]->l_real->l_versions == NULL)
(void) _dl_check_map_versions (new->l_searchlist.r_list[i]->l_real,
0, 0);
{
struct link_map *map = new->l_searchlist.r_list[i]->l_real;
_dl_check_map_versions (map, 0, 0);
#ifndef SHARED
/* During static dlopen, check if ld.so has been loaded.
Perform partial initialization in this case. This must
come after the symbol versioning initialization in
_dl_check_map_versions. */
if (map->l_info[DT_SONAME] != NULL
&& strcmp (((const char *) D_PTR (map, l_info[DT_STRTAB])
+ map->l_info[DT_SONAME]->d_un.d_val), LD_SO) == 0)
__rtld_static_init (map);
#endif
}
#ifdef SHARED
/* Auditing checkpoint: we have added all objects. */

56
elf/rtld_static_init.c Normal file
View File

@ -0,0 +1,56 @@
/* Partial initialization of ld.so loaded via static dlopen.
Copyright (C) 2021 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 <assert.h>
/* Very special case: This object is built into the static libc, but
must know the layout of _rtld_global_ro. */
#define SHARED
#include <ldsodefs.h>
#include <rtld_static_init.h>
void
__rtld_static_init (struct link_map *map)
{
const ElfW(Sym) *sym
= _dl_lookup_direct (map, "_rtld_global_ro",
0x9f28436a, /* dl_new_hash output. */
"GLIBC_PRIVATE",
0x0963cf85); /* _dl_elf_hash output. */
assert (sym != NULL);
struct rtld_global_ro *dl = DL_SYMBOL_ADDRESS (map, sym);
/* Perform partial initialization here. Note that this runs before
ld.so is relocated, so only members initialized without
relocations can be written here. */
#ifdef HAVE_AUX_VECTOR
extern __typeof (dl->_dl_auxv) _dl_auxv attribute_hidden;
dl->_dl_auxv = _dl_auxv;
extern __typeof (dl->_dl_clktck) _dl_clktck attribute_hidden;
dl->_dl_clktck = _dl_clktck;
#endif
extern __typeof (dl->_dl_hwcap) _dl_hwcap attribute_hidden;
dl->_dl_hwcap = _dl_hwcap;
extern __typeof (dl->_dl_hwcap2) _dl_hwcap2 attribute_hidden;
dl->_dl_hwcap2 = _dl_hwcap2;
extern __typeof (dl->_dl_pagesize) _dl_pagesize attribute_hidden;
dl->_dl_pagesize = _dl_pagesize;
__rtld_static_init_arch (map, dl);
}

29
elf/tst-auxvalmod.c Normal file
View File

@ -0,0 +1,29 @@
/* Wrapper for getauxval testing.
Copyright (C) 2021 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 <errno.h>
#include <sys/auxv.h>
unsigned long
getauxval_wrapper (unsigned long type, int *errnop)
{
errno = *errnop;
unsigned long result = getauxval (type);
*errnop = errno;
return result;
}

View File

@ -0,0 +1,66 @@
/* Test getauxval from a dynamic library after static dlopen.
Copyright (C) 2021 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 <dlfcn.h>
#include <errno.h>
#include <stdio.h>
#include <support/check.h>
#include <support/xdlfcn.h>
#include <sys/auxv.h>
unsigned long getauxval_wrapper (unsigned long type, int *errnop);
static int
do_test (void)
{
unsigned long outer_random = getauxval (AT_RANDOM);
if (outer_random == 0)
FAIL_UNSUPPORTED ("getauxval does not support AT_RANDOM");
unsigned long missing_auxv_type;
for (missing_auxv_type = AT_RANDOM + 1; ; ++missing_auxv_type)
{
errno = 0;
if (getauxval (missing_auxv_type) == 0 && errno != 0)
{
TEST_COMPARE (errno, ENOENT);
break;
}
}
printf ("info: first missing type: %lu\n", missing_auxv_type);
void *handle = xdlopen ("tst-auxvalmod.so", RTLD_LAZY);
void *ptr = xdlsym (handle, "getauxval_wrapper");
__typeof__ (getauxval_wrapper) *wrapper = ptr;
int inner_errno = 0;
unsigned long inner_random = wrapper (AT_RANDOM, &inner_errno);
TEST_COMPARE (outer_random, inner_random);
inner_errno = 0;
TEST_COMPARE (wrapper (missing_auxv_type, &inner_errno), 0);
TEST_COMPARE (inner_errno, ENOENT);
TEST_COMPARE (getauxval (AT_HWCAP), wrapper (AT_HWCAP, &inner_errno));
TEST_COMPARE (getauxval (AT_HWCAP2), wrapper (AT_HWCAP2, &inner_errno));
xdlclose (handle);
return 0;
}
#include <support/test-driver.c>

View File

@ -1313,6 +1313,13 @@ dl_init_static_tls (struct link_map *map)
#endif
}
#ifndef SHARED
/* Called before relocating ld.so during static dlopen. This can be
used to partly initialize the dormant ld.so copy in the static
dlopen namespace. */
void __rtld_static_init (struct link_map *map) attribute_hidden;
#endif
/* Return true if the ld.so copy in this namespace is actually active
and working. If false, the dl_open/dlfcn hooks have to be used to
call into the outer dynamic linker (which happens after static

View File

@ -0,0 +1,24 @@
/* Partial initialization of ld.so loaded via static dlopen. Generic helper.
Copyright (C) 2021 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/>. */
static inline void
__rtld_static_init_arch (struct link_map *map, struct rtld_global_ro *dl)
{
/* The generic helper does not perform any additional
initialization. */
}