[RFC][PATCH v12 5/8] Implement dlmopen RTLD_SHARED flag (bug 22745)
Adhemerval Zanella
adhemerval.zanella@linaro.org
Mon Aug 9 19:07:41 GMT 2021
On 08/07/2021 13:32, Vivek Das Mohapatra via Libc-alpha wrote:
> This flag will instruct dlmopen to create a shared object present
> in the main namespace and accessible from the selected namespace
> when supplied in the MODE argument.
>
> include/link.h: Update the link_map struct to allow proxies
>
> We already have an l_real pointer, used for a similar purpose by
> the linker for copies of ld.so in secondary namespaces. Update its
> documentation and add a bitfield to indicate when link_map entry
> is a proxy.
>
> elf/dl-object.c: Implement a helper function to proxy link_map entries
>
> Provides the minimal functionality needed to take an existing
> link_map entry and create a proxy for it in the specified namespace.
>
> elf/dl-load.c, elf/dl-open.c: Implement RTLD_SHARED dlmopen proxying
>
> This uses the new infrastructure to implement RTLD_SHARED object
> proxying via dlmopen: Instead of opening the specified object in
> the requested namespace we open it in the main namespace (if it
> is not already present there) and proxy it to the destination.
>
> The following rules apply:
>
> If a proxy of the object is already present in the requested namespace,
> we simply return it (with an incremented direct-open count).
>
> If the object is already present in the requested namespace, a dl
> error is signalled, since we cannot satisfy the user's request.
>
> Proxies are never created in the main namespace: RTLD_SHARED has no
> effect when the requested namespace is LM_ID_BASE.
>
> elf/dl-fini.c: Handle proxy link_maps in the shutdown path (bug 22745)
>
> When cleaning up before exit we should not call destructors or
> otherwise free [most of] the contents of proxied link_map entries
> since they share [most of] their contents with the LM_ID_BASE
> objects to which they point.
>
> elf/dl-init.c: Skip proxy link_map entries in dl init path
>
> Proxies should not trigger calls to DT_INIT constructors since they're
> just shims that point to the real, already loaded and initialised, objects.
>
> elf/dl-open.c: Skip libc init if namespace has no libc map
>
> Secondary namespaces which share their libc mapping with the main
> namespace cannot (and should not) have _dl_call_libc_early_init
> called for them by dl_open_worker.
>
> elf/dl-open.c: When creating a proxy check NS 0 libc map
>
> The libc_already_loaded check normally considers the libc_map entry
> in GL(dl_ns)[args->nsid].libc_map.
>
> This is not correct for proxies, which use the libc_map from
> the default namespace (as proxies are dummy entries that point
> to the base namespace via their l_real members).
>
> elf/dl-load.c, dl-open.c: Compare DSOs by file ID & check DF_GNU_1_UNIQUE
>
> If _dl_map_object_from_fd finds that a DSO it was asked to
> load into a non-base namespace is already loaded (into the
> main namespace) and is flagged DF_GNU_1_UNIQUE then it should
> return that DSO's link map entry.
>
> In such cases _dl_open_worker must notice that this has
> happened and continue down the link map proxy generation
> path instead of normal link map entry preparation.
>
> elf/dl-load.c: error if RTLD_SHARED = DF_GNU_1_UNIQUE semantics violated
>
> elf/dl-open.c: Use search helper to find preloaded DT_GNU_UNIQUE DSOs
>
> If a DSO already exists (with the same name) in the base namespace
> and it is flagged DT_GNU_UNIQUE then we should behave as if a proxy
> had been requested.
>
> elf/dl-load.c: When loading DSOs in other namespaces check DT_GNU_UNIQUE
>
> If a DSO has not already been loaded and the target is not the main
> namespace then we must check to see if it's been DT_GNU_UNIQUE tagged
> and load it into the main namespace instead.
>
> dl_open_worker has alread been modified to notice the discrepancy
> between the request and the result in such cases, and will set up
> a proxy in the target namespace.
>
> elf/dl-load.c: Suppress audit calls when a (new) namespace is empty
>
> When preparing an RTLD_SHARED proxy in a new namespace
> it is possible for the target namespace to be empty:
>
> This can happen for RTLD_SHARED + LM_ID_NEWLM.
>
> The audit infrastructure should not be invoked at this
> point (as there's nothing there to audit yet).
>
> bits/dlfcn.h, elf/dl-load.c, elf/dl-open.c, elf/rtld.c,
> sysdeps/mips/bits/dlfcn.h:
> Suppress inter-namespace DSO sharing for audit libraries
>
> Audit libraries should not participate in DSO sharing: In
> particular libraries tagged with DF_GNU_1_UNIQUE should not
> be shared between the audit namespace and any others - they
> should get their own copy.
>
> This is signalled to the loader code by passing the RTLD_ISOLATE
> flag from the relevant entry point in the dl modes argument.
>
> elf/dl-sym.c: dlsym, dlvsym must be able to resolve symbols via proxies
Patch look good in general, but I have some comments and improvements below.
The main change is to remove the 'has_gnu_unique' function and move the code
on 'open_verify()'.
There are also multiple indentation mismatches, where the used one differs
from what the current files uses.
> ---
> bits/dlfcn.h | 10 ++
> elf/dl-close.c | 43 +++++----
> elf/dl-deps.c | 17 ++++
> elf/dl-fini.c | 6 +-
> elf/dl-init.c | 4 +-
> elf/dl-load.c | 181 +++++++++++++++++++++++++++++++++----
> elf/dl-lookup.c | 26 +++++-
> elf/dl-object.c | 78 ++++++++++++++++
> elf/dl-open.c | 121 +++++++++++++++++++++++--
> elf/dl-sym.c | 14 +++
> elf/rtld.c | 2 +-
> include/link.h | 6 +-
> sysdeps/generic/ldsodefs.h | 7 ++
> sysdeps/mips/bits/dlfcn.h | 10 ++
> 14 files changed, 473 insertions(+), 52 deletions(-)
>
> diff --git a/bits/dlfcn.h b/bits/dlfcn.h
> index f3bc63e958..1366cb3546 100644
> --- a/bits/dlfcn.h
> +++ b/bits/dlfcn.h
> @@ -32,6 +32,16 @@
> visible as if the object were linked directly into the program. */
> #define RTLD_GLOBAL 0x00100
>
> +/* If the following bit is set in the MODE argument to dlmopen
> + then the target object is loaded into the main namespace (if
> + it is not already there) and a shallow copy (proxy) is placed
> + in the target namespace: This allows multiple namespaces to
I think you meant a period instead of a ':' here.
> + share a single instance of a DSO. */
> +#define RTLD_SHARED 0x00080
> +
> +/* Suppress RTLD_SHARED and/or DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE */
> +#define RTLD_ISOLATE 0x00040
> +
> /* Unix98 demands the following flag which is the inverse to RTLD_GLOBAL.
> The implementation does this by default and so we can define the
> value to zero. */
> diff --git a/elf/dl-close.c b/elf/dl-close.c
> index 3720e47dd1..bcdddc75c5 100644
> --- a/elf/dl-close.c
> +++ b/elf/dl-close.c
> @@ -284,8 +284,9 @@ _dl_close_worker (struct link_map *map, bool force)
>
> /* Call its termination function. Do not do it for
> half-cooked objects. Temporarily disable exception
> - handling, so that errors are fatal. */
> - if (imap->l_init_called)
> + handling, so that errors are fatal.
> + Proxies should never have this flag set, but we double check. */
> + if (imap->l_init_called && !imap->l_proxy)
> {
> /* When debugging print a message first. */
> if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS,
Ok.
> @@ -361,7 +362,9 @@ _dl_close_worker (struct link_map *map, bool force)
> one for the terminating NULL pointer. */
> size_t remain = (new_list != NULL) + 1;
> bool removed_any = false;
> - for (size_t cnt = 0; imap->l_scope[cnt] != NULL; ++cnt)
> + for (size_t cnt = 0;
> + imap->l_scope && imap->l_scope[cnt] != NULL;
> + ++cnt)
> /* This relies on l_scope[] entries being always set either
> to its own l_symbolic_searchlist address, or some map's
> l_searchlist address. */
Follow the already set indentation (using tabs instead of space).
> @@ -689,8 +692,10 @@ _dl_close_worker (struct link_map *map, bool force)
>
> /* We can unmap all the maps at once. We determined the
> start address and length when we loaded the object and
> - the `munmap' call does the rest. */
> - DL_UNMAP (imap);
> + the `munmap' call does the rest. Proxies do not have
> + any segments of their own to unmap. */
> + if (!imap->l_proxy)
> + DL_UNMAP (imap);
>
> /* Finally, unlink the data structure and free it. */
> #if DL_NNS == 1
Same as before
> @@ -730,19 +735,23 @@ _dl_close_worker (struct link_map *map, bool force)
> _dl_debug_printf ("\nfile=%s [%lu]; destroying link map\n",
> imap->l_name, imap->l_ns);
>
> - /* This name always is allocated. */
> - free (imap->l_name);
> - /* Remove the list with all the names of the shared object. */
> + /* Skip structures borrowed by proxies from the real map. */
> + if (!imap->l_proxy)
> + {
> + /* This name always is allocated. */
> + free (imap->l_name);
> + /* Remove the list with all the names of the shared object. */
>
> - struct libname_list *lnp = imap->l_libname;
> - do
> - {
> - struct libname_list *this = lnp;
> - lnp = lnp->next;
> - if (!this->dont_free)
> - free (this);
> - }
> - while (lnp != NULL);
> + struct libname_list *lnp = imap->l_libname;
> + do
> + {
> + struct libname_list *this = lnp;
> + lnp = lnp->next;
> + if (!this->dont_free)
> + free (this);
> + }
> + while (lnp != NULL);
> + }
>
> /* Remove the searchlists. */
> free (imap->l_initfini);
Ok (module the indentation).
> diff --git a/elf/dl-deps.c b/elf/dl-deps.c
> index 087a49b212..b06134c512 100644
> --- a/elf/dl-deps.c
> +++ b/elf/dl-deps.c
> @@ -66,6 +66,23 @@ openaux (void *a)
> ? lt_library : args->map->l_type),
> args->trace_mode, args->open_mode,
> args->map->l_ns);
> +
> + /* This implies that we need to prepare a proxy in the target namespace. */
> + if (__glibc_unlikely (args->map->l_ns != LM_ID_BASE &&
> + args->aux->l_ns == LM_ID_BASE))
> + {
> + if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
> + _dl_debug_printf ("need proxy for file=%s [%lu]; initstate=%ld\n\n",
> + args->aux->l_name, args->aux->l_ns,
> + (long int)args->aux->l_real->l_relocated);
There is no need to cast here, since the type of bitfiels will be 'int'.
> +
> + args->aux = _dl_new_proxy (args->aux, args->open_mode, args->map->l_ns);
> +
> + if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
> + _dl_debug_printf ("proxying dependency=%s [%lu]; direct_opencount=%u\n\n",
> + args->aux->l_name, args->aux->l_ns,
> + args->aux->l_direct_opencount);
> + }
> }
>
> static ptrdiff_t
Ok.
> diff --git a/elf/dl-fini.c b/elf/dl-fini.c
> index 6dbdfe4b3e..10194488bb 100644
> --- a/elf/dl-fini.c
> +++ b/elf/dl-fini.c
> @@ -73,7 +73,7 @@ _dl_fini (void)
> assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);
> for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
> /* Do not handle ld.so in secondary namespaces. */
> - if (l == l->l_real)
> + if (l == l->l_real || l->l_proxy)
> {
> assert (i < nloaded);
>
> @@ -111,7 +111,9 @@ _dl_fini (void)
> {
> struct link_map *l = maps[i];
>
> - if (l->l_init_called)
> + /* Do not call fini functions via proxies, or for
> + objects which are not marked as initialised. */
> + if (l->l_init_called && !l->l_proxy)
> {
> /* Make sure nothing happens if we are called twice. */
> l->l_init_called = 0;
Ok.
> diff --git a/elf/dl-init.c b/elf/dl-init.c
> index f924d26642..00ce8bb9d6 100644
> --- a/elf/dl-init.c
> +++ b/elf/dl-init.c
> @@ -30,8 +30,8 @@ call_init (struct link_map *l, int argc, char **argv, char **env)
> need relocation, and neither do proxy objects.) */
> assert (l->l_real->l_relocated || l->l_real->l_type == lt_executable);
>
> - if (l->l_init_called)
> - /* This object is all done. */
> + if (l->l_init_called || l->l_proxy)
> + /* This object is all done, or a proxy (and therefore initless). */
> return;
>
> /* Avoid handling this constructor again in case we have a circular
Ok.
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> index 050c64135a..19b8eb64bc 100644
> --- a/elf/dl-load.c
> +++ b/elf/dl-load.c
> @@ -854,6 +854,53 @@ _dl_init_paths (const char *llp, const char *source,
> __rtld_env_path_list.dirs = (void *) -1;
> }
>
> +static bool
> +has_gnu_unique (int fd, const ElfW(Ehdr) *header, const ElfW(Phdr) *phdr)
> +{
> + bool unique = false;
> + const ElfW(Phdr) *ph;
> +
> + for (ph = phdr; ph < &phdr[header->e_phnum]; ++ph)
> + {
> + off64_t end;
> + off64_t pos;
> + ssize_t bytes = -1;
> + ElfW(Dyn) entry = { .d_tag = 0, .d_un.d_val = 0 };
> +
> + switch (ph->p_type)
> + {
> + case PT_DYNAMIC:
> + pos = ph->p_offset;
> + end = pos + ph->p_filesz;
> +
> + while (pos < end)
> + {
> + bytes = __pread64_nocancel (fd, &entry, sizeof (ElfW(Dyn)), pos);
Line too long.
> +
> + if (__glibc_unlikely (bytes != sizeof (ElfW(Dyn))))
> + goto done;
We should throw a "cannot read file data" for this case instead of use the default
LM_ID_BASE.
> +
> + pos += bytes;
> +
> + switch (entry.d_tag)
> + {
> + case DT_GNU_FLAGS_1:
> + unique = (entry.d_un.d_val & DF_GNU_1_UNIQUE);
> + goto done;
Just use 'return' here and remove the goto.
> + break;
> +
> + case DT_NULL:
> + goto done;
> + break;
> + }
> + }
> + break;
> + }
> + }
> +
> + done:
> + return unique;
> +}
>
I think there is no need to read the program headers again to check if the has
the gnu unique flag since on open_verify we already do it for ".note.ABI-tag".
Instead I think we should set a flag on open_verify() and use it later.
> /* Process PT_GNU_PROPERTY program header PH in module L after
> PT_LOAD segments are mapped. Only one NT_GNU_PROPERTY_TYPE_0
> @@ -1037,6 +1084,32 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
> }
> #endif
>
> + /* DSOs in the main namespace which are flagged DF_GNU_1_UNIQUE should only
> + be opened into the main namespace. Other namespaces should only get
Double space after period.
> + proxies. */
> + if (__glibc_unlikely ((nsid != LM_ID_BASE) && !(mode & RTLD_ISOLATE)))
> + {
> + /* Check base ns to see if the name matched another already loaded. */
> + for (l = GL(dl_ns)[LM_ID_BASE]._ns_loaded; l != NULL; l = l->l_next)
> + if (!l->l_removed && _dl_file_id_match_p (&l->l_file_id, &id))
> + {
> + if (!(l->l_gnu_flags_1 & DF_GNU_1_UNIQUE))
> + continue;
> +
> + /* Already loaded. Bump its reference count and return it. */
> + __close_nocancel (fd);
> +
> + /* If the name is not listed for this object add it. */
> + free (realname);
> + add_name_to_object (l, name);
> +
> + /* NOTE: It is important that our caller picks up on the fact
> + that we have NOT returned an object in the requested namespace
> + and handles the proxying correctly. */
> + return l;
> + }
> + }
> +
> if (mode & RTLD_NOLOAD)
> {
> /* We are not supposed to load the object unless it is already
Ok.
> @@ -1062,8 +1135,11 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
> && __glibc_unlikely (GLRO(dl_naudit) > 0))
> {
> struct link_map *head = GL(dl_ns)[nsid]._ns_loaded;
> - /* Do not call the functions for any auditing object. */
> - if (head->l_auditing == 0)
> + /* Do not call the functions for any auditing object.
> + Do not try to call auditing functions if the namespace
> + is currently empty. This can hapen when opening the first
> + DSO in a new namespace. */
> + if ((head != NULL) && (head->l_auditing == 0))
Use '!head->l_auditing" to be consistent with other cases.
> {
> struct audit_ifaces *afct = GLRO(dl_audit);
> for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
> @@ -1089,6 +1165,32 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
> else
> assert (r->r_state == RT_ADD);
>
> + /* Load the ELF header using preallocated struct space if it's big enough. */
> + maplength = header->e_phnum * sizeof (ElfW(Phdr));
> + if (header->e_phoff + maplength <= (size_t) fbp->len)
> + phdr = (void *) (fbp->buf + header->e_phoff);
> + else
> + {
> + phdr = alloca (maplength);
> + if ((size_t) __pread64_nocancel (fd, (void *) phdr, maplength,
> + header->e_phoff) != maplength)
> + {
> + errstring = N_("cannot read file data");
> + goto lose_errno;
> + }
> + }
> +
Ok, you are moving the code below to check if DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE.
> + /* We need to check for DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE before we start
> + initialising any namespace dependent metatada. */
> + if (__glibc_unlikely ((nsid != LM_ID_BASE) && !(mode & RTLD_ISOLATE)))
> + {
> + /* Target DSO is flagged as unique: Make sure it gets loaded into
> + the base namespace. It is up to our caller to generate a proxy
> + in the target nsid. */
> + if (has_gnu_unique (fd, header, phdr))
> + nsid = LM_ID_BASE;
> + }
> +
> /* Enter the new object in the list of loaded objects. */
> l = _dl_new_object (realname, name, l_type, loader, mode, nsid);
> if (__glibc_unlikely (l == NULL))
> @@ -1106,20 +1208,6 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
> type = header->e_type;
> l->l_phnum = header->e_phnum;
>
> - maplength = header->e_phnum * sizeof (ElfW(Phdr));
> - if (header->e_phoff + maplength <= (size_t) fbp->len)
> - phdr = (void *) (fbp->buf + header->e_phoff);
> - else
> - {
> - phdr = alloca (maplength);
> - if ((size_t) __pread64_nocancel (fd, (void *) phdr, maplength,
> - header->e_phoff) != maplength)
> - {
> - errstring = N_("cannot read file data");
> - goto lose_errno;
> - }
> - }
> -
> /* On most platforms presume that PT_GNU_STACK is absent and the stack is
> * executable. Other platforms default to a nonexecutable stack and don't
> * need PT_GNU_STACK to do so. */
> @@ -2027,6 +2115,37 @@ open_path (const char *name, size_t namelen, int mode,
> return -1;
> }
>
Ok.
> +/* Search for a link map proxy in the given namespace by name.
> + Consider it to be an error if the found object is not a proxy. */
> +struct link_map *
> +_dl_find_proxy (Lmid_t nsid, const char *name)
> +{
> + struct link_map *l;
> +
> + for (l = GL(dl_ns)[nsid]._ns_loaded; l != NULL; l = l->l_next)
> + {
> + if (__glibc_unlikely ((l->l_faked | l->l_removed) != 0))
> + continue;
> +
> + if (!_dl_name_match_p (name, l))
> + continue;
> +
> + /* We have a match - stop searching. */
> + break;
> + }
> +
> + if (l != NULL)
> + {
> + if (l->l_proxy)
> + return l;
> +
> + _dl_signal_error (EEXIST, name, NULL,
> + N_("object cannot be demoted to a proxy"));
> + }
> +
> + return NULL;
> +}
> +
> /* Search for a shared object in a given namespace. */
> struct link_map *
> _dl_find_dso (const char *name, Lmid_t nsid)
Ok.
> @@ -2080,12 +2199,42 @@ _dl_map_object (struct link_map *loader, const char *name,
>
> assert (nsid >= 0);
> assert (nsid < GL(dl_nns));
> + assert (!((mode & RTLD_ISOLATE) && (mode & RTLD_SHARED)));
> +
> +#ifdef SHARED
> + /* Only need to do proxy checks if 'nsid' is not LM_ID_BASE. */
> + if (__glibc_unlikely ((mode & RTLD_SHARED) && (nsid != LM_ID_BASE)))
> + {
> + /* Search the namespace in case the object is already proxied. */
> + l = _dl_find_proxy (nsid, name);
> + if (l != NULL)
> + return l;
> +
> + /* Further searches should be in the base ns: We will proxy the
> + resulting object in dl_open_worker *after* it is initialised. */
> + nsid = LM_ID_BASE;
> + }
> +#endif
>
> /* Look for this name among those already loaded. */
> l = _dl_find_dso (name, nsid);
>
> if (l != NULL)
> {
> +#ifdef SHARED
> + /* If we are trying to load a DF_GNU_1_UNIQUE flagged DSO which WAS
> + already opened in the target NS but with RTLD_ISOLATE so it WAS NOT
> + created as a proxy we need to error out since we cannot satisfy the
> + DF_GNU_1_UNIQUE is-equivalent-to RTLD_SHARED semantics. */
> + if (!(mode & RTLD_ISOLATE) &&
> + (l->l_ns != LM_ID_BASE) &&
> + (l->l_gnu_flags_1 & DF_GNU_1_UNIQUE) &&
> + !l->l_proxy)
> + {
> + _dl_signal_error (EEXIST, name, NULL,
> + N_("object cannot be demoted to a proxy"));
> + }
> +#endif
> return l;
> }
>
Ok. Do we have a test for it?
> diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
> index eea217eb28..42effd8e4d 100644
> --- a/elf/dl-lookup.c
> +++ b/elf/dl-lookup.c
> @@ -24,6 +24,7 @@
> #include <ldsodefs.h>
> #include <dl-hash.h>
> #include <dl-machine.h>
> +#include <dl-dst.h>
> #include <sysdep-cancel.h>
> #include <libc-lock.h>
> #include <tls.h>
> @@ -917,11 +918,28 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
> }
> }
>
> + /* Found a candidate symbol but it resides in the base namespace
> + BUT we are in another namespace. Therefore we want a proxy
> + (which should already have been created by this point. */
> + if (__glibc_likely ((current_value.m != NULL) &&
> + !(IS_RTLD (current_value.m))))
> + if (__glibc_unlikely (undef_map->l_ns != LM_ID_BASE &&
> + current_value.m->l_ns == LM_ID_BASE))
> + {
I think it would be simpler to just write the comparison without trying to be too clever
with branch prediction:
if (current_value.m != NULL && !IS_RTLD (current_value.m)
&& undef_map->l_ns != LM_ID_BASE && current_value.m->l_name == LM_ID_BASE)
> + struct link_map *proxy = NULL;
> + proxy = _dl_find_proxy (undef_map->l_ns, current_value.m->l_name);
Just attribute the proxy directly:
struct link_map *proxy = _dl_find_proxy (undef_map->l_ns,
current_value.m->l_name);
> +
> + if (proxy != NULL)
> + current_value.m = proxy;
> + else
> + _dl_debug_printf ("Failed to find proxy for %s\n", current_value.m->l_name);
All _dl_debug_printf() call are only enable if uses does set LD_DEBUG, we
should do the same here:
else if (GLRO(dl_debug_mask) & DL_DEBUG_SYMBOLS)
_dl_debug_printf ("Failed to find proxy for %s\n", current_value.m->l_name);
> + }
> +
> /* We have to check whether this would bind UNDEF_MAP to an object
> in the global scope which was dynamically loaded. In this case
> we have to prevent the latter from being unloaded unless the
> UNDEF_MAP object is also unloaded. */
> - if (__glibc_unlikely (current_value.m->l_type == lt_loaded)
> + if (__glibc_unlikely (current_value.m->l_real->l_type == lt_loaded)
> /* Don't do this for explicit lookups as opposed to implicit
> runtime lookups. */
> && (flags & DL_LOOKUP_ADD_DEPENDENCY) != 0
Ok.
> @@ -935,8 +953,8 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
> version, type_class, flags, skip_map);
>
> /* The object is used. */
> - if (__glibc_unlikely (current_value.m->l_used == 0))
> - current_value.m->l_used = 1;
> + if (__glibc_unlikely (current_value.m->l_real->l_used == 0))
> + current_value.m->l_real->l_used = 1;
>
> if (__glibc_unlikely (GLRO(dl_debug_mask)
> & (DL_DEBUG_BINDINGS|DL_DEBUG_PRELINK)))
> @@ -944,7 +962,7 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
> ¤t_value, version, type_class, protected);
>
> *ref = current_value.s;
> - return LOOKUP_VALUE (current_value.m);
> + return LOOKUP_VALUE (current_value.m->l_real);
> }
>
>
Ok.
> diff --git a/elf/dl-object.c b/elf/dl-object.c
> index 1875599eb2..30a05b086f 100644
> --- a/elf/dl-object.c
> +++ b/elf/dl-object.c
> @@ -21,6 +21,7 @@
> #include <stdlib.h>
> #include <unistd.h>
> #include <ldsodefs.h>
> +#include <libintl.h>
>
> #include <assert.h>
>
> @@ -50,6 +51,83 @@ _dl_add_to_namespace_list (struct link_map *new, Lmid_t nsid)
> __rtld_lock_unlock_recursive (GL(dl_load_write_lock));
> }
>
> +/* Proxy an existing link map entry into a new link map:
> + This is based on _dl_new_object, skipping the steps we know we won't need
> + because this is mostly just a shell for the l_real pointer holding the real
> + link map entry (normally l == l->l_real, but not for ld.so in non-main
> + link maps or RTLD_SHARED proxies).
> + It also flags the proxy by setting l_proxy, and sets the the no-delete
> + flag in the original if it is an lt_loaded. */
> +struct link_map *
> +_dl_new_proxy (struct link_map *old, int mode, Lmid_t nsid)
> +{
> + const char *name;
> + struct link_map *new;
> + struct libname_list *newname;
> +#ifdef SHARED
> + unsigned int na = GLRO(dl_naudit);
> +
> + if ((mode & __RTLD_OPENEXEC) != 0)
> + na = DL_NNS;
> +
> + size_t audit_space = na * sizeof (struct auditstate);
> +#else
> +# define audit_space 0
> +#endif
> +
> + name = old->l_name;
> +
> + /* Find the original link map entry if 'old' is itself a proxy. */
> + while (old != NULL && old->l_proxy)
> + old = old->l_real;
> +
> + if (old == NULL)
> + _dl_signal_error (EINVAL, name, NULL, N_("cannot proxy NULL link_map"));
> +
> + /* Object already exists in the target namespace. This should get handled
> + by dl_open_worker but just in case we get this far, handle it: */
> + if (__glibc_unlikely (old->l_ns == nsid))
> + _dl_signal_error (EEXIST, name, NULL,
> + N_("existing object cannot be demoted to a proxy"));
> +
> + /* Now duplicate as little of _dl_new_object as possible to get a
> + working proxied object in the target link map. */
> + new = (struct link_map *) calloc (sizeof (*new) + audit_space
> + + sizeof (struct link_map *)
> + + sizeof (*newname) + PATH_MAX, 1);
> +
> + if (new == NULL)
> + _dl_signal_error (ENOMEM, name, NULL,
> + N_("cannot create shared object descriptor"));
> +
> + /* Specific to the proxy. */
> + new->l_real = old;
> + new->l_proxy = 1;
> + new->l_ns = nsid;
> +
> + /* Copied from the origin. */
> + new->l_libname = old->l_libname;
> + new->l_name = old->l_name;
> + /* Proxies are considered lt_loaded if the real entry type is lt_library. */
> + new->l_type = (old->l_type == lt_library) ? lt_loaded : old->l_type;
> +
> + if (__glibc_unlikely (mode & RTLD_NODELETE))
> + new->l_flags_1 |= DF_1_NODELETE;
> +
> + /* Specific to the origin. Ideally we'd do some accounting here but
> + for now it's easier to pin the original so the proxy remains valid. */
> + if (old->l_type == lt_loaded)
> + old->l_flags_1 |= DF_1_NODELETE;
> +
> + /* Fix up the searchlist so that relocations work. */
> + _dl_map_object_deps (new, NULL, 0, 0,
> + mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT));
> +
> + /* And finally put the proxy in the target namespace. */
> + _dl_add_to_namespace_list (new, nsid);
> +
> + return new;
> +}
>
> /* Allocate a `struct link_map' for a new object being loaded,
> and enter it into the _dl_loaded list. */
Ok.
> diff --git a/elf/dl-open.c b/elf/dl-open.c
> index a066f39bd0..8eed817980 100644
> --- a/elf/dl-open.c
> +++ b/elf/dl-open.c
> @@ -488,6 +488,16 @@ dl_open_worker (void *a)
> const char *file = args->file;
> int mode = args->mode;
> struct link_map *call_map = NULL;
> + struct link_map *preloaded = NULL;
> + bool want_proxy = false;
> + bool dl_isolate = mode & RTLD_ISOLATE;
> + Lmid_t proxy_ns = LM_ID_BASE;
> +
> + /* Isolation means we should suppress all inter-namespace sharing. */
> + if (dl_isolate)
> + mode &= ~RTLD_SHARED;
> + else
> + want_proxy = mode & RTLD_SHARED;
>
> /* Determine the caller's map if necessary. This is needed in case
> we have a DST, when we don't know the namespace ID we have to put
Ok.
> @@ -512,6 +522,15 @@ dl_open_worker (void *a)
> args->nsid = call_map->l_ns;
> }
>
> + /* Now that we know the NS for sure, sanity check the mode. */
> + if (__glibc_likely (args->nsid == LM_ID_BASE) &&
> + __glibc_unlikely (mode & RTLD_SHARED))
> + {
> + args->mode &= ~RTLD_SHARED;
> + mode &= ~RTLD_SHARED;
> + want_proxy = 0;
> + }
> +
> /* The namespace ID is now known. Keep track of whether libc.so was
> already loaded, to determine whether it is necessary to call the
> early initialization routine (or clear libc_map on error). */
Ok.
> @@ -525,6 +544,24 @@ dl_open_worker (void *a)
> may not be true if this is a recursive call to dlopen. */
> _dl_debug_initialize (0, args->nsid);
>
> + /* Target Lmid is not the base and we haven't explicitly asked for a proxy:
> + We need to check for a matching DSO in the base Lmid in case it is flagged
> + DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE in which case we add RTLD_SHARED to the
> + mode and set want_proxy.
> + NOTE: RTLD_ISOLATE in the mode suppresses this behaviour. */
> + if (__glibc_unlikely (args->nsid != LM_ID_BASE) &&
> + __glibc_likely (!dl_isolate) &&
> + __glibc_likely (!want_proxy))
> + {
> + preloaded = _dl_find_dso (file, LM_ID_BASE);
> +
> + if ((preloaded != NULL) && (preloaded->l_gnu_flags_1 & DF_GNU_1_UNIQUE))
> + {
> + want_proxy = true;
> + mode |= RTLD_SHARED;
> + }
> + }
> +
> /* Load the named object. */
> struct link_map *new;
> args->map = new = _dl_map_object (call_map, file, lt_loaded, 0,
Ok.
> @@ -545,6 +582,35 @@ dl_open_worker (void *a)
> /* This object is directly loaded. */
> ++new->l_direct_opencount;
>
> + /* Proxy already existed in the target ns, nothing left to do. */
> + if (__glibc_unlikely (new->l_proxy))
> + {
> + if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
> + _dl_debug_printf ("proxied file=%s [%lu]; direct_opencount=%u\n\n",
> + new->l_name, new->l_ns, new->l_direct_opencount);
> + return;
> + }
> +
> + /* If we are trying to load a DF_GNU_1_UNIQUE flagged DSO which was
> + NOT ALREADY LOADED (or not loaded with the name we are using) then
> + _dl_map_object will have returned an instance from the main namespace.
> + We need to detect this and set up the RTLD_SHARED flags. */
> + if (__glibc_unlikely (args->nsid != LM_ID_BASE && new->l_ns == LM_ID_BASE))
> + {
> + want_proxy = RTLD_SHARED;
> + mode |= RTLD_SHARED;
> + }
> +
> + /* If we want proxy and we get this far then the entry in 'new' will
> + be in the main namespace: we should revert to the main namespace code
> + path(s), but remember the namespace we want the proxy to be in. */
> + if (__glibc_unlikely (want_proxy))
> + {
> + proxy_ns = args->nsid;
> + args->nsid = LM_ID_BASE;
> + args->libc_already_loaded = GL(dl_ns)[LM_ID_BASE].libc_map != NULL;
> + }
> +
> /* It was already open. */
> if (__glibc_unlikely (new->l_searchlist.r_list != NULL))
> {
Ok.
> @@ -576,6 +642,16 @@ dl_open_worker (void *a)
>
> assert (_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT);
>
> + if (__glibc_unlikely (want_proxy))
> + {
> + args->map = new = _dl_new_proxy (new, mode, proxy_ns);
> + ++new->l_direct_opencount;
> +
> + if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
> + _dl_debug_printf ("proxying file=%s [%lu]; direct_opencount=%u\n\n",
> + new->l_name, new->l_ns, new->l_direct_opencount);
> + }
> +
> return;
> }
>
Ok.
> @@ -586,7 +662,8 @@ dl_open_worker (void *a)
>
> /* Load that object's dependencies. */
> _dl_map_object_deps (new, NULL, 0, 0,
> - mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT));
> + mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT |
> + RTLD_ISOLATE));
>
> /* So far, so good. Now check the versions. */
> for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
Ok.
> @@ -680,7 +757,10 @@ dl_open_worker (void *a)
> {
> l = new->l_initfini[i];
>
> - if (l->l_real->l_relocated)
> + if (l->l_proxy)
> + l = l->l_real;
> +
> + if (l->l_relocated)
> continue;
>
> if (! relocation_in_progress)
Ok.
> @@ -769,16 +849,21 @@ dl_open_worker (void *a)
> namespace. */
> if (!args->libc_already_loaded)
> {
> + /* If this is a secondary (nsid != LM_ID_BASE) namespace then
> + it is POSSIBLE there's no libc_map at all - We use the one
> + shared with LM_ID_BASE instead (which MUST already be
> + initialised for us to even reach here). */
> struct link_map *libc_map = GL(dl_ns)[args->nsid].libc_map;
> #ifdef SHARED
> - bool initial = libc_map->l_ns == LM_ID_BASE;
> + bool initial = (libc_map != NULL) && (libc_map->l_real->l_ns == LM_ID_BASE);
> #else
> /* In the static case, there is only one namespace, but it
> contains a secondary libc (the primary libc is statically
> linked). */
> bool initial = false;
> #endif
> - _dl_call_libc_early_init (libc_map, initial);
> + if (libc_map != NULL)
> + _dl_call_libc_early_init (libc_map, initial);
> }
>
This is already fixed by 3908fa933a4354.
> /* Run the initializer functions of new objects. Temporarily
> @@ -799,10 +884,27 @@ dl_open_worker (void *a)
> if (mode & RTLD_GLOBAL)
> add_to_global_update (new);
>
> + if (__glibc_unlikely (want_proxy))
> + {
> + /* args->map is the return slot which the caller sees, but keep
> + the original value of new hanging around so we can see the
> + real link map entry (for logging etc). */
> + args->map = _dl_new_proxy (new, mode, proxy_ns);
> + ++args->map->l_direct_opencount;
> + }
> +
> /* Let the user know about the opencount. */
> if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
> - _dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
> - new->l_name, new->l_ns, new->l_direct_opencount);
> + {
> + _dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
> + new->l_name, new->l_ns, new->l_direct_opencount);
> +
> + if (args->map->l_proxy)
> + _dl_debug_printf ("proxying file=%s [%lu]; direct_opencount=%u\n\n",
> + args->map->l_name,
> + args->map->l_ns,
> + args->map->l_direct_opencount);
> + }
> }
>
Ok.
> void *
> @@ -891,8 +993,11 @@ no more namespaces available for dlmopen()"));
> if (__glibc_unlikely (exception.errstring != NULL))
> {
> /* Avoid keeping around a dangling reference to the libc.so link
> - map in case it has been cached in libc_map. */
> - if (!args.libc_already_loaded)
> + map in case it has been cached in libc_map.
> + If this is a secondary namespace opened with LM_ID_NEWLM and
> + WITHOUT RTLD_ISOLATE then nsid can still be -1 (LM_ID_NEWLM)
> + at this point. */
> + if (!args.libc_already_loaded && nsid >= LM_ID_BASE)
> GL(dl_ns)[nsid].libc_map = NULL;
>
> /* Remove the object from memory. It may be in an inconsistent
Ok.
> diff --git a/elf/dl-sym.c b/elf/dl-sym.c
> index fa0cce678f..966079915a 100644
> --- a/elf/dl-sym.c
> +++ b/elf/dl-sym.c
> @@ -96,6 +96,10 @@ do_sym (void *handle, const char *name, void *who,
> {
> match = _dl_sym_find_caller_link_map (caller);
>
> + /* Proxies don't contain any symbols: Need to look at the real DSO. */
> + if (__glibc_unlikely (match->l_proxy))
> + match = match->l_real;
> +
> /* Search the global scope. We have the simple case where
> we look up in the scope of an object which was part of
> the initial binary. And then the more complex part
Ok.
> @@ -140,6 +144,11 @@ RTLD_NEXT used in code not dynamically loaded"));
> }
>
> struct link_map *l = match;
> +
> + /* Proxies don't contain any symbols: Need to look at the real DSO. */
> + if (__glibc_unlikely (l->l_proxy))
> + l = l->l_real;
> +
> while (l->l_loader != NULL)
> l = l->l_loader;
>
> @@ -150,6 +159,11 @@ RTLD_NEXT used in code not dynamically loaded"));
> {
> /* Search the scope of the given object. */
> struct link_map *map = handle;
> +
> + /* Proxies don't contain any symbols: Need to look at the real DSO. */
> + if (__glibc_unlikely (map->l_proxy))
> + map = map->l_real;
> +
> result = GLRO(dl_lookup_symbol_x) (name, map, &ref, map->l_local_scope,
> vers, 0, flags, NULL);
> }
Ok.
> diff --git a/elf/rtld.c b/elf/rtld.c
> index fbbd60b446..9246200be1 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -661,7 +661,7 @@ dlmopen_doit (void *a)
> struct dlmopen_args *args = (struct dlmopen_args *) a;
> args->map = _dl_open (args->fname,
> (RTLD_LAZY | __RTLD_DLOPEN | __RTLD_AUDIT
> - | __RTLD_SECURE),
> + | __RTLD_SECURE | RTLD_ISOLATE),
> dl_main, LM_ID_NEWLM, _dl_argc, _dl_argv,
> __environ);
> }
Ok.
> diff --git a/include/link.h b/include/link.h
> index 6ed35eb808..55e0cad71d 100644
> --- a/include/link.h
> +++ b/include/link.h
> @@ -107,8 +107,9 @@ struct link_map
> They may change without notice. */
>
> /* This is an element which is only ever different from a pointer to
> - the very same copy of this type for ld.so when it is used in more
> - than one namespace. */
> + the very same copy of this type when:
> + - A shallow copy of ld.so is placed in namespaces other than LM_ID_BASE.
> + - An object is proxied into a namespace by dlmopen with RTLD_SHARED. */
> struct link_map *l_real;
>
> /* Number of the namespace this link map belongs to. */
> @@ -180,6 +181,7 @@ struct link_map
> unsigned int l_relocated:1; /* Nonzero if object's relocations done. */
> unsigned int l_init_called:1; /* Nonzero if DT_INIT function called. */
> unsigned int l_global:1; /* Nonzero if object in _dl_global_scope. */
> + unsigned int l_proxy:1; /* Nonzero if object is a shallow copy. */
> unsigned int l_reserved:2; /* Reserved for internal use. */
> unsigned int l_phdr_allocated:1; /* Nonzero if the data structure pointed
> to by `l_phdr' is allocated. */
Ok.
> diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> index 44e7097712..66cfc1deb3 100644
> --- a/sysdeps/generic/ldsodefs.h
> +++ b/sysdeps/generic/ldsodefs.h
> @@ -1015,6 +1015,11 @@ extern lookup_t _dl_lookup_symbol_x (const char *undef,
> struct link_map *skip_map)
> attribute_hidden;
>
> +/* Proxy an existing link map entry into a new link map */
> +extern struct link_map *_dl_new_proxy (struct link_map *old,
> + int mode,
> + Lmid_t nsid)
> + attribute_hidden;
>
> /* Restricted version of _dl_lookup_symbol_x. Searches MAP (and only
> MAP) for the symbol UNDEF_NAME, with GNU hash NEW_HASH (computed
> @@ -1290,6 +1295,8 @@ rtld_hidden_proto (_dl_find_dso_for_object)
> extern struct link_map *_dl_find_dso (const char *name, Lmid_t nsid);
> rtld_hidden_proto (_dl_find_dso)
>
> +extern struct link_map *_dl_find_proxy (Lmid_t nsid, const char *name);
> +rtld_hidden_proto (_dl_find_proxy)
>
> /* Initialization which is normally done by the dynamic linker. */
> extern void _dl_non_dynamic_init (void)
Ok.
> diff --git a/sysdeps/mips/bits/dlfcn.h b/sysdeps/mips/bits/dlfcn.h
> index 5cec898de3..898797f863 100644
> --- a/sysdeps/mips/bits/dlfcn.h
> +++ b/sysdeps/mips/bits/dlfcn.h
> @@ -32,6 +32,16 @@
> visible as if the object were linked directly into the program. */
> #define RTLD_GLOBAL 0x0004
>
> +/* If the following bit is set in the MODE argument to dlmopen
> + then the target object is loaded into the main namespace (if
> + it is not already there) and a shallow copy (proxy) is placed
> + in the target namespace: This allows multiple namespaces to
> + share a single instance of a DSO. */
> +#define RTLD_SHARED 0x00020
> +
> +/* Suppress RTLD_SHARED and/or DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE */
> +#define RTLD_ISOLATE 0x00040
> +
> /* Unix98 demands the following flag which is the inverse to RTLD_GLOBAL.
> The implementation does this by default and so we can define the
> value to zero. */
>
Ok.
More information about the Libc-alpha
mailing list