[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,
>  			&current_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