public inbox for glibc-cvs@sourceware.org
help / color / mirror / Atom feed
* [glibc/azanella/rtld-shared] elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
@ 2021-08-09 20:26 Adhemerval Zanella
  0 siblings, 0 replies; 6+ messages in thread
From: Adhemerval Zanella @ 2021-08-09 20:26 UTC (permalink / raw)
  To: glibc-cvs

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=fe22e2fa0cc0b02745042cd5260b9b346e022431

commit fe22e2fa0cc0b02745042cd5260b9b346e022431
Author: Vivek Das Mohapatra <vivek@collabora.com>
Date:   Tue Aug 3 10:22:22 2021 -0300

    elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
    
    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
    
    tst-tls-ie-dlmopen checks to see that new namespaces consume
    TLS memory as expected: This does not happen when new namespaces
    share the same libc instance (since TLS is allocated only when
    a new libc instance insitialises its threading infrastructure).
    
    Adding RTLD_ISOLATE to the dlmopen flags in the test restores
    the old behaviour which allows the test to check what it
    actually needs to.
    
    Checked on x86_64-linux-gnu and i686-linux-gnu.
    
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

Diff:
---
 bits/dlfcn.h               |  10 +++
 elf/dl-close.c             |  41 ++++++----
 elf/dl-deps.c              |  17 ++++
 elf/dl-fini.c              |   6 +-
 elf/dl-init.c              |   4 +-
 elf/dl-load.c              | 192 +++++++++++++++++++++++++++++++++++++--------
 elf/dl-lookup.c            |  23 +++++-
 elf/dl-object.c            |  78 ++++++++++++++++++
 elf/dl-open.c              | 112 ++++++++++++++++++++++++--
 elf/dl-sym.c               |  14 ++++
 elf/rtld.c                 |   2 +-
 elf/tst-tls-ie-dlmopen.c   |   7 +-
 extra-lib.mk               |   2 +
 htl/Makefile               |   3 +-
 include/link.h             |   6 +-
 sysdeps/generic/ldsodefs.h |   7 ++
 sysdeps/mips/bits/dlfcn.h  |  10 +++
 17 files changed, 465 insertions(+), 69 deletions(-)

diff --git a/bits/dlfcn.h b/bits/dlfcn.h
index f3bc63e958..26d715d813 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
+   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 f39001cab9..03ff2c1c4f 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -286,8 +286,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,
@@ -363,7 +364,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.  */
@@ -691,8 +694,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
@@ -732,19 +737,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.  */
-
-	  struct libname_list *lnp = imap->l_libname;
-	  do
+	  /* Skip structures borrowed by proxies from the real map.  */
+	  if (!imap->l_proxy)
 	    {
-	      struct libname_list *this = lnp;
-	      lnp = lnp->next;
-	      if (!this->dont_free)
-		free (this);
+	      /* 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);
 	    }
-	  while (lnp != NULL);
 
 	  /* Remove the searchlists.  */
 	  free (imap->l_initfini);
diff --git a/elf/dl-deps.c b/elf/dl-deps.c
index 087a49b212..303d0f42c0 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=%d\n\n",
+			  args->aux->l_name, args->aux->l_ns,
+			  args->aux->l_real->l_relocated);
+
+      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
diff --git a/elf/dl-fini.c b/elf/dl-fini.c
index 6dbdfe4b3e..51f45be9fe 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;
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
diff --git a/elf/dl-load.c b/elf/dl-load.c
index fb23ba632b..83c05410a7 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -854,7 +854,6 @@ _dl_init_paths (const char *llp, const char *source,
     __rtld_env_path_list.dirs = (void *) -1;
 }
 
-
 /* Process PT_GNU_PROPERTY program header PH in module L after
    PT_LOAD segments are mapped.  Only one NT_GNU_PROPERTY_TYPE_0
    note is handled which contains processor specific properties.
@@ -931,14 +930,12 @@ _dl_process_pt_gnu_property (struct link_map *l, int fd, const ElfW(Phdr) *ph)
 /* Map in the shared object NAME, actually located in REALNAME, and already
    opened on FD.  */
 
-#ifndef EXTERNAL_MAP_FROM_FD
 static
-#endif
 struct link_map *
 _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 			struct filebuf *fbp, char *realname,
 			struct link_map *loader, int l_type, int mode,
-			void **stack_endp, Lmid_t nsid)
+			void **stack_endp, bool has_gnu_unique, Lmid_t nsid)
 {
   struct link_map *l = NULL;
   const ElfW(Ehdr) *header;
@@ -1037,6 +1034,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
+     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);
+
+	    /* NB: 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
@@ -1062,8 +1085,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 neither try to
+	     call auditing functions if the namespace is currently empty.
+	     This can happen when opening the first DSO in a new namespace.
+	   */
+	  if ((head != NULL) && !head->l_auditing)
 	    {
 	      struct audit_ifaces *afct = GLRO(dl_audit);
 	      for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
@@ -1089,6 +1115,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;
+	}
+    }
+
+  /* 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)
+        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 +1158,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.  */
@@ -1583,7 +1621,8 @@ print_search_path (struct r_search_path_elem **list,
 static int
 open_verify (const char *name, int fd,
              struct filebuf *fbp, struct link_map *loader,
-	     int whatcode, int mode, bool *found_other_class, bool free_name)
+	     int whatcode, int mode, bool *found_other_class,
+	     bool *has_gnu_unique, bool free_name)
 {
   /* This is the expected ELF header.  */
 #define ELF32_CLASS ELFCLASS32
@@ -1862,6 +1901,31 @@ open_verify (const char *name, int fd,
 
 	    break;
 	  }
+	else if (ph->p_type == PT_DYNAMIC)
+	  {
+	    off64_t pos = ph->p_offset;
+	    off64_t end = pos + ph->p_filesz;
+
+	    while (pos < end)
+	      {
+		ElfW (Dyn) entry;
+		ssize_t bytes = __pread64_nocancel (fd, &entry,
+						    sizeof (ElfW (Dyn)), pos);
+		if (bytes != sizeof (entry))
+		  goto read_error;
+		pos += bytes;
+
+		switch (entry.d_tag)
+		  {
+		  case DT_GNU_FLAGS_1:
+		    *has_gnu_unique = entry.d_un.d_val & DF_GNU_1_UNIQUE;
+		    /* fallthrough  */
+		  case DT_NULL:
+		    break;
+		  }
+	      }
+
+	  }
       free (abi_note_malloced);
     }
 
@@ -1879,7 +1943,7 @@ static int
 open_path (const char *name, size_t namelen, int mode,
 	   struct r_search_path_struct *sps, char **realname,
 	   struct filebuf *fbp, struct link_map *loader, int whatcode,
-	   bool *found_other_class)
+	   bool *found_other_class, bool *has_gnu_unique)
 {
   struct r_search_path_elem **dirs = sps->dirs;
   char *buf;
@@ -1933,7 +1997,7 @@ open_path (const char *name, size_t namelen, int mode,
 	    _dl_debug_printf ("  trying file=%s\n", buf);
 
 	  fd = open_verify (buf, -1, fbp, loader, whatcode, mode,
-			    found_other_class, false);
+			    found_other_class, has_gnu_unique, false);
 	  if (this_dir->status[cnt] == unknown)
 	    {
 	      if (fd != -1)
@@ -2027,6 +2091,37 @@ open_path (const char *name, size_t namelen, int mode,
   return -1;
 }
 
+/* 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)
@@ -2080,12 +2175,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;
     }
 
@@ -2135,6 +2260,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   /* Will be true if we found a DSO which is of the other ELF class.  */
   bool found_other_class = false;
+  /* Will be true if the DSO has a DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE.  */
+  bool has_gnu_unique = false;
 
   if (strchr (name, '/') == NULL)
     {
@@ -2164,7 +2291,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		fd = open_path (name, namelen, mode,
 				&l->l_rpath_dirs,
 				&realname, &fb, loader, LA_SER_RUNPATH,
-				&found_other_class);
+				&found_other_class, &has_gnu_unique);
 		if (fd != -1)
 		  break;
 
@@ -2180,7 +2307,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	    fd = open_path (name, namelen, mode,
 			    &main_map->l_rpath_dirs,
 			    &realname, &fb, loader ?: main_map, LA_SER_RUNPATH,
-			    &found_other_class);
+			    &found_other_class, &has_gnu_unique);
 	}
 
       /* Try the LD_LIBRARY_PATH environment variable.  */
@@ -2188,7 +2315,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	fd = open_path (name, namelen, mode, &__rtld_env_path_list,
 			&realname, &fb,
 			loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
-			LA_SER_LIBPATH, &found_other_class);
+			LA_SER_LIBPATH, &found_other_class, &has_gnu_unique);
 
       /* Look at the RUNPATH information for this binary.  */
       if (fd == -1 && loader != NULL
@@ -2196,7 +2323,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 			  DT_RUNPATH, "RUNPATH"))
 	fd = open_path (name, namelen, mode,
 			&loader->l_runpath_dirs, &realname, &fb, loader,
-			LA_SER_RUNPATH, &found_other_class);
+			LA_SER_RUNPATH, &found_other_class, &has_gnu_unique);
 
       if (fd == -1)
         {
@@ -2206,7 +2333,7 @@ _dl_map_object (struct link_map *loader, const char *name,
               fd = open_verify (realname, fd,
                                 &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
                                 LA_SER_CONFIG, mode, &found_other_class,
-                                false);
+                                &has_gnu_unique, false);
               if (fd == -1)
                 free (realname);
             }
@@ -2260,7 +2387,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		  fd = open_verify (cached, -1,
 				    &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
 				    LA_SER_CONFIG, mode, &found_other_class,
-				    false);
+				    &has_gnu_unique, false);
 		  if (__glibc_likely (fd != -1))
 		    realname = cached;
 		  else
@@ -2276,7 +2403,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 	      || __glibc_likely (!(l->l_flags_1 & DF_1_NODEFLIB)))
 	  && __rtld_search_dirs.dirs != (void *) -1)
 	fd = open_path (name, namelen, mode, &__rtld_search_dirs,
-			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class);
+			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class,
+			&has_gnu_unique);
 
       /* Add another newline when we are tracing the library loading.  */
       if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
@@ -2294,7 +2422,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	{
 	  fd = open_verify (realname, -1, &fb,
 			    loader ?: GL(dl_ns)[nsid]._ns_loaded, 0, mode,
-			    &found_other_class, true);
+			    &found_other_class, &has_gnu_unique, true);
 	  if (__glibc_unlikely (fd == -1))
 	    free (realname);
 	}
@@ -2355,7 +2483,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   void *stack_end = __libc_stack_end;
   return _dl_map_object_from_fd (name, origname, fd, &fb, realname, loader,
-				 type, mode, &stack_end, nsid);
+				 type, mode, &stack_end, has_gnu_unique, nsid);
 }
 
 struct add_path_state
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index eea217eb28..bac84753aa 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,25 @@ _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 (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 = _dl_find_proxy (undef_map->l_ns,
+					       current_value.m->l_name);
+      if (proxy != NULL)
+	current_value.m = proxy;
+      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
@@ -935,8 +950,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 +959,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);
 }
 
 
diff --git a/elf/dl-object.c b/elf/dl-object.c
index 1875599eb2..1d8fd6332c 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), 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.  */
diff --git a/elf/dl-open.c b/elf/dl-open.c
index ec386626f9..aa8b041743 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
@@ -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).  */
@@ -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,
@@ -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))
     {
@@ -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;
     }
 
