The future of static dlopen

Florian Weimer fweimer@redhat.com
Sat Dec 16 13:28:00 GMT 2017


Folklore has it that static dlopen and dlmopen are closely related. 
Both have an outer and inner libc, and thus share a similar problem of 
making sure that they have the same view of the process and share data 
as needed.

However, there is this code in _dl_map_object_from_fd:

   /* When loading into a namespace other than the base one we must
      avoid loading ld.so since there can only be one copy.  Ever.  */
   if (__glibc_unlikely (nsid != LM_ID_BASE)
       && (_dl_file_id_match_p (&id, &GL(dl_rtld_map).l_file_id)
	  || _dl_name_match_p (name, &GL(dl_rtld_map))))
     {
       /* This is indeed ld.so.  Create a new link_map which refers to
	 the real one for almost everything.  */
       l = _dl_new_object (realname, name, l_type, loader, mode, nsid);

So the dynamic linker is indeed shared across dlmopen namespaces.  If we 
want to share anything between libcs, we can simply do this by 
implementing it in ld.so instead.

However, this works only for dlmopen.  For static dlopen, there is no 
outer lds.so that can be shared.  Instead, a new inner ld.so is loaded 
but not initialized, leading to bugs such as bug 20802 (getauxval not 
working after static dlopen).

In fact, when the inner ld.so appears to work, it only does so because 
it is bypassed.  For dlopen from the loaded DSOs, we have two different 
mechanisms, one for libc, one for libdl, which install the non-ld.so 
implementation of dlopen into the inner libc, called 
__libc_register_dl_open_hook and __libc_register_dlfcn_hook.  These 
hooks, when active, completely replace the implementation.  Here's the 
example for dlopen:

void *
__dlopen (const char *file, int mode DL_CALLER_DECL)
{
# ifdef SHARED
   if (__glibc_unlikely (_dlfcn_hook != NULL))
     return _dlfcn_hook->dlopen (file, mode, DL_CALLER);
# endif

This is not exactly harmless because there are still crash handlers 
which call dlopen as part of the crash reporting procedure (to load the 
libgcc unwinder).  It is possible, however, to mangle those function 
pointers (although this will of course break static dlopen from existing 
binaries, but we require recompilation already as there is no stable 
ABI; see bug 20204).

Let me stress again that these hooks are *not* needed for the dlmopen 
case.  There, _rtld_global_ro is fully initialized, and a call to 
GLRO(dl_open) just works (and so would a call to the ld.so function 
through an ELF relocation).

As the getauxval bug 20802 shows, the set of hooks is currently 
incomplete.  Another example is dlvsym support from libc.so itself for 
internal use, which is missing from elf/dl-libc.c (and which I need to 
implement libidn2 support for AI_IDN).  There are probably many other 
things missing as well, e.g. bug 10652 which still lacks root cause 
analysis.

This led me to wonder if there is a more natural way of implementing 
static dlopen.  The current scheme certainly has the advantage that it 
is possible to dlopen a DSO which is not linked against libc.so and 
ld.so (basically, without DT_NEEDED) with minimal extra overhead and 
dependency on additional files.  However, I'm not sure how common that 
use case is.  Our own use of static dlopen for NSS modules does not fit 
that.

If the static-dlopen-of-statically-linked-DSO is not a useful use case 
to support, maybe we should change the static dlopen implementation to 
load ld.so first and let it handle all further dynamic linking.  We 
would have to tweak the regular entry point so that the TLS 
initialization and some other steps are skipped because the main 
executable has already done that work.  At that point, we would load 
ld.so pretty much like the kernel would load it.  After the 
initialization, the dynamic loader would work just in the way it does 
for dynamically linked binaries.

But this leads to the question: Why do this at all?  Shouldn't we 
perhaps simply tell the kernel to load the dynamic loader for us?  That 
is, create a dynamically linked executable?

Since a statically linked executable is already tied to the libc.so and 
ld.so version it was created with, what exactly is the use case for 
static dlopen?

Should we remove support for static dlopen?  And use some other 
mechanism to implement NSS for statically linked binaries?

Thanks,
Florian



More information about the Libc-alpha mailing list