[03/23] elf: Partially initialize ld.so after static dlopen (bug 20802)

Message ID 99282e8e9fb4813d741b1177256567e56da3862e.1620838411.git.fweimer@redhat.com
State Committed
Headers
Series nptl: Move almost all remaining functions into libc |

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Florian Weimer May 12, 2021, 4:57 p.m. UTC
  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).
---
 dlfcn/tststatic5.c                 | 11 ++---
 elf/Makefile                       | 11 +++--
 elf/dl-open.c                      | 17 +++++++-
 elf/rtld_static_init.c             | 56 +++++++++++++++++++++++++
 elf/tst-auxvalmod.c                | 29 +++++++++++++
 elf/tst-getauxval-static.c         | 66 ++++++++++++++++++++++++++++++
 sysdeps/generic/ldsodefs.h         |  7 ++++
 sysdeps/generic/rtld_static_init.h | 24 +++++++++++
 8 files changed, 208 insertions(+), 13 deletions(-)
 create mode 100644 elf/rtld_static_init.c
 create mode 100644 elf/tst-auxvalmod.c
 create mode 100644 elf/tst-getauxval-static.c
 create mode 100644 sysdeps/generic/rtld_static_init.h
  

Comments

Adhemerval Zanella May 13, 2021, 7:35 p.m. UTC | #1
On 12/05/2021 13:57, Florian Weimer via Libc-alpha wrote:
> 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).

LGTM, with some small nit below.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  dlfcn/tststatic5.c                 | 11 ++---
>  elf/Makefile                       | 11 +++--
>  elf/dl-open.c                      | 17 +++++++-
>  elf/rtld_static_init.c             | 56 +++++++++++++++++++++++++
>  elf/tst-auxvalmod.c                | 29 +++++++++++++
>  elf/tst-getauxval-static.c         | 66 ++++++++++++++++++++++++++++++
>  sysdeps/generic/ldsodefs.h         |  7 ++++
>  sysdeps/generic/rtld_static_init.h | 24 +++++++++++
>  8 files changed, 208 insertions(+), 13 deletions(-)
>  create mode 100644 elf/rtld_static_init.c
>  create mode 100644 elf/tst-auxvalmod.c
>  create mode 100644 elf/tst-getauxval-static.c
>  create mode 100644 sysdeps/generic/rtld_static_init.h
> 
> diff --git a/dlfcn/tststatic5.c b/dlfcn/tststatic5.c
> index 872e87fbaf..e0a50b7dcc 100644
> --- a/dlfcn/tststatic5.c
> +++ b/dlfcn/tststatic5.c
> @@ -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)
>  {

Ok.

> diff --git a/elf/Makefile b/elf/Makefile
> index 4e148e532a..5e809dbfaa 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -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)

Ok.

> diff --git a/elf/dl-open.c b/elf/dl-open.c
> index 0887fc5cc5..7e018bb44c 100644
> --- a/elf/dl-open.c
> +++ b/elf/dl-open.c
> @@ -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;
> +	(void) _dl_check_map_versions (map, 0, 0);

Why the cast here?

> +#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.  */

Ok.

> diff --git a/elf/rtld_static_init.c b/elf/rtld_static_init.c
> new file mode 100644
> index 0000000000..cd823096d6
> --- /dev/null
> +++ b/elf/rtld_static_init.c
> @@ -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.  */

This is tricky, how can we be sure about which members do not need
relocations?

> +#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);
> +}

Ok.

> diff --git a/elf/tst-auxvalmod.c b/elf/tst-auxvalmod.c
> new file mode 100644
> index 0000000000..0676d66a0c
> --- /dev/null
> +++ b/elf/tst-auxvalmod.c
> @@ -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
> +   <http://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;
> +}

Ok.

> diff --git a/elf/tst-getauxval-static.c b/elf/tst-getauxval-static.c
> new file mode 100644
> index 0000000000..1d5dc86915
> --- /dev/null
> +++ b/elf/tst-getauxval-static.c
> @@ -0,0 +1,66 @@
> +/* Test getauxval from a dynamic library after static dlopen.
> +   Copyright (C) 2016 Free Software Foundation, Inc.

Is this date correct? 

> +   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 <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 RT_RANDOM");

s/RT_RANDOM/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>

Ok.

> diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> index 8426b5cbd8..e43d6f5ecd 100644
> --- a/sysdeps/generic/ldsodefs.h
> +++ b/sysdeps/generic/ldsodefs.h
> @@ -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

Ok.

> diff --git a/sysdeps/generic/rtld_static_init.h b/sysdeps/generic/rtld_static_init.h
> new file mode 100644
> index 0000000000..3f8dde22a8
> --- /dev/null
> +++ b/sysdeps/generic/rtld_static_init.h
> @@ -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.  */
> +}
> 

Ok.
  
Florian Weimer May 13, 2021, 8:09 p.m. UTC | #2
* Adhemerval Zanella:

>> diff --git a/elf/dl-open.c b/elf/dl-open.c
>> index 0887fc5cc5..7e018bb44c 100644
>> --- a/elf/dl-open.c
>> +++ b/elf/dl-open.c
>> @@ -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;
>> +	(void) _dl_check_map_versions (map, 0, 0);
>
> Why the cast here?

I don't know, it awas in the old code.  Presumably to indicate that it's
okay to ignore the return status.

>> +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.  */
>
> This is tricky, how can we be sure about which members do not need
> relocations?

I think only initializers that have an address dependency trigger
relocations.  Anything else will evaluate to a constant and directly be
emitted by the assembler (as this is C, not C++).

>> diff --git a/elf/tst-getauxval-static.c b/elf/tst-getauxval-static.c
>> new file mode 100644
>> index 0000000000..1d5dc86915
>> --- /dev/null
>> +++ b/elf/tst-getauxval-static.c
>> @@ -0,0 +1,66 @@
>> +/* Test getauxval from a dynamic library after static dlopen.
>> +   Copyright (C) 2016 Free Software Foundation, Inc.
>
> Is this date correct?

Yes and no.  I wrote the test in 2016 (before support/ existed
apparently), but I think the recommendation is to use the year of the
commit date.  I had already fixed it locally after posting.

>> +static int
>> +do_test (void)
>> +{
>> +  unsigned long outer_random = getauxval (AT_RANDOM);
>> +  if (outer_random == 0)
>> +    FAIL_UNSUPPORTED ("getauxval does not support RT_RANDOM");
>
> s/RT_RANDOM/AT_RANDOM

Thanks, fixed

Florian
  
Adhemerval Zanella May 13, 2021, 8:11 p.m. UTC | #3
On 13/05/2021 17:09, Florian Weimer wrote:
> * Adhemerval Zanella:
> 
>>> diff --git a/elf/dl-open.c b/elf/dl-open.c
>>> index 0887fc5cc5..7e018bb44c 100644
>>> --- a/elf/dl-open.c
>>> +++ b/elf/dl-open.c
>>> @@ -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;
>>> +	(void) _dl_check_map_versions (map, 0, 0);
>>
>> Why the cast here?
> 
> I don't know, it awas in the old code.  Presumably to indicate that it's
> okay to ignore the return status.

I think it should be safer to remove it, as the current code does
(the function does not have the attribute to emit an warning when
it is ignored).

> 
>>> +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.  */
>>
>> This is tricky, how can we be sure about which members do not need
>> relocations?
> 
> I think only initializers that have an address dependency trigger
> relocations.  Anything else will evaluate to a constant and directly be
> emitted by the assembler (as this is C, not C++).

My point is how can we be used that adding a initialization on
__rtld_static_init is safe? If it requires relocations, will loader
emit and issue to warn us?
  
Florian Weimer May 13, 2021, 8:44 p.m. UTC | #4
* Adhemerval Zanella:

> On 13/05/2021 17:09, Florian Weimer wrote:
>> * Adhemerval Zanella:
>> 
>>>> diff --git a/elf/dl-open.c b/elf/dl-open.c
>>>> index 0887fc5cc5..7e018bb44c 100644
>>>> --- a/elf/dl-open.c
>>>> +++ b/elf/dl-open.c
>>>> @@ -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;
>>>> +	(void) _dl_check_map_versions (map, 0, 0);
>>>
>>> Why the cast here?
>> 
>> I don't know, it awas in the old code.  Presumably to indicate that it's
>> okay to ignore the return status.
>
> I think it should be safer to remove it, as the current code does
> (the function does not have the attribute to emit an warning when
> it is ignored).

Okay, it's gone.

>>>> +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.  */
>>>
>>> This is tricky, how can we be sure about which members do not need
>>> relocations?
>> 
>> I think only initializers that have an address dependency trigger
>> relocations.  Anything else will evaluate to a constant and directly be
>> emitted by the assembler (as this is C, not C++).
>
> My point is how can we be used that adding a initialization on
> __rtld_static_init is safe? If it requires relocations, will loader
> emit and issue to warn us?

Ahh, this isn't waht I meant.  __rtld_static_init is statically linked
(not in ld.so) and therefore itself fully relocated.  My concern is that
the relocation of the just-loaded ld.so might overwrite the
initialization in __rtld_static_init.  Not sure how to express that
better.

I'm also very confused how all this works on REL (not RELA) targets with
their in-place addends if they need a bootstrap relocation.  Does this
work because we only handle relative relocations during bootstrap
relocation, and those do never have addends (or rather, addends are
expressed differently) and are therefore idempotent?

Thanks,
Florian
  

Patch

diff --git a/dlfcn/tststatic5.c b/dlfcn/tststatic5.c
index 872e87fbaf..e0a50b7dcc 100644
--- a/dlfcn/tststatic5.c
+++ b/dlfcn/tststatic5.c
@@ -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)
 {
diff --git a/elf/Makefile b/elf/Makefile
index 4e148e532a..5e809dbfaa 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -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)
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 0887fc5cc5..7e018bb44c 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -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;
+	(void) _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.  */
diff --git a/elf/rtld_static_init.c b/elf/rtld_static_init.c
new file mode 100644
index 0000000000..cd823096d6
--- /dev/null
+++ b/elf/rtld_static_init.c
@@ -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);
+}
diff --git a/elf/tst-auxvalmod.c b/elf/tst-auxvalmod.c
new file mode 100644
index 0000000000..0676d66a0c
--- /dev/null
+++ b/elf/tst-auxvalmod.c
@@ -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
+   <http://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;
+}
diff --git a/elf/tst-getauxval-static.c b/elf/tst-getauxval-static.c
new file mode 100644
index 0000000000..1d5dc86915
--- /dev/null
+++ b/elf/tst-getauxval-static.c
@@ -0,0 +1,66 @@ 
+/* Test getauxval from a dynamic library after static dlopen.
+   Copyright (C) 2016 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 <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 RT_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>
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 8426b5cbd8..e43d6f5ecd 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -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
diff --git a/sysdeps/generic/rtld_static_init.h b/sysdeps/generic/rtld_static_init.h
new file mode 100644
index 0000000000..3f8dde22a8
--- /dev/null
+++ b/sysdeps/generic/rtld_static_init.h
@@ -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.  */
+}