@@ -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)
@@ -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)
@@ -792,10 +872,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);
+    }
 }
 
 void *
@@ -884,8 +981,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
diff --git a/elf/dl-sym.c b/elf/dl-sym.c
index de5769f926..e3955e7a51 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
@@ -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);
     }
diff --git a/elf/rtld.c b/elf/rtld.c
index d733359eaf..148a3d4f18 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -660,7 +660,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);
 }
diff --git a/elf/tst-tls-ie-dlmopen.c b/elf/tst-tls-ie-dlmopen.c
index a579d72d2d..bca522a90b 100644
--- a/elf/tst-tls-ie-dlmopen.c
+++ b/elf/tst-tls-ie-dlmopen.c
@@ -52,8 +52,9 @@ blocked_thread_func (void *closure)
 static void *
 load_and_access (Lmid_t lmid, const char *mod, const char *func)
 {
-  /* Load module with TLS.  */
-  void *p = xdlmopen (lmid, mod, RTLD_NOW);
+  /* Load module with TLS, the RTLD_ISOLATE is to force the libc.so to be
+     loaded in a separated namespace.  */
+  void *p = xdlmopen (lmid, mod, RTLD_NOW | RTLD_ISOLATE);
   /* Access the TLS variable to ensure it is allocated.  */
   void (*f) (void) = (void (*) (void))xdlsym (p, func);
   f ();
@@ -95,7 +96,7 @@ do_test (void)
      than 1024 bytes are available (exact number depends on TLS optimizations
      and the libc TLS use).  */
   printf ("The next dlmopen should fail...\n");
-  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW);
+  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW | RTLD_ISOLATE);
   if (p != NULL)
     FAIL_EXIT1 ("error: expected dlmopen to fail because there is "
 		"not enough surplus static TLS.\n");
diff --git a/extra-lib.mk b/extra-lib.mk
index 9051958ec0..38184adae1 100644
--- a/extra-lib.mk
+++ b/extra-lib.mk
@@ -102,9 +102,11 @@ $(objpfx)$(lib).so: $(firstword $($(lib)-map) \
 					    $(filter $(lib).map, \
 						     $(version-maps))))
 ifneq ($(ld-zunique),yes)
+ifneq ($(lib),libpthread)
 $(objpfx)$(lib).so: $(common-objpfx)/elf/dynamic-notes.os
 endif
 endif
+endif
 
 endif
 
diff --git a/htl/Makefile b/htl/Makefile
index c2a25dcd79..ce6d9a8cd1 100644
--- a/htl/Makefile
+++ b/htl/Makefile
@@ -202,9 +202,10 @@ $(inst_libdir)/libpthread_syms.a: $(srcdir)/libpthread_syms.a $(+force)
 libc-link.so = $(common-objpfx)libc.so
 
 extra-B-pthread.so = -B$(common-objpfx)htl/
+# Turn off DF_GNU_1_UNIQUE for libpthread now that it's a stub.
 LDFLAGS-pthread.so = -Wl,--enable-new-dtags,-z,nodelete,-z,initfirst
 ifeq ($(ld-zunique),yes)
-LDFLAGS-pthread.so += -Wl,-z,unique
+LDFLAGS-pthread.so += -Wl,-z,nounique
 endif
 
 include ../Rules
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.  */
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 22f3fea436..f49cbfc3ae 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)
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.  */


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [glibc/azanella/rtld-shared] elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
@ 2021-09-27 18:54 Adhemerval Zanella
  0 siblings, 0 replies; 6+ messages in thread
From: Adhemerval Zanella @ 2021-09-27 18:54 UTC (permalink / raw)
  To: glibc-cvs

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=010fc01f004241b6afcae92f2d20fe47e6869f72

commit 010fc01f004241b6afcae92f2d20fe47e6869f72
Author: Vivek Das Mohapatra <vivek@collabora.com>
Date:   Tue Aug 3 10:22:22 2021 -0300

    elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
    
    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
    
    tst-tls-ie-dlmopen checks to see that new namespaces consume
    TLS memory as expected: This does not happen when new namespaces
    share the same libc instance (since TLS is allocated only when
    a new libc instance insitialises its threading infrastructure).
    
    Adding RTLD_ISOLATE to the dlmopen flags in the test restores
    the old behaviour which allows the test to check what it
    actually needs to.
    
    Checked on x86_64-linux-gnu and i686-linux-gnu.
    
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

Diff:
---
 NEWS                       |  10 +++
 bits/dlfcn.h               |  10 +++
 elf/dl-close.c             |  41 ++++++----
 elf/dl-deps.c              |  17 ++++
 elf/dl-fini.c              |   6 +-
 elf/dl-init.c              |   4 +-
 elf/dl-load.c              | 190 ++++++++++++++++++++++++++++++++++++++-------
 elf/dl-lookup.c            |  23 +++++-
 elf/dl-object.c            |  78 +++++++++++++++++++
 elf/dl-open.c              | 112 ++++++++++++++++++++++++--
 elf/dl-sym.c               |  14 ++++
 elf/rtld.c                 |   2 +-
 elf/tst-tls-ie-dlmopen.c   |   7 +-
 extra-lib.mk               |   2 +
 htl/Makefile               |   1 +
 include/link.h             |   6 +-
 sysdeps/generic/ldsodefs.h |   7 ++
 sysdeps/mips/bits/dlfcn.h  |  10 +++
 18 files changed, 474 insertions(+), 66 deletions(-)

diff --git a/NEWS b/NEWS
index 9ae04b2f20..dc8b46a40b 100644
--- a/NEWS
+++ b/NEWS
@@ -35,6 +35,16 @@ Major new features:
 * The __STDC_IEC_60559_BFP__ and __STDC_IEC_60559_COMPLEX__ macros are
   predefined as specified in TS 18661-1:2014.
 
+* The glibc now handle objects marked with the GNU extension DF_GNU_1_UNIQUE,
+  where the DSO should be loaded only once and shared over namespaces.
+
+* The dlmopen interface now supports the RTLD_SHARED flag as a GNU extension.
+  If an object is loaded with the new RTLD_SHARED flag the glibc ensures
+  that a "master" copy exists (and is flagged as no-delete) in the
+  main namespace and a thin wrapper or clone is placed in the target
+  namespace.  This allows to shared DSO that can be loaded only once with
+  multiple namespaces.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
 * The r_version update in the debugger interface makes the glibc binary
diff --git a/bits/dlfcn.h b/bits/dlfcn.h
index f3bc63e958..26d715d813 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
+   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 93ff5c96e9..bbc178cb3e 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -286,8 +286,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,
@@ -363,7 +364,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.  */
@@ -691,8 +694,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
@@ -732,19 +737,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.  */
-
-	  struct libname_list *lnp = imap->l_libname;
-	  do
+	  /* Skip structures borrowed by proxies from the real map.  */
+	  if (!imap->l_proxy)
 	    {
-	      struct libname_list *this = lnp;
-	      lnp = lnp->next;
-	      if (!this->dont_free)
-		free (this);
+	      /* 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);
 	    }
-	  while (lnp != NULL);
 
 	  /* Remove the searchlists.  */
 	  free (imap->l_initfini);
diff --git a/elf/dl-deps.c b/elf/dl-deps.c
index 087a49b212..303d0f42c0 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=%d\n\n",
+			  args->aux->l_name, args->aux->l_ns,
+			  args->aux->l_real->l_relocated);
+
+      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
diff --git a/elf/dl-fini.c b/elf/dl-fini.c
index 6dbdfe4b3e..51f45be9fe 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;
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
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 4f06c5e510..13e76ba870 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -854,7 +854,6 @@ _dl_init_paths (const char *llp, const char *source,
     __rtld_env_path_list.dirs = (void *) -1;
 }
 
-
 /* Process PT_GNU_PROPERTY program header PH in module L after
    PT_LOAD segments are mapped.  Only one NT_GNU_PROPERTY_TYPE_0
    note is handled which contains processor specific properties.
@@ -935,7 +934,7 @@ static struct link_map *
 _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 			struct filebuf *fbp, char *realname,
 			struct link_map *loader, int l_type, int mode,
-			void **stack_endp, Lmid_t nsid)
+			void **stack_endp, bool has_gnu_unique, Lmid_t nsid)
 {
   struct link_map *l = NULL;
   const ElfW(Ehdr) *header;
@@ -1034,6 +1033,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
+     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);
+
+	    /* NB: 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
@@ -1059,8 +1084,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 neither try to
+	     call auditing functions if the namespace is currently empty.
+	     This can happen when opening the first DSO in a new namespace.
+	   */
+	  if ((head != NULL) && !head->l_auditing)
 	    {
 	      struct audit_ifaces *afct = GLRO(dl_audit);
 	      for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
@@ -1086,6 +1114,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;
+	}
+    }
+
+  /* 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)
+        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))
@@ -1103,20 +1157,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.  */
@@ -1581,7 +1621,8 @@ print_search_path (struct r_search_path_elem **list,
 static int
 open_verify (const char *name, int fd,
              struct filebuf *fbp, struct link_map *loader,
-	     int whatcode, int mode, bool *found_other_class, bool free_name)
+	     int whatcode, int mode, bool *found_other_class,
+	     bool *has_gnu_unique, bool free_name)
 {
   /* This is the expected ELF header.  */
 #define ELF32_CLASS ELFCLASS32
@@ -1860,6 +1901,31 @@ open_verify (const char *name, int fd,
 
 	    break;
 	  }
+	else if (ph->p_type == PT_DYNAMIC)
+	  {
+	    off64_t pos = ph->p_offset;
+	    off64_t end = pos + ph->p_filesz;
+
+	    while (pos < end)
+	      {
+		ElfW (Dyn) entry;
+		ssize_t bytes = __pread64_nocancel (fd, &entry,
+						    sizeof (ElfW (Dyn)), pos);
+		if (bytes != sizeof (entry))
+		  goto read_error;
+		pos += bytes;
+
+		switch (entry.d_tag)
+		  {
+		  case DT_GNU_FLAGS_1:
+		    *has_gnu_unique = entry.d_un.d_val & DF_GNU_1_UNIQUE;
+		    /* fallthrough  */
+		  case DT_NULL:
+		    break;
+		  }
+	      }
+
+	  }
       free (abi_note_malloced);
     }
 
@@ -1877,7 +1943,7 @@ static int
 open_path (const char *name, size_t namelen, int mode,
 	   struct r_search_path_struct *sps, char **realname,
 	   struct filebuf *fbp, struct link_map *loader, int whatcode,
-	   bool *found_other_class)
+	   bool *found_other_class, bool *has_gnu_unique)
 {
   struct r_search_path_elem **dirs = sps->dirs;
   char *buf;
@@ -1931,7 +1997,7 @@ open_path (const char *name, size_t namelen, int mode,
 	    _dl_debug_printf ("  trying file=%s\n", buf);
 
 	  fd = open_verify (buf, -1, fbp, loader, whatcode, mode,
-			    found_other_class, false);
+			    found_other_class, has_gnu_unique, false);
 	  if (this_dir->status[cnt] == unknown)
 	    {
 	      if (fd != -1)
@@ -2025,6 +2091,37 @@ open_path (const char *name, size_t namelen, int mode,
   return -1;
 }
 
+/* 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)
@@ -2078,12 +2175,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;
     }
 
@@ -2133,6 +2260,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   /* Will be true if we found a DSO which is of the other ELF class.  */
   bool found_other_class = false;
+  /* Will be true if the DSO has a DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE.  */
+  bool has_gnu_unique = false;
 
   if (strchr (name, '/') == NULL)
     {
@@ -2162,7 +2291,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		fd = open_path (name, namelen, mode,
 				&l->l_rpath_dirs,
 				&realname, &fb, loader, LA_SER_RUNPATH,
-				&found_other_class);
+				&found_other_class, &has_gnu_unique);
 		if (fd != -1)
 		  break;
 
@@ -2178,7 +2307,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	    fd = open_path (name, namelen, mode,
 			    &main_map->l_rpath_dirs,
 			    &realname, &fb, loader ?: main_map, LA_SER_RUNPATH,
-			    &found_other_class);
+			    &found_other_class, &has_gnu_unique);
 	}
 
       /* Try the LD_LIBRARY_PATH environment variable.  */
@@ -2186,7 +2315,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	fd = open_path (name, namelen, mode, &__rtld_env_path_list,
 			&realname, &fb,
 			loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
-			LA_SER_LIBPATH, &found_other_class);
+			LA_SER_LIBPATH, &found_other_class, &has_gnu_unique);
 
       /* Look at the RUNPATH information for this binary.  */
       if (fd == -1 && loader != NULL
@@ -2194,7 +2323,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 			  DT_RUNPATH, "RUNPATH"))
 	fd = open_path (name, namelen, mode,
 			&loader->l_runpath_dirs, &realname, &fb, loader,
-			LA_SER_RUNPATH, &found_other_class);
+			LA_SER_RUNPATH, &found_other_class, &has_gnu_unique);
 
       if (fd == -1)
         {
@@ -2204,7 +2333,7 @@ _dl_map_object (struct link_map *loader, const char *name,
               fd = open_verify (realname, fd,
                                 &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
                                 LA_SER_CONFIG, mode, &found_other_class,
-                                false);
+                                &has_gnu_unique, false);
               if (fd == -1)
                 free (realname);
             }
@@ -2258,7 +2387,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		  fd = open_verify (cached, -1,
 				    &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
 				    LA_SER_CONFIG, mode, &found_other_class,
-				    false);
+				    &has_gnu_unique, false);
 		  if (__glibc_likely (fd != -1))
 		    realname = cached;
 		  else
@@ -2274,7 +2403,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 	      || __glibc_likely (!(l->l_flags_1 & DF_1_NODEFLIB)))
 	  && __rtld_search_dirs.dirs != (void *) -1)
 	fd = open_path (name, namelen, mode, &__rtld_search_dirs,
-			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class);
+			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class,
+			&has_gnu_unique);
 
       /* Add another newline when we are tracing the library loading.  */
       if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
@@ -2292,7 +2422,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	{
 	  fd = open_verify (realname, -1, &fb,
 			    loader ?: GL(dl_ns)[nsid]._ns_loaded, 0, mode,
-			    &found_other_class, true);
+			    &found_other_class, &has_gnu_unique, true);
 	  if (__glibc_unlikely (fd == -1))
 	    free (realname);
 	}
@@ -2353,7 +2483,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   void *stack_end = __libc_stack_end;
   return _dl_map_object_from_fd (name, origname, fd, &fb, realname, loader,
-				 type, mode, &stack_end, nsid);
+				 type, mode, &stack_end, has_gnu_unique, nsid);
 }
 
 struct add_path_state
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index eea217eb28..bac84753aa 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,25 @@ _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 (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 = _dl_find_proxy (undef_map->l_ns,
+					       current_value.m->l_name);
+      if (proxy != NULL)
+	current_value.m = proxy;
+      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
@@ -935,8 +950,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 +959,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);
 }
 
 
diff --git a/elf/dl-object.c b/elf/dl-object.c
index 1875599eb2..1d8fd6332c 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), 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.  */
diff --git a/elf/dl-open.c b/elf/dl-open.c
index a25443f6d1..57a94a7d55 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
@@ -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).  */
@@ -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,
@@ -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))
     {
@@ -576,6 +642,16 @@ dl_open_worker (void *a)
 
       assert (_dl_debug_update (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;
     }
 
@@ -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)
@@ -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)
@@ -792,10 +872,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);
+    }
 }
 
 void *
@@ -884,8 +981,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
diff --git a/elf/dl-sym.c b/elf/dl-sym.c
index de5769f926..e3955e7a51 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
@@ -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);
     }
diff --git a/elf/rtld.c b/elf/rtld.c
index 8d2bba3d43..b70cbbbda6 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);
 }
diff --git a/elf/tst-tls-ie-dlmopen.c b/elf/tst-tls-ie-dlmopen.c
index a579d72d2d..bca522a90b 100644
--- a/elf/tst-tls-ie-dlmopen.c
+++ b/elf/tst-tls-ie-dlmopen.c
@@ -52,8 +52,9 @@ blocked_thread_func (void *closure)
 static void *
 load_and_access (Lmid_t lmid, const char *mod, const char *func)
 {
-  /* Load module with TLS.  */
-  void *p = xdlmopen (lmid, mod, RTLD_NOW);
+  /* Load module with TLS, the RTLD_ISOLATE is to force the libc.so to be
+     loaded in a separated namespace.  */
+  void *p = xdlmopen (lmid, mod, RTLD_NOW | RTLD_ISOLATE);
   /* Access the TLS variable to ensure it is allocated.  */
   void (*f) (void) = (void (*) (void))xdlsym (p, func);
   f ();
@@ -95,7 +96,7 @@ do_test (void)
      than 1024 bytes are available (exact number depends on TLS optimizations
      and the libc TLS use).  */
   printf ("The next dlmopen should fail...\n");
-  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW);
+  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW | RTLD_ISOLATE);
   if (p != NULL)
     FAIL_EXIT1 ("error: expected dlmopen to fail because there is "
 		"not enough surplus static TLS.\n");
diff --git a/extra-lib.mk b/extra-lib.mk
index 9051958ec0..38184adae1 100644
--- a/extra-lib.mk
+++ b/extra-lib.mk
@@ -102,9 +102,11 @@ $(objpfx)$(lib).so: $(firstword $($(lib)-map) \
 					    $(filter $(lib).map, \
 						     $(version-maps))))
 ifneq ($(ld-zunique),yes)
+ifneq ($(lib),libpthread)
 $(objpfx)$(lib).so: $(common-objpfx)/elf/dynamic-notes.os
 endif
 endif
+endif
 
 endif
 
diff --git a/htl/Makefile b/htl/Makefile
index cf9d12fc12..0dfa7f2d92 100644
--- a/htl/Makefile
+++ b/htl/Makefile
@@ -202,6 +202,7 @@ $(inst_libdir)/libpthread_syms.a: $(srcdir)/libpthread_syms.a $(+force)
 libc-link.so = $(common-objpfx)libc.so
 
 extra-B-pthread.so = -B$(common-objpfx)htl/
+# Turn off DF_GNU_1_UNIQUE for libpthread now that it's a stub.
 LDFLAGS-pthread.so = -Wl,--enable-new-dtags,-z,nodelete,-z,initfirst
 
 include ../Rules
diff --git a/include/link.h b/include/link.h
index fd39c2207c..befc73a0ed 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.  */
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index c64650ff25..573cbc1647 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -1018,6 +1018,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
@@ -1297,6 +1302,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)
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.  */


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [glibc/azanella/rtld-shared] elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
@ 2021-08-10 13:42 Adhemerval Zanella
  0 siblings, 0 replies; 6+ messages in thread
From: Adhemerval Zanella @ 2021-08-10 13:42 UTC (permalink / raw)
  To: glibc-cvs

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=a9763dad8f5d3b510c288777f0313242b1a5ab7b

commit a9763dad8f5d3b510c288777f0313242b1a5ab7b
Author: Vivek Das Mohapatra <vivek@collabora.com>
Date:   Tue Aug 3 10:22:22 2021 -0300

    elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
    
    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
    
    tst-tls-ie-dlmopen checks to see that new namespaces consume
    TLS memory as expected: This does not happen when new namespaces
    share the same libc instance (since TLS is allocated only when
    a new libc instance insitialises its threading infrastructure).
    
    Adding RTLD_ISOLATE to the dlmopen flags in the test restores
    the old behaviour which allows the test to check what it
    actually needs to.
    
    Checked on x86_64-linux-gnu and i686-linux-gnu.
    
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

Diff:
---
 NEWS                       |  10 ++-
 bits/dlfcn.h               |  10 +++
 elf/dl-close.c             |  41 ++++++----
 elf/dl-deps.c              |  17 ++++
 elf/dl-fini.c              |   6 +-
 elf/dl-init.c              |   4 +-
 elf/dl-load.c              | 190 ++++++++++++++++++++++++++++++++++++++-------
 elf/dl-lookup.c            |  23 +++++-
 elf/dl-object.c            |  78 +++++++++++++++++++
 elf/dl-open.c              | 112 ++++++++++++++++++++++++--
 elf/dl-sym.c               |  14 ++++
 elf/rtld.c                 |   2 +-
 elf/tst-tls-ie-dlmopen.c   |   7 +-
 extra-lib.mk               |   2 +
 htl/Makefile               |   1 +
 include/link.h             |   6 +-
 sysdeps/generic/ldsodefs.h |   7 ++
 sysdeps/mips/bits/dlfcn.h  |  10 +++
 18 files changed, 473 insertions(+), 67 deletions(-)

diff --git a/NEWS b/NEWS
index 79c895e382..593d064fd5 100644
--- a/NEWS
+++ b/NEWS
@@ -9,7 +9,15 @@ Version 2.35
 
 Major new features:
 
-  [Add new features here]
+* The glibc now handle objects marked with the GNU extension DF_GNU_1_UNIQUE,
+  where the DSO should be loaded only once and shared over namespaces.
+
+* The dlmopen interface now supports the RTLD_SHARED flag as a GNU extension.
+  If an object is loaded with the new RTLD_SHARED flag the glibc ensures
+  that a "master" copy exists (and is flagged as no-delete) in the
+  main namespace and a thin wrapper or clone is placed in the target
+  namespace.  This allows to shared DSO that can be loaded only once with
+  multiple namespaces.
 
 Deprecated and removed features, and other changes affecting compatibility:
 
diff --git a/bits/dlfcn.h b/bits/dlfcn.h
index f3bc63e958..26d715d813 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
+   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 f39001cab9..03ff2c1c4f 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -286,8 +286,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,
@@ -363,7 +364,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.  */
@@ -691,8 +694,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
@@ -732,19 +737,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.  */
-
-	  struct libname_list *lnp = imap->l_libname;
-	  do
+	  /* Skip structures borrowed by proxies from the real map.  */
+	  if (!imap->l_proxy)
 	    {
-	      struct libname_list *this = lnp;
-	      lnp = lnp->next;
-	      if (!this->dont_free)
-		free (this);
+	      /* 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);
 	    }
-	  while (lnp != NULL);
 
 	  /* Remove the searchlists.  */
 	  free (imap->l_initfini);
diff --git a/elf/dl-deps.c b/elf/dl-deps.c
index 087a49b212..303d0f42c0 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=%d\n\n",
+			  args->aux->l_name, args->aux->l_ns,
+			  args->aux->l_real->l_relocated);
+
+      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
diff --git a/elf/dl-fini.c b/elf/dl-fini.c
index 6dbdfe4b3e..51f45be9fe 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;
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
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 8533add1b4..6a090c34b1 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -854,7 +854,6 @@ _dl_init_paths (const char *llp, const char *source,
     __rtld_env_path_list.dirs = (void *) -1;
 }
 
-
 /* Process PT_GNU_PROPERTY program header PH in module L after
    PT_LOAD segments are mapped.  Only one NT_GNU_PROPERTY_TYPE_0
    note is handled which contains processor specific properties.
@@ -935,7 +934,7 @@ static struct link_map *
 _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 			struct filebuf *fbp, char *realname,
 			struct link_map *loader, int l_type, int mode,
-			void **stack_endp, Lmid_t nsid)
+			void **stack_endp, bool has_gnu_unique, Lmid_t nsid)
 {
   struct link_map *l = NULL;
   const ElfW(Ehdr) *header;
@@ -1034,6 +1033,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
+     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);
+
+	    /* NB: 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
@@ -1059,8 +1084,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 neither try to
+	     call auditing functions if the namespace is currently empty.
+	     This can happen when opening the first DSO in a new namespace.
+	   */
+	  if ((head != NULL) && !head->l_auditing)
 	    {
 	      struct audit_ifaces *afct = GLRO(dl_audit);
 	      for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
@@ -1086,6 +1114,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;
+	}
+    }
+
+  /* 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)
+        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))
@@ -1103,20 +1157,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.  */
@@ -1580,7 +1620,8 @@ print_search_path (struct r_search_path_elem **list,
 static int
 open_verify (const char *name, int fd,
              struct filebuf *fbp, struct link_map *loader,
-	     int whatcode, int mode, bool *found_other_class, bool free_name)
+	     int whatcode, int mode, bool *found_other_class,
+	     bool *has_gnu_unique, bool free_name)
 {
   /* This is the expected ELF header.  */
 #define ELF32_CLASS ELFCLASS32
@@ -1859,6 +1900,31 @@ open_verify (const char *name, int fd,
 
 	    break;
 	  }
+	else if (ph->p_type == PT_DYNAMIC)
+	  {
+	    off64_t pos = ph->p_offset;
+	    off64_t end = pos + ph->p_filesz;
+
+	    while (pos < end)
+	      {
+		ElfW (Dyn) entry;
+		ssize_t bytes = __pread64_nocancel (fd, &entry,
+						    sizeof (ElfW (Dyn)), pos);
+		if (bytes != sizeof (entry))
+		  goto read_error;
+		pos += bytes;
+
+		switch (entry.d_tag)
+		  {
+		  case DT_GNU_FLAGS_1:
+		    *has_gnu_unique = entry.d_un.d_val & DF_GNU_1_UNIQUE;
+		    /* fallthrough  */
+		  case DT_NULL:
+		    break;
+		  }
+	      }
+
+	  }
       free (abi_note_malloced);
     }
 
@@ -1876,7 +1942,7 @@ static int
 open_path (const char *name, size_t namelen, int mode,
 	   struct r_search_path_struct *sps, char **realname,
 	   struct filebuf *fbp, struct link_map *loader, int whatcode,
-	   bool *found_other_class)
+	   bool *found_other_class, bool *has_gnu_unique)
 {
   struct r_search_path_elem **dirs = sps->dirs;
   char *buf;
@@ -1930,7 +1996,7 @@ open_path (const char *name, size_t namelen, int mode,
 	    _dl_debug_printf ("  trying file=%s\n", buf);
 
 	  fd = open_verify (buf, -1, fbp, loader, whatcode, mode,
-			    found_other_class, false);
+			    found_other_class, has_gnu_unique, false);
 	  if (this_dir->status[cnt] == unknown)
 	    {
 	      if (fd != -1)
@@ -2024,6 +2090,37 @@ open_path (const char *name, size_t namelen, int mode,
   return -1;
 }
 
+/* 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)
@@ -2077,12 +2174,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;
     }
 
@@ -2132,6 +2259,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   /* Will be true if we found a DSO which is of the other ELF class.  */
   bool found_other_class = false;
+  /* Will be true if the DSO has a DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE.  */
+  bool has_gnu_unique = false;
 
   if (strchr (name, '/') == NULL)
     {
@@ -2161,7 +2290,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		fd = open_path (name, namelen, mode,
 				&l->l_rpath_dirs,
 				&realname, &fb, loader, LA_SER_RUNPATH,
-				&found_other_class);
+				&found_other_class, &has_gnu_unique);
 		if (fd != -1)
 		  break;
 
@@ -2177,7 +2306,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	    fd = open_path (name, namelen, mode,
 			    &main_map->l_rpath_dirs,
 			    &realname, &fb, loader ?: main_map, LA_SER_RUNPATH,
-			    &found_other_class);
+			    &found_other_class, &has_gnu_unique);
 	}
 
       /* Try the LD_LIBRARY_PATH environment variable.  */
@@ -2185,7 +2314,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	fd = open_path (name, namelen, mode, &__rtld_env_path_list,
 			&realname, &fb,
 			loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
-			LA_SER_LIBPATH, &found_other_class);
+			LA_SER_LIBPATH, &found_other_class, &has_gnu_unique);
 
       /* Look at the RUNPATH information for this binary.  */
       if (fd == -1 && loader != NULL
@@ -2193,7 +2322,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 			  DT_RUNPATH, "RUNPATH"))
 	fd = open_path (name, namelen, mode,
 			&loader->l_runpath_dirs, &realname, &fb, loader,
-			LA_SER_RUNPATH, &found_other_class);
+			LA_SER_RUNPATH, &found_other_class, &has_gnu_unique);
 
       if (fd == -1)
         {
@@ -2203,7 +2332,7 @@ _dl_map_object (struct link_map *loader, const char *name,
               fd = open_verify (realname, fd,
                                 &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
                                 LA_SER_CONFIG, mode, &found_other_class,
-                                false);
+                                &has_gnu_unique, false);
               if (fd == -1)
                 free (realname);
             }
@@ -2257,7 +2386,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		  fd = open_verify (cached, -1,
 				    &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
 				    LA_SER_CONFIG, mode, &found_other_class,
-				    false);
+				    &has_gnu_unique, false);
 		  if (__glibc_likely (fd != -1))
 		    realname = cached;
 		  else
@@ -2273,7 +2402,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 	      || __glibc_likely (!(l->l_flags_1 & DF_1_NODEFLIB)))
 	  && __rtld_search_dirs.dirs != (void *) -1)
 	fd = open_path (name, namelen, mode, &__rtld_search_dirs,
-			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class);
+			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class,
+			&has_gnu_unique);
 
       /* Add another newline when we are tracing the library loading.  */
       if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
@@ -2291,7 +2421,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	{
 	  fd = open_verify (realname, -1, &fb,
 			    loader ?: GL(dl_ns)[nsid]._ns_loaded, 0, mode,
-			    &found_other_class, true);
+			    &found_other_class, &has_gnu_unique, true);
 	  if (__glibc_unlikely (fd == -1))
 	    free (realname);
 	}
@@ -2352,7 +2482,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   void *stack_end = __libc_stack_end;
   return _dl_map_object_from_fd (name, origname, fd, &fb, realname, loader,
-				 type, mode, &stack_end, nsid);
+				 type, mode, &stack_end, has_gnu_unique, nsid);
 }
 
 struct add_path_state
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index eea217eb28..bac84753aa 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,25 @@ _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 (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 = _dl_find_proxy (undef_map->l_ns,
+					       current_value.m->l_name);
+      if (proxy != NULL)
+	current_value.m = proxy;
+      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
@@ -935,8 +950,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 +959,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);
 }
 
 
diff --git a/elf/dl-object.c b/elf/dl-object.c
index 1875599eb2..1d8fd6332c 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), 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.  */
diff --git a/elf/dl-open.c b/elf/dl-open.c
index ec386626f9..aa8b041743 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
@@ -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).  */
@@ -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,
@@ -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))
     {
@@ -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;
     }
 
@@ -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)
@@ -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)
@@ -792,10 +872,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);
+    }
 }
 
 void *
@@ -884,8 +981,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
diff --git a/elf/dl-sym.c b/elf/dl-sym.c
index de5769f926..e3955e7a51 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
@@ -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);
     }
diff --git a/elf/rtld.c b/elf/rtld.c
index d733359eaf..148a3d4f18 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -660,7 +660,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);
 }
diff --git a/elf/tst-tls-ie-dlmopen.c b/elf/tst-tls-ie-dlmopen.c
index a579d72d2d..bca522a90b 100644
--- a/elf/tst-tls-ie-dlmopen.c
+++ b/elf/tst-tls-ie-dlmopen.c
@@ -52,8 +52,9 @@ blocked_thread_func (void *closure)
 static void *
 load_and_access (Lmid_t lmid, const char *mod, const char *func)
 {
-  /* Load module with TLS.  */
-  void *p = xdlmopen (lmid, mod, RTLD_NOW);
+  /* Load module with TLS, the RTLD_ISOLATE is to force the libc.so to be
+     loaded in a separated namespace.  */
+  void *p = xdlmopen (lmid, mod, RTLD_NOW | RTLD_ISOLATE);
   /* Access the TLS variable to ensure it is allocated.  */
   void (*f) (void) = (void (*) (void))xdlsym (p, func);
   f ();
@@ -95,7 +96,7 @@ do_test (void)
      than 1024 bytes are available (exact number depends on TLS optimizations
      and the libc TLS use).  */
   printf ("The next dlmopen should fail...\n");
-  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW);
+  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW | RTLD_ISOLATE);
   if (p != NULL)
     FAIL_EXIT1 ("error: expected dlmopen to fail because there is "
 		"not enough surplus static TLS.\n");
diff --git a/extra-lib.mk b/extra-lib.mk
index 9051958ec0..38184adae1 100644
--- a/extra-lib.mk
+++ b/extra-lib.mk
@@ -102,9 +102,11 @@ $(objpfx)$(lib).so: $(firstword $($(lib)-map) \
 					    $(filter $(lib).map, \
 						     $(version-maps))))
 ifneq ($(ld-zunique),yes)
+ifneq ($(lib),libpthread)
 $(objpfx)$(lib).so: $(common-objpfx)/elf/dynamic-notes.os
 endif
 endif
+endif
 
 endif
 
diff --git a/htl/Makefile b/htl/Makefile
index cf9d12fc12..0dfa7f2d92 100644
--- a/htl/Makefile
+++ b/htl/Makefile
@@ -202,6 +202,7 @@ $(inst_libdir)/libpthread_syms.a: $(srcdir)/libpthread_syms.a $(+force)
 libc-link.so = $(common-objpfx)libc.so
 
 extra-B-pthread.so = -B$(common-objpfx)htl/
+# Turn off DF_GNU_1_UNIQUE for libpthread now that it's a stub.
 LDFLAGS-pthread.so = -Wl,--enable-new-dtags,-z,nodelete,-z,initfirst
 
 include ../Rules
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.  */
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 22f3fea436..f49cbfc3ae 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)
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.  */


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [glibc/azanella/rtld-shared] elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
@ 2021-08-10 12:43 Adhemerval Zanella
  0 siblings, 0 replies; 6+ messages in thread
From: Adhemerval Zanella @ 2021-08-10 12:43 UTC (permalink / raw)
  To: glibc-cvs

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=7a2ab772a2facf038559849317e5d3e20b72d44d

commit 7a2ab772a2facf038559849317e5d3e20b72d44d
Author: Vivek Das Mohapatra <vivek@collabora.com>
Date:   Tue Aug 3 10:22:22 2021 -0300

    elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
    
    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
    
    tst-tls-ie-dlmopen checks to see that new namespaces consume
    TLS memory as expected: This does not happen when new namespaces
    share the same libc instance (since TLS is allocated only when
    a new libc instance insitialises its threading infrastructure).
    
    Adding RTLD_ISOLATE to the dlmopen flags in the test restores
    the old behaviour which allows the test to check what it
    actually needs to.
    
    Checked on x86_64-linux-gnu and i686-linux-gnu.
    
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

Diff:
---
 NEWS                       |  10 ++-
 bits/dlfcn.h               |  10 +++
 elf/dl-close.c             |  41 ++++++----
 elf/dl-deps.c              |  17 ++++
 elf/dl-fini.c              |   6 +-
 elf/dl-init.c              |   4 +-
 elf/dl-load.c              | 190 ++++++++++++++++++++++++++++++++++++++-------
 elf/dl-lookup.c            |  23 +++++-
 elf/dl-object.c            |  78 +++++++++++++++++++
 elf/dl-open.c              | 112 ++++++++++++++++++++++++--
 elf/dl-sym.c               |  14 ++++
 elf/rtld.c                 |   2 +-
 elf/tst-tls-ie-dlmopen.c   |   7 +-
 extra-lib.mk               |   2 +
 htl/Makefile               |   3 +-
 include/link.h             |   6 +-
 sysdeps/generic/ldsodefs.h |   7 ++
 sysdeps/mips/bits/dlfcn.h  |  10 +++
 18 files changed, 474 insertions(+), 68 deletions(-)

diff --git a/NEWS b/NEWS
index 79c895e382..593d064fd5 100644
--- a/NEWS
+++ b/NEWS
@@ -9,7 +9,15 @@ Version 2.35
 
 Major new features:
 
-  [Add new features here]
+* The glibc now handle objects marked with the GNU extension DF_GNU_1_UNIQUE,
+  where the DSO should be loaded only once and shared over namespaces.
+
+* The dlmopen interface now supports the RTLD_SHARED flag as a GNU extension.
+  If an object is loaded with the new RTLD_SHARED flag the glibc ensures
+  that a "master" copy exists (and is flagged as no-delete) in the
+  main namespace and a thin wrapper or clone is placed in the target
+  namespace.  This allows to shared DSO that can be loaded only once with
+  multiple namespaces.
 
 Deprecated and removed features, and other changes affecting compatibility:
 
diff --git a/bits/dlfcn.h b/bits/dlfcn.h
index f3bc63e958..26d715d813 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
+   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 f39001cab9..03ff2c1c4f 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -286,8 +286,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,
@@ -363,7 +364,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.  */
@@ -691,8 +694,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
@@ -732,19 +737,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.  */
-
-	  struct libname_list *lnp = imap->l_libname;
-	  do
+	  /* Skip structures borrowed by proxies from the real map.  */
+	  if (!imap->l_proxy)
 	    {
-	      struct libname_list *this = lnp;
-	      lnp = lnp->next;
-	      if (!this->dont_free)
-		free (this);
+	      /* 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);
 	    }
-	  while (lnp != NULL);
 
 	  /* Remove the searchlists.  */
 	  free (imap->l_initfini);
diff --git a/elf/dl-deps.c b/elf/dl-deps.c
index 087a49b212..303d0f42c0 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=%d\n\n",
+			  args->aux->l_name, args->aux->l_ns,
+			  args->aux->l_real->l_relocated);
+
+      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
diff --git a/elf/dl-fini.c b/elf/dl-fini.c
index 6dbdfe4b3e..51f45be9fe 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;
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
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 8533add1b4..6a090c34b1 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -854,7 +854,6 @@ _dl_init_paths (const char *llp, const char *source,
     __rtld_env_path_list.dirs = (void *) -1;
 }
 
-
 /* Process PT_GNU_PROPERTY program header PH in module L after
    PT_LOAD segments are mapped.  Only one NT_GNU_PROPERTY_TYPE_0
    note is handled which contains processor specific properties.
@@ -935,7 +934,7 @@ static struct link_map *
 _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 			struct filebuf *fbp, char *realname,
 			struct link_map *loader, int l_type, int mode,
-			void **stack_endp, Lmid_t nsid)
+			void **stack_endp, bool has_gnu_unique, Lmid_t nsid)
 {
   struct link_map *l = NULL;
   const ElfW(Ehdr) *header;
@@ -1034,6 +1033,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
+     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);
+
+	    /* NB: 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
@@ -1059,8 +1084,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 neither try to
+	     call auditing functions if the namespace is currently empty.
+	     This can happen when opening the first DSO in a new namespace.
+	   */
+	  if ((head != NULL) && !head->l_auditing)
 	    {
 	      struct audit_ifaces *afct = GLRO(dl_audit);
 	      for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
@@ -1086,6 +1114,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;
+	}
+    }
+
+  /* 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)
+        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))
@@ -1103,20 +1157,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.  */
@@ -1580,7 +1620,8 @@ print_search_path (struct r_search_path_elem **list,
 static int
 open_verify (const char *name, int fd,
              struct filebuf *fbp, struct link_map *loader,
-	     int whatcode, int mode, bool *found_other_class, bool free_name)
+	     int whatcode, int mode, bool *found_other_class,
+	     bool *has_gnu_unique, bool free_name)
 {
   /* This is the expected ELF header.  */
 #define ELF32_CLASS ELFCLASS32
@@ -1859,6 +1900,31 @@ open_verify (const char *name, int fd,
 
 	    break;
 	  }
+	else if (ph->p_type == PT_DYNAMIC)
+	  {
+	    off64_t pos = ph->p_offset;
+	    off64_t end = pos + ph->p_filesz;
+
+	    while (pos < end)
+	      {
+		ElfW (Dyn) entry;
+		ssize_t bytes = __pread64_nocancel (fd, &entry,
+						    sizeof (ElfW (Dyn)), pos);
+		if (bytes != sizeof (entry))
+		  goto read_error;
+		pos += bytes;
+
+		switch (entry.d_tag)
+		  {
+		  case DT_GNU_FLAGS_1:
+		    *has_gnu_unique = entry.d_un.d_val & DF_GNU_1_UNIQUE;
+		    /* fallthrough  */
+		  case DT_NULL:
+		    break;
+		  }
+	      }
+
+	  }
       free (abi_note_malloced);
     }
 
@@ -1876,7 +1942,7 @@ static int
 open_path (const char *name, size_t namelen, int mode,
 	   struct r_search_path_struct *sps, char **realname,
 	   struct filebuf *fbp, struct link_map *loader, int whatcode,
-	   bool *found_other_class)
+	   bool *found_other_class, bool *has_gnu_unique)
 {
   struct r_search_path_elem **dirs = sps->dirs;
   char *buf;
@@ -1930,7 +1996,7 @@ open_path (const char *name, size_t namelen, int mode,
 	    _dl_debug_printf ("  trying file=%s\n", buf);
 
 	  fd = open_verify (buf, -1, fbp, loader, whatcode, mode,
-			    found_other_class, false);
+			    found_other_class, has_gnu_unique, false);
 	  if (this_dir->status[cnt] == unknown)
 	    {
 	      if (fd != -1)
@@ -2024,6 +2090,37 @@ open_path (const char *name, size_t namelen, int mode,
   return -1;
 }
 
+/* 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)
@@ -2077,12 +2174,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;
     }
 
@@ -2132,6 +2259,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   /* Will be true if we found a DSO which is of the other ELF class.  */
   bool found_other_class = false;
+  /* Will be true if the DSO has a DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE.  */
+  bool has_gnu_unique = false;
 
   if (strchr (name, '/') == NULL)
     {
@@ -2161,7 +2290,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		fd = open_path (name, namelen, mode,
 				&l->l_rpath_dirs,
 				&realname, &fb, loader, LA_SER_RUNPATH,
-				&found_other_class);
+				&found_other_class, &has_gnu_unique);
 		if (fd != -1)
 		  break;
 
@@ -2177,7 +2306,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	    fd = open_path (name, namelen, mode,
 			    &main_map->l_rpath_dirs,
 			    &realname, &fb, loader ?: main_map, LA_SER_RUNPATH,
-			    &found_other_class);
+			    &found_other_class, &has_gnu_unique);
 	}
 
       /* Try the LD_LIBRARY_PATH environment variable.  */
@@ -2185,7 +2314,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	fd = open_path (name, namelen, mode, &__rtld_env_path_list,
 			&realname, &fb,
 			loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
-			LA_SER_LIBPATH, &found_other_class);
+			LA_SER_LIBPATH, &found_other_class, &has_gnu_unique);
 
       /* Look at the RUNPATH information for this binary.  */
       if (fd == -1 && loader != NULL
@@ -2193,7 +2322,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 			  DT_RUNPATH, "RUNPATH"))
 	fd = open_path (name, namelen, mode,
 			&loader->l_runpath_dirs, &realname, &fb, loader,
-			LA_SER_RUNPATH, &found_other_class);
+			LA_SER_RUNPATH, &found_other_class, &has_gnu_unique);
 
       if (fd == -1)
         {
@@ -2203,7 +2332,7 @@ _dl_map_object (struct link_map *loader, const char *name,
               fd = open_verify (realname, fd,
                                 &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
                                 LA_SER_CONFIG, mode, &found_other_class,
-                                false);
+                                &has_gnu_unique, false);
               if (fd == -1)
                 free (realname);
             }
@@ -2257,7 +2386,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		  fd = open_verify (cached, -1,
 				    &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
 				    LA_SER_CONFIG, mode, &found_other_class,
-				    false);
+				    &has_gnu_unique, false);
 		  if (__glibc_likely (fd != -1))
 		    realname = cached;
 		  else
@@ -2273,7 +2402,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 	      || __glibc_likely (!(l->l_flags_1 & DF_1_NODEFLIB)))
 	  && __rtld_search_dirs.dirs != (void *) -1)
 	fd = open_path (name, namelen, mode, &__rtld_search_dirs,
-			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class);
+			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class,
+			&has_gnu_unique);
 
       /* Add another newline when we are tracing the library loading.  */
       if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
@@ -2291,7 +2421,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	{
 	  fd = open_verify (realname, -1, &fb,
 			    loader ?: GL(dl_ns)[nsid]._ns_loaded, 0, mode,
-			    &found_other_class, true);
+			    &found_other_class, &has_gnu_unique, true);
 	  if (__glibc_unlikely (fd == -1))
 	    free (realname);
 	}
@@ -2352,7 +2482,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   void *stack_end = __libc_stack_end;
   return _dl_map_object_from_fd (name, origname, fd, &fb, realname, loader,
-				 type, mode, &stack_end, nsid);
+				 type, mode, &stack_end, has_gnu_unique, nsid);
 }
 
 struct add_path_state
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index eea217eb28..bac84753aa 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,25 @@ _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 (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 = _dl_find_proxy (undef_map->l_ns,
+					       current_value.m->l_name);
+      if (proxy != NULL)
+	current_value.m = proxy;
+      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
@@ -935,8 +950,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 +959,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);
 }
 
 
diff --git a/elf/dl-object.c b/elf/dl-object.c
index 1875599eb2..1d8fd6332c 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), 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.  */
diff --git a/elf/dl-open.c b/elf/dl-open.c
index ec386626f9..aa8b041743 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
@@ -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).  */
@@ -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,
@@ -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))
     {
@@ -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;
     }
 
@@ -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)
@@ -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)
@@ -792,10 +872,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);
+    }
 }
 
 void *
@@ -884,8 +981,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
diff --git a/elf/dl-sym.c b/elf/dl-sym.c
index de5769f926..e3955e7a51 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
@@ -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);
     }
diff --git a/elf/rtld.c b/elf/rtld.c
index d733359eaf..148a3d4f18 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -660,7 +660,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);
 }
diff --git a/elf/tst-tls-ie-dlmopen.c b/elf/tst-tls-ie-dlmopen.c
index a579d72d2d..bca522a90b 100644
--- a/elf/tst-tls-ie-dlmopen.c
+++ b/elf/tst-tls-ie-dlmopen.c
@@ -52,8 +52,9 @@ blocked_thread_func (void *closure)
 static void *
 load_and_access (Lmid_t lmid, const char *mod, const char *func)
 {
-  /* Load module with TLS.  */
-  void *p = xdlmopen (lmid, mod, RTLD_NOW);
+  /* Load module with TLS, the RTLD_ISOLATE is to force the libc.so to be
+     loaded in a separated namespace.  */
+  void *p = xdlmopen (lmid, mod, RTLD_NOW | RTLD_ISOLATE);
   /* Access the TLS variable to ensure it is allocated.  */
   void (*f) (void) = (void (*) (void))xdlsym (p, func);
   f ();
@@ -95,7 +96,7 @@ do_test (void)
      than 1024 bytes are available (exact number depends on TLS optimizations
      and the libc TLS use).  */
   printf ("The next dlmopen should fail...\n");
-  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW);
+  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW | RTLD_ISOLATE);
   if (p != NULL)
     FAIL_EXIT1 ("error: expected dlmopen to fail because there is "
 		"not enough surplus static TLS.\n");
diff --git a/extra-lib.mk b/extra-lib.mk
index 9051958ec0..38184adae1 100644
--- a/extra-lib.mk
+++ b/extra-lib.mk
@@ -102,9 +102,11 @@ $(objpfx)$(lib).so: $(firstword $($(lib)-map) \
 					    $(filter $(lib).map, \
 						     $(version-maps))))
 ifneq ($(ld-zunique),yes)
+ifneq ($(lib),libpthread)
 $(objpfx)$(lib).so: $(common-objpfx)/elf/dynamic-notes.os
 endif
 endif
+endif
 
 endif
 
diff --git a/htl/Makefile b/htl/Makefile
index c2a25dcd79..ce6d9a8cd1 100644
--- a/htl/Makefile
+++ b/htl/Makefile
@@ -202,9 +202,10 @@ $(inst_libdir)/libpthread_syms.a: $(srcdir)/libpthread_syms.a $(+force)
 libc-link.so = $(common-objpfx)libc.so
 
 extra-B-pthread.so = -B$(common-objpfx)htl/
+# Turn off DF_GNU_1_UNIQUE for libpthread now that it's a stub.
 LDFLAGS-pthread.so = -Wl,--enable-new-dtags,-z,nodelete,-z,initfirst
 ifeq ($(ld-zunique),yes)
-LDFLAGS-pthread.so += -Wl,-z,unique
+LDFLAGS-pthread.so += -Wl,-z,nounique
 endif
 
 include ../Rules
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.  */
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 22f3fea436..f49cbfc3ae 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)
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.  */


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [glibc/azanella/rtld-shared] elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
@ 2021-08-09 20:31 Adhemerval Zanella
  0 siblings, 0 replies; 6+ messages in thread
From: Adhemerval Zanella @ 2021-08-09 20:31 UTC (permalink / raw)
  To: glibc-cvs

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=9b0d6d16f381647f3f847caff13ecdb04329576e

commit 9b0d6d16f381647f3f847caff13ecdb04329576e
Author: Vivek Das Mohapatra <vivek@collabora.com>
Date:   Tue Aug 3 10:22:22 2021 -0300

    elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
    
    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
    
    tst-tls-ie-dlmopen checks to see that new namespaces consume
    TLS memory as expected: This does not happen when new namespaces
    share the same libc instance (since TLS is allocated only when
    a new libc instance insitialises its threading infrastructure).
    
    Adding RTLD_ISOLATE to the dlmopen flags in the test restores
    the old behaviour which allows the test to check what it
    actually needs to.
    
    Checked on x86_64-linux-gnu and i686-linux-gnu.
    
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

Diff:
---
 NEWS                       |  10 ++-
 bits/dlfcn.h               |  10 +++
 elf/dl-close.c             |  41 ++++++----
 elf/dl-deps.c              |  17 ++++
 elf/dl-fini.c              |   6 +-
 elf/dl-init.c              |   4 +-
 elf/dl-load.c              | 192 +++++++++++++++++++++++++++++++++++++--------
 elf/dl-lookup.c            |  23 +++++-
 elf/dl-object.c            |  78 ++++++++++++++++++
 elf/dl-open.c              | 112 ++++++++++++++++++++++++--
 elf/dl-sym.c               |  14 ++++
 elf/rtld.c                 |   2 +-
 elf/tst-tls-ie-dlmopen.c   |   7 +-
 extra-lib.mk               |   2 +
 htl/Makefile               |   3 +-
 include/link.h             |   6 +-
 sysdeps/generic/ldsodefs.h |   7 ++
 sysdeps/mips/bits/dlfcn.h  |  10 +++
 18 files changed, 474 insertions(+), 70 deletions(-)

diff --git a/NEWS b/NEWS
index 79c895e382..593d064fd5 100644
--- a/NEWS
+++ b/NEWS
@@ -9,7 +9,15 @@ Version 2.35
 
 Major new features:
 
-  [Add new features here]
+* The glibc now handle objects marked with the GNU extension DF_GNU_1_UNIQUE,
+  where the DSO should be loaded only once and shared over namespaces.
+
+* The dlmopen interface now supports the RTLD_SHARED flag as a GNU extension.
+  If an object is loaded with the new RTLD_SHARED flag the glibc ensures
+  that a "master" copy exists (and is flagged as no-delete) in the
+  main namespace and a thin wrapper or clone is placed in the target
+  namespace.  This allows to shared DSO that can be loaded only once with
+  multiple namespaces.
 
 Deprecated and removed features, and other changes affecting compatibility:
 
diff --git a/bits/dlfcn.h b/bits/dlfcn.h
index f3bc63e958..26d715d813 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
+   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 f39001cab9..03ff2c1c4f 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -286,8 +286,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,
@@ -363,7 +364,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.  */
@@ -691,8 +694,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
@@ -732,19 +737,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.  */
-
-	  struct libname_list *lnp = imap->l_libname;
-	  do
+	  /* Skip structures borrowed by proxies from the real map.  */
+	  if (!imap->l_proxy)
 	    {
-	      struct libname_list *this = lnp;
-	      lnp = lnp->next;
-	      if (!this->dont_free)
-		free (this);
+	      /* 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);
 	    }
-	  while (lnp != NULL);
 
 	  /* Remove the searchlists.  */
 	  free (imap->l_initfini);
diff --git a/elf/dl-deps.c b/elf/dl-deps.c
index 087a49b212..303d0f42c0 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=%d\n\n",
+			  args->aux->l_name, args->aux->l_ns,
+			  args->aux->l_real->l_relocated);
+
+      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
diff --git a/elf/dl-fini.c b/elf/dl-fini.c
index 6dbdfe4b3e..51f45be9fe 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;
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
diff --git a/elf/dl-load.c b/elf/dl-load.c
index fb23ba632b..83c05410a7 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -854,7 +854,6 @@ _dl_init_paths (const char *llp, const char *source,
     __rtld_env_path_list.dirs = (void *) -1;
 }
 
-
 /* Process PT_GNU_PROPERTY program header PH in module L after
    PT_LOAD segments are mapped.  Only one NT_GNU_PROPERTY_TYPE_0
    note is handled which contains processor specific properties.
@@ -931,14 +930,12 @@ _dl_process_pt_gnu_property (struct link_map *l, int fd, const ElfW(Phdr) *ph)
 /* Map in the shared object NAME, actually located in REALNAME, and already
    opened on FD.  */
 
-#ifndef EXTERNAL_MAP_FROM_FD
 static
-#endif
 struct link_map *
 _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 			struct filebuf *fbp, char *realname,
 			struct link_map *loader, int l_type, int mode,
-			void **stack_endp, Lmid_t nsid)
+			void **stack_endp, bool has_gnu_unique, Lmid_t nsid)
 {
   struct link_map *l = NULL;
   const ElfW(Ehdr) *header;
@@ -1037,6 +1034,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
+     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);
+
+	    /* NB: 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
@@ -1062,8 +1085,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 neither try to
+	     call auditing functions if the namespace is currently empty.
+	     This can happen when opening the first DSO in a new namespace.
+	   */
+	  if ((head != NULL) && !head->l_auditing)
 	    {
 	      struct audit_ifaces *afct = GLRO(dl_audit);
 	      for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
@@ -1089,6 +1115,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;
+	}
+    }
+
+  /* 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)
+        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 +1158,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.  */
@@ -1583,7 +1621,8 @@ print_search_path (struct r_search_path_elem **list,
 static int
 open_verify (const char *name, int fd,
              struct filebuf *fbp, struct link_map *loader,
-	     int whatcode, int mode, bool *found_other_class, bool free_name)
+	     int whatcode, int mode, bool *found_other_class,
+	     bool *has_gnu_unique, bool free_name)
 {
   /* This is the expected ELF header.  */
 #define ELF32_CLASS ELFCLASS32
@@ -1862,6 +1901,31 @@ open_verify (const char *name, int fd,
 
 	    break;
 	  }
+	else if (ph->p_type == PT_DYNAMIC)
+	  {
+	    off64_t pos = ph->p_offset;
+	    off64_t end = pos + ph->p_filesz;
+
+	    while (pos < end)
+	      {
+		ElfW (Dyn) entry;
+		ssize_t bytes = __pread64_nocancel (fd, &entry,
+						    sizeof (ElfW (Dyn)), pos);
+		if (bytes != sizeof (entry))
+		  goto read_error;
+		pos += bytes;
+
+		switch (entry.d_tag)
+		  {
+		  case DT_GNU_FLAGS_1:
+		    *has_gnu_unique = entry.d_un.d_val & DF_GNU_1_UNIQUE;
+		    /* fallthrough  */
+		  case DT_NULL:
+		    break;
+		  }
+	      }
+
+	  }
       free (abi_note_malloced);
     }
 
@@ -1879,7 +1943,7 @@ static int
 open_path (const char *name, size_t namelen, int mode,
 	   struct r_search_path_struct *sps, char **realname,
 	   struct filebuf *fbp, struct link_map *loader, int whatcode,
-	   bool *found_other_class)
+	   bool *found_other_class, bool *has_gnu_unique)
 {
   struct r_search_path_elem **dirs = sps->dirs;
   char *buf;
@@ -1933,7 +1997,7 @@ open_path (const char *name, size_t namelen, int mode,
 	    _dl_debug_printf ("  trying file=%s\n", buf);
 
 	  fd = open_verify (buf, -1, fbp, loader, whatcode, mode,
-			    found_other_class, false);
+			    found_other_class, has_gnu_unique, false);
 	  if (this_dir->status[cnt] == unknown)
 	    {
 	      if (fd != -1)
@@ -2027,6 +2091,37 @@ open_path (const char *name, size_t namelen, int mode,
   return -1;
 }
 
+/* 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)
@@ -2080,12 +2175,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;
     }
 
@@ -2135,6 +2260,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   /* Will be true if we found a DSO which is of the other ELF class.  */
   bool found_other_class = false;
+  /* Will be true if the DSO has a DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE.  */
+  bool has_gnu_unique = false;
 
   if (strchr (name, '/') == NULL)
     {
@@ -2164,7 +2291,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		fd = open_path (name, namelen, mode,
 				&l->l_rpath_dirs,
 				&realname, &fb, loader, LA_SER_RUNPATH,
-				&found_other_class);
+				&found_other_class, &has_gnu_unique);
 		if (fd != -1)
 		  break;
 
@@ -2180,7 +2307,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	    fd = open_path (name, namelen, mode,
 			    &main_map->l_rpath_dirs,
 			    &realname, &fb, loader ?: main_map, LA_SER_RUNPATH,
-			    &found_other_class);
+			    &found_other_class, &has_gnu_unique);
 	}
 
       /* Try the LD_LIBRARY_PATH environment variable.  */
@@ -2188,7 +2315,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	fd = open_path (name, namelen, mode, &__rtld_env_path_list,
 			&realname, &fb,
 			loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
-			LA_SER_LIBPATH, &found_other_class);
+			LA_SER_LIBPATH, &found_other_class, &has_gnu_unique);
 
       /* Look at the RUNPATH information for this binary.  */
       if (fd == -1 && loader != NULL
@@ -2196,7 +2323,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 			  DT_RUNPATH, "RUNPATH"))
 	fd = open_path (name, namelen, mode,
 			&loader->l_runpath_dirs, &realname, &fb, loader,
-			LA_SER_RUNPATH, &found_other_class);
+			LA_SER_RUNPATH, &found_other_class, &has_gnu_unique);
 
       if (fd == -1)
         {
@@ -2206,7 +2333,7 @@ _dl_map_object (struct link_map *loader, const char *name,
               fd = open_verify (realname, fd,
                                 &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
                                 LA_SER_CONFIG, mode, &found_other_class,
-                                false);
+                                &has_gnu_unique, false);
               if (fd == -1)
                 free (realname);
             }
@@ -2260,7 +2387,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		  fd = open_verify (cached, -1,
 				    &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
 				    LA_SER_CONFIG, mode, &found_other_class,
-				    false);
+				    &has_gnu_unique, false);
 		  if (__glibc_likely (fd != -1))
 		    realname = cached;
 		  else
@@ -2276,7 +2403,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 	      || __glibc_likely (!(l->l_flags_1 & DF_1_NODEFLIB)))
 	  && __rtld_search_dirs.dirs != (void *) -1)
 	fd = open_path (name, namelen, mode, &__rtld_search_dirs,
-			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class);
+			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class,
+			&has_gnu_unique);
 
       /* Add another newline when we are tracing the library loading.  */
       if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
@@ -2294,7 +2422,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	{
 	  fd = open_verify (realname, -1, &fb,
 			    loader ?: GL(dl_ns)[nsid]._ns_loaded, 0, mode,
-			    &found_other_class, true);
+			    &found_other_class, &has_gnu_unique, true);
 	  if (__glibc_unlikely (fd == -1))
 	    free (realname);
 	}
@@ -2355,7 +2483,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   void *stack_end = __libc_stack_end;
   return _dl_map_object_from_fd (name, origname, fd, &fb, realname, loader,
-				 type, mode, &stack_end, nsid);
+				 type, mode, &stack_end, has_gnu_unique, nsid);
 }
 
 struct add_path_state
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index eea217eb28..bac84753aa 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,25 @@ _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 (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 = _dl_find_proxy (undef_map->l_ns,
+					       current_value.m->l_name);
+      if (proxy != NULL)
+	current_value.m = proxy;
+      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
@@ -935,8 +950,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 +959,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);
 }
 
 
diff --git a/elf/dl-object.c b/elf/dl-object.c
index 1875599eb2..1d8fd6332c 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), 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.  */
diff --git a/elf/dl-open.c b/elf/dl-open.c
index ec386626f9..aa8b041743 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
@@ -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).  */
@@ -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,
@@ -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))
     {
@@ -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;
     }
 
@@ -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)
@@ -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)
@@ -792,10 +872,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);
+    }
 }
 
 void *
@@ -884,8 +981,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
diff --git a/elf/dl-sym.c b/elf/dl-sym.c
index de5769f926..e3955e7a51 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
@@ -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);
     }
diff --git a/elf/rtld.c b/elf/rtld.c
index d733359eaf..148a3d4f18 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -660,7 +660,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);
 }
diff --git a/elf/tst-tls-ie-dlmopen.c b/elf/tst-tls-ie-dlmopen.c
index a579d72d2d..bca522a90b 100644
--- a/elf/tst-tls-ie-dlmopen.c
+++ b/elf/tst-tls-ie-dlmopen.c
@@ -52,8 +52,9 @@ blocked_thread_func (void *closure)
 static void *
 load_and_access (Lmid_t lmid, const char *mod, const char *func)
 {
-  /* Load module with TLS.  */
-  void *p = xdlmopen (lmid, mod, RTLD_NOW);
+  /* Load module with TLS, the RTLD_ISOLATE is to force the libc.so to be
+     loaded in a separated namespace.  */
+  void *p = xdlmopen (lmid, mod, RTLD_NOW | RTLD_ISOLATE);
   /* Access the TLS variable to ensure it is allocated.  */
   void (*f) (void) = (void (*) (void))xdlsym (p, func);
   f ();
@@ -95,7 +96,7 @@ do_test (void)
      than 1024 bytes are available (exact number depends on TLS optimizations
      and the libc TLS use).  */
   printf ("The next dlmopen should fail...\n");
-  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW);
+  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW | RTLD_ISOLATE);
   if (p != NULL)
     FAIL_EXIT1 ("error: expected dlmopen to fail because there is "
 		"not enough surplus static TLS.\n");
diff --git a/extra-lib.mk b/extra-lib.mk
index 9051958ec0..38184adae1 100644
--- a/extra-lib.mk
+++ b/extra-lib.mk
@@ -102,9 +102,11 @@ $(objpfx)$(lib).so: $(firstword $($(lib)-map) \
 					    $(filter $(lib).map, \
 						     $(version-maps))))
 ifneq ($(ld-zunique),yes)
+ifneq ($(lib),libpthread)
 $(objpfx)$(lib).so: $(common-objpfx)/elf/dynamic-notes.os
 endif
 endif
+endif
 
 endif
 
diff --git a/htl/Makefile b/htl/Makefile
index c2a25dcd79..ce6d9a8cd1 100644
--- a/htl/Makefile
+++ b/htl/Makefile
@@ -202,9 +202,10 @@ $(inst_libdir)/libpthread_syms.a: $(srcdir)/libpthread_syms.a $(+force)
 libc-link.so = $(common-objpfx)libc.so
 
 extra-B-pthread.so = -B$(common-objpfx)htl/
+# Turn off DF_GNU_1_UNIQUE for libpthread now that it's a stub.
 LDFLAGS-pthread.so = -Wl,--enable-new-dtags,-z,nodelete,-z,initfirst
 ifeq ($(ld-zunique),yes)
-LDFLAGS-pthread.so += -Wl,-z,unique
+LDFLAGS-pthread.so += -Wl,-z,nounique
 endif
 
 include ../Rules
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.  */
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 22f3fea436..f49cbfc3ae 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)
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.  */


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [glibc/azanella/rtld-shared] elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
@ 2021-08-09 20:17 Adhemerval Zanella
  0 siblings, 0 replies; 6+ messages in thread
From: Adhemerval Zanella @ 2021-08-09 20:17 UTC (permalink / raw)
  To: glibc-cvs

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=797e156ebe274ea0188a1697fb4224d5e7dd3e88

commit 797e156ebe274ea0188a1697fb4224d5e7dd3e88
Author: Vivek Das Mohapatra <vivek@collabora.com>
Date:   Tue Aug 3 10:22:22 2021 -0300

    elf: Implement dlmopen RTLD_SHARED flag (BZ #22745)
    
    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
    
    tst-tls-ie-dlmopen checks to see that new namespaces consume
    TLS memory as expected: This does not happen when new namespaces
    share the same libc instance (since TLS is allocated only when
    a new libc instance insitialises its threading infrastructure).
    
    Adding RTLD_ISOLATE to the dlmopen flags in the test restores
    the old behaviour which allows the test to check what it
    actually needs to.
    
    Checked on x86_64-linux-gnu and i686-linux-gnu.
    
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

Diff:
---
 bits/dlfcn.h               |  10 +++
 elf/dl-close.c             |  41 ++++++----
 elf/dl-deps.c              |  17 ++++
 elf/dl-fini.c              |   6 +-
 elf/dl-init.c              |   4 +-
 elf/dl-load.c              | 192 +++++++++++++++++++++++++++++++++++++--------
 elf/dl-lookup.c            |  23 +++++-
 elf/dl-object.c            |  78 ++++++++++++++++++
 elf/dl-open.c              | 112 ++++++++++++++++++++++++--
 elf/dl-sym.c               |  14 ++++
 elf/rtld.c                 |   2 +-
 elf/tst-tls-ie-dlmopen.c   |   7 +-
 extra-lib.mk               |   2 +
 htl/Makefile               |   3 +-
 include/link.h             |   6 +-
 sysdeps/generic/ldsodefs.h |   7 ++
 sysdeps/mips/bits/dlfcn.h  |  10 +++
 17 files changed, 465 insertions(+), 69 deletions(-)

diff --git a/bits/dlfcn.h b/bits/dlfcn.h
index f3bc63e958..26d715d813 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
+   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 f39001cab9..03ff2c1c4f 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -286,8 +286,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,
@@ -363,7 +364,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.  */
@@ -691,8 +694,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
@@ -732,19 +737,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.  */
-
-	  struct libname_list *lnp = imap->l_libname;
-	  do
+	  /* Skip structures borrowed by proxies from the real map.  */
+	  if (!imap->l_proxy)
 	    {
-	      struct libname_list *this = lnp;
-	      lnp = lnp->next;
-	      if (!this->dont_free)
-		free (this);
+	      /* 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);
 	    }
-	  while (lnp != NULL);
 
 	  /* Remove the searchlists.  */
 	  free (imap->l_initfini);
diff --git a/elf/dl-deps.c b/elf/dl-deps.c
index 087a49b212..303d0f42c0 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=%d\n\n",
+			  args->aux->l_name, args->aux->l_ns,
+			  args->aux->l_real->l_relocated);
+
+      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
diff --git a/elf/dl-fini.c b/elf/dl-fini.c
index 6dbdfe4b3e..51f45be9fe 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;
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
diff --git a/elf/dl-load.c b/elf/dl-load.c
index fb23ba632b..83c05410a7 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -854,7 +854,6 @@ _dl_init_paths (const char *llp, const char *source,
     __rtld_env_path_list.dirs = (void *) -1;
 }
 
-
 /* Process PT_GNU_PROPERTY program header PH in module L after
    PT_LOAD segments are mapped.  Only one NT_GNU_PROPERTY_TYPE_0
    note is handled which contains processor specific properties.
@@ -931,14 +930,12 @@ _dl_process_pt_gnu_property (struct link_map *l, int fd, const ElfW(Phdr) *ph)
 /* Map in the shared object NAME, actually located in REALNAME, and already
    opened on FD.  */
 
-#ifndef EXTERNAL_MAP_FROM_FD
 static
-#endif
 struct link_map *
 _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 			struct filebuf *fbp, char *realname,
 			struct link_map *loader, int l_type, int mode,
-			void **stack_endp, Lmid_t nsid)
+			void **stack_endp, bool has_gnu_unique, Lmid_t nsid)
 {
   struct link_map *l = NULL;
   const ElfW(Ehdr) *header;
@@ -1037,6 +1034,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
+     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);
+
+	    /* NB: 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
@@ -1062,8 +1085,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 neither try to
+	     call auditing functions if the namespace is currently empty.
+	     This can happen when opening the first DSO in a new namespace.
+	   */
+	  if ((head != NULL) && !head->l_auditing)
 	    {
 	      struct audit_ifaces *afct = GLRO(dl_audit);
 	      for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
@@ -1089,6 +1115,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;
+	}
+    }
+
+  /* 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)
+        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 +1158,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.  */
@@ -1583,7 +1621,8 @@ print_search_path (struct r_search_path_elem **list,
 static int
 open_verify (const char *name, int fd,
              struct filebuf *fbp, struct link_map *loader,
-	     int whatcode, int mode, bool *found_other_class, bool free_name)
+	     int whatcode, int mode, bool *found_other_class,
+	     bool *has_gnu_unique, bool free_name)
 {
   /* This is the expected ELF header.  */
 #define ELF32_CLASS ELFCLASS32
@@ -1862,6 +1901,31 @@ open_verify (const char *name, int fd,
 
 	    break;
 	  }
+	else if (ph->p_type == PT_DYNAMIC)
+	  {
+	    off64_t pos = ph->p_offset;
+	    off64_t end = pos + ph->p_filesz;
+
+	    while (pos < end)
+	      {
+		ElfW (Dyn) entry;
+		ssize_t bytes = __pread64_nocancel (fd, &entry,
+						    sizeof (ElfW (Dyn)), pos);
+		if (bytes != sizeof (entry))
+		  goto read_error;
+		pos += bytes;
+
+		switch (entry.d_tag)
+		  {
+		  case DT_GNU_FLAGS_1:
+		    *has_gnu_unique = entry.d_un.d_val & DF_GNU_1_UNIQUE;
+		    /* fallthrough  */
+		  case DT_NULL:
+		    break;
+		  }
+	      }
+
+	  }
       free (abi_note_malloced);
     }
 
@@ -1879,7 +1943,7 @@ static int
 open_path (const char *name, size_t namelen, int mode,
 	   struct r_search_path_struct *sps, char **realname,
 	   struct filebuf *fbp, struct link_map *loader, int whatcode,
-	   bool *found_other_class)
+	   bool *found_other_class, bool *has_gnu_unique)
 {
   struct r_search_path_elem **dirs = sps->dirs;
   char *buf;
@@ -1933,7 +1997,7 @@ open_path (const char *name, size_t namelen, int mode,
 	    _dl_debug_printf ("  trying file=%s\n", buf);
 
 	  fd = open_verify (buf, -1, fbp, loader, whatcode, mode,
-			    found_other_class, false);
+			    found_other_class, has_gnu_unique, false);
 	  if (this_dir->status[cnt] == unknown)
 	    {
 	      if (fd != -1)
@@ -2027,6 +2091,37 @@ open_path (const char *name, size_t namelen, int mode,
   return -1;
 }
 
+/* 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)
@@ -2080,12 +2175,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;
     }
 
@@ -2135,6 +2260,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   /* Will be true if we found a DSO which is of the other ELF class.  */
   bool found_other_class = false;
+  /* Will be true if the DSO has a DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE.  */
+  bool has_gnu_unique = false;
 
   if (strchr (name, '/') == NULL)
     {
@@ -2164,7 +2291,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		fd = open_path (name, namelen, mode,
 				&l->l_rpath_dirs,
 				&realname, &fb, loader, LA_SER_RUNPATH,
-				&found_other_class);
+				&found_other_class, &has_gnu_unique);
 		if (fd != -1)
 		  break;
 
@@ -2180,7 +2307,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	    fd = open_path (name, namelen, mode,
 			    &main_map->l_rpath_dirs,
 			    &realname, &fb, loader ?: main_map, LA_SER_RUNPATH,
-			    &found_other_class);
+			    &found_other_class, &has_gnu_unique);
 	}
 
       /* Try the LD_LIBRARY_PATH environment variable.  */
@@ -2188,7 +2315,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	fd = open_path (name, namelen, mode, &__rtld_env_path_list,
 			&realname, &fb,
 			loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
-			LA_SER_LIBPATH, &found_other_class);
+			LA_SER_LIBPATH, &found_other_class, &has_gnu_unique);
 
       /* Look at the RUNPATH information for this binary.  */
       if (fd == -1 && loader != NULL
@@ -2196,7 +2323,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 			  DT_RUNPATH, "RUNPATH"))
 	fd = open_path (name, namelen, mode,
 			&loader->l_runpath_dirs, &realname, &fb, loader,
-			LA_SER_RUNPATH, &found_other_class);
+			LA_SER_RUNPATH, &found_other_class, &has_gnu_unique);
 
       if (fd == -1)
         {
@@ -2206,7 +2333,7 @@ _dl_map_object (struct link_map *loader, const char *name,
               fd = open_verify (realname, fd,
                                 &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
                                 LA_SER_CONFIG, mode, &found_other_class,
-                                false);
+                                &has_gnu_unique, false);
               if (fd == -1)
                 free (realname);
             }
@@ -2260,7 +2387,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 		  fd = open_verify (cached, -1,
 				    &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
 				    LA_SER_CONFIG, mode, &found_other_class,
-				    false);
+				    &has_gnu_unique, false);
 		  if (__glibc_likely (fd != -1))
 		    realname = cached;
 		  else
@@ -2276,7 +2403,8 @@ _dl_map_object (struct link_map *loader, const char *name,
 	      || __glibc_likely (!(l->l_flags_1 & DF_1_NODEFLIB)))
 	  && __rtld_search_dirs.dirs != (void *) -1)
 	fd = open_path (name, namelen, mode, &__rtld_search_dirs,
-			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class);
+			&realname, &fb, l, LA_SER_DEFAULT, &found_other_class,
+			&has_gnu_unique);
 
       /* Add another newline when we are tracing the library loading.  */
       if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
@@ -2294,7 +2422,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 	{
 	  fd = open_verify (realname, -1, &fb,
 			    loader ?: GL(dl_ns)[nsid]._ns_loaded, 0, mode,
-			    &found_other_class, true);
+			    &found_other_class, &has_gnu_unique, true);
 	  if (__glibc_unlikely (fd == -1))
 	    free (realname);
 	}
@@ -2355,7 +2483,7 @@ _dl_map_object (struct link_map *loader, const char *name,
 
   void *stack_end = __libc_stack_end;
   return _dl_map_object_from_fd (name, origname, fd, &fb, realname, loader,
-				 type, mode, &stack_end, nsid);
+				 type, mode, &stack_end, has_gnu_unique, nsid);
 }
 
 struct add_path_state
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index eea217eb28..bac84753aa 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,25 @@ _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 (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 = _dl_find_proxy (undef_map->l_ns,
+					       current_value.m->l_name);
+      if (proxy != NULL)
+	current_value.m = proxy;
+      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
@@ -935,8 +950,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 +959,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);
 }
 
 
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.  */
diff --git a/elf/dl-open.c b/elf/dl-open.c
index ec386626f9..aa8b041743 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
@@ -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).  */
@@ -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,
@@ -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))
     {
@@ -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;
     }
 
@@ -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)
@@ -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)
@@ -792,10 +872,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);
+    }
 }
 
 void *
@@ -884,8 +981,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
diff --git a/elf/dl-sym.c b/elf/dl-sym.c
index de5769f926..e3955e7a51 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
@@ -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);
     }
diff --git a/elf/rtld.c b/elf/rtld.c
index d733359eaf..148a3d4f18 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -660,7 +660,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);
 }
diff --git a/elf/tst-tls-ie-dlmopen.c b/elf/tst-tls-ie-dlmopen.c
index a579d72d2d..bca522a90b 100644
--- a/elf/tst-tls-ie-dlmopen.c
+++ b/elf/tst-tls-ie-dlmopen.c
@@ -52,8 +52,9 @@ blocked_thread_func (void *closure)
 static void *
 load_and_access (Lmid_t lmid, const char *mod, const char *func)
 {
-  /* Load module with TLS.  */
-  void *p = xdlmopen (lmid, mod, RTLD_NOW);
+  /* Load module with TLS, the RTLD_ISOLATE is to force the libc.so to be
+     loaded in a separated namespace.  */
+  void *p = xdlmopen (lmid, mod, RTLD_NOW | RTLD_ISOLATE);
   /* Access the TLS variable to ensure it is allocated.  */
   void (*f) (void) = (void (*) (void))xdlsym (p, func);
   f ();
@@ -95,7 +96,7 @@ do_test (void)
      than 1024 bytes are available (exact number depends on TLS optimizations
      and the libc TLS use).  */
   printf ("The next dlmopen should fail...\n");
-  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW);
+  void *p = dlmopen (LM_ID_BASE, "tst-tls-ie-mod4.so", RTLD_NOW | RTLD_ISOLATE);
   if (p != NULL)
     FAIL_EXIT1 ("error: expected dlmopen to fail because there is "
 		"not enough surplus static TLS.\n");
diff --git a/extra-lib.mk b/extra-lib.mk
index 9051958ec0..38184adae1 100644
--- a/extra-lib.mk
+++ b/extra-lib.mk
@@ -102,9 +102,11 @@ $(objpfx)$(lib).so: $(firstword $($(lib)-map) \
 					    $(filter $(lib).map, \
 						     $(version-maps))))
 ifneq ($(ld-zunique),yes)
+ifneq ($(lib),libpthread)
 $(objpfx)$(lib).so: $(common-objpfx)/elf/dynamic-notes.os
 endif
 endif
+endif
 
 endif
 
diff --git a/htl/Makefile b/htl/Makefile
index c2a25dcd79..ce6d9a8cd1 100644
--- a/htl/Makefile
+++ b/htl/Makefile
@@ -202,9 +202,10 @@ $(inst_libdir)/libpthread_syms.a: $(srcdir)/libpthread_syms.a $(+force)
 libc-link.so = $(common-objpfx)libc.so
 
 extra-B-pthread.so = -B$(common-objpfx)htl/
+# Turn off DF_GNU_1_UNIQUE for libpthread now that it's a stub.
 LDFLAGS-pthread.so = -Wl,--enable-new-dtags,-z,nodelete,-z,initfirst
 ifeq ($(ld-zunique),yes)
-LDFLAGS-pthread.so += -Wl,-z,unique
+LDFLAGS-pthread.so += -Wl,-z,nounique
 endif
 
 include ../Rules
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.  */
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 22f3fea436..f49cbfc3ae 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)
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.  */


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2021-09-27 18:54 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-09 20:26 [glibc/azanella/rtld-shared] elf: Implement dlmopen RTLD_SHARED flag (BZ #22745) Adhemerval Zanella
  -- strict thread matches above, loose matches on Subject: below --
2021-09-27 18:54 Adhemerval Zanella
2021-08-10 13:42 Adhemerval Zanella
2021-08-10 12:43 Adhemerval Zanella
2021-08-09 20:31 Adhemerval Zanella
2021-08-09 20:17 Adhemerval Zanella

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).