This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: The future of static dlopen


On 12/16/2017 05:28 AM, Florian Weimer wrote:
> 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.

They are closely related only in that the static dlopen can be considered
as a form of a namespace. The objects opened by the static dlopen are
isolated from the static application linkages. Some of the problems are
the same, some are not.
 
> 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.

We don't know if that is the best solution for what our users want.

* Allowing different dynamic loaders provides better isolation.
  - Would require a loader<->loader API.
  - Even better LD_AUDIT isolation.

* Allowing different dynamic loaders lets you load newer libraries
  than you can possibly support.
  - Load libraries in a chroot/container that may require a newer
    ld.so (so long as the new ld.so supports the loader<->loader API).

Your suggestion is the simplest solution though, which is to move any
needed features into the parent ld.so, and always assure your outer
process uses the latest ld.so.
 
> 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).

There is an outer ld.so, but it's linked *into* the application.
 
> 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:

The design of these hooks is to bridge the static ld.so into the
inner dynamic namespace, and effect what happens with dlmopen, having
just one dynamic loader.
 
> 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).

What harm is caused by this? Could you expand on this a bit?

> 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).

Correct, there is *no* stable ABI, you must always run your static
binary (that uses dlopen) with the *exact* matching glibc you built with.
You cannot upgrade glibc and expect static binaries using dlopen to continue
to work. This is a known limitation and we express it very clearly.
 
> 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).

Correct.
 
> 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.

Yes, the implementation of static dlopen has some rough edges.

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

Right.

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

The same system would let dlmopen use a distinct ld.so, and this would
allow you to chain-load a newer loader in userspace, and "step into"
a newer runtime, do something, and then "step out" and destroy the
namespace you created. This could let a parent process load newer-than-you
plugins that require completely new runtimes.

Notes:
"A Multi-User Virtual Machine"
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.61.5598&rep=rep1&type=pdf
- Uses dlmopen/LD_AUDIT.

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

Sure, but I think it might be simpler to do what you suggest below :-)

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

None except to support NSS. The point of a static executable is not
to have *any* dependencies.

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

Yes, I think we *could* remove support for static dlopen if you could
solve the NSS issues.

It would be easiest to have a proxy process to handle these requests
for you... such a proxy process could be a proxy thread instead?
As you suggest earlier have the kernel start a new tid, and map into
your VMA a new dynamic executable that you can access and call into
for services?

At this point the kernel will just reject such patches saying it could
be implemented completely in the static executable e.g. map in a new
ld.so and bootstrap a new runtime properly.

The difficulty is that NSS plugins need to do a lot to interface with
their respective service providers.

-- 
Cheers,
Carlos.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]