djdelorie / rpms / glibc

Forked from rpms/glibc 3 years ago
Clone
a5ececf
commit f63b73814f74032c0e5d0a83300e3d864ef905e5
a5ececf
Author: Florian Weimer <fweimer@redhat.com>
a5ececf
Date:   Wed Nov 13 15:44:56 2019 +0100
a5ececf
a5ececf
    Remove all loaded objects if dlopen fails, ignoring NODELETE [BZ #20839]
a5ececf
    
a5ececf
    This introduces a “pending NODELETE” state in the link map, which is
a5ececf
    flipped to the persistent NODELETE state late in dlopen, via
a5ececf
    activate_nodelete.    During initial relocation, symbol binding
a5ececf
    records pending NODELETE state only.  dlclose ignores pending NODELETE
a5ececf
    state.  Taken together, this results that a partially completed dlopen
a5ececf
    is rolled back completely because new NODELETE mappings are unloaded.
a5ececf
    
a5ececf
    Tested on x86_64-linux-gnu and i386-linux-gnu.
a5ececf
    
a5ececf
    Change-Id: Ib2a3d86af6f92d75baca65431d74783ee0dbc292
a5ececf
a5ececf
Conflicts:
a5ececf
	elf/Makefile
a5ececf
	  (Usual test backport differences.)
a5ececf
a5ececf
diff --git a/elf/Makefile b/elf/Makefile
a5ececf
index b0a00a2b1252eee8..0cdb511a858cf274 100644
a5ececf
--- a/elf/Makefile
a5ececf
+++ b/elf/Makefile
a5ececf
@@ -192,7 +192,8 @@ tests += restest1 preloadtest loadfail multiload origtest resolvfail \
a5ececf
 	 tst-latepthread tst-tls-manydynamic tst-nodelete-dlclose \
a5ececf
 	 tst-debug1 tst-main1 tst-absolute-sym tst-absolute-zero tst-big-note \
a5ececf
 	 tst-unwind-ctor tst-unwind-main tst-audit13 \
a5ececf
-	 tst-sonamemove-link tst-sonamemove-dlopen tst-initfinilazyfail
a5ececf
+	 tst-sonamemove-link tst-sonamemove-dlopen tst-initfinilazyfail \
a5ececf
+	 tst-dlopenfail
a5ececf
 #	 reldep9
a5ececf
 tests-internal += loadtest unload unload2 circleload1 \
a5ececf
 	 neededtest neededtest2 neededtest3 neededtest4 \
a5ececf
@@ -284,7 +285,8 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
a5ececf
 		tst-absolute-zero-lib tst-big-note-lib tst-unwind-ctor-lib \
a5ececf
 		tst-audit13mod1 tst-sonamemove-linkmod1 \
a5ececf
 		tst-sonamemove-runmod1 tst-sonamemove-runmod2 \
a5ececf
-		tst-initlazyfailmod tst-finilazyfailmod
a5ececf
+		tst-initlazyfailmod tst-finilazyfailmod \
a5ececf
+		tst-dlopenfailmod1 tst-dlopenfaillinkmod tst-dlopenfailmod2
a5ececf
 # Most modules build with _ISOMAC defined, but those filtered out
a5ececf
 # depend on internal headers.
a5ececf
 modules-names-tests = $(filter-out ifuncmod% tst-libc_dlvsym-dso tst-tlsmod%,\
a5ececf
@@ -1566,3 +1568,13 @@ LDFLAGS-tst-initlazyfailmod.so = \
a5ececf
   -Wl,-z,lazy -Wl,--unresolved-symbols=ignore-all
a5ececf
 LDFLAGS-tst-finilazyfailmod.so = \
a5ececf
   -Wl,-z,lazy -Wl,--unresolved-symbols=ignore-all
a5ececf
+
a5ececf
+$(objpfx)tst-dlopenfail: $(libdl)
a5ececf
+$(objpfx)tst-dlopenfail.out: \
a5ececf
+  $(objpfx)tst-dlopenfailmod1.so $(objpfx)tst-dlopenfailmod2.so
a5ececf
+# Order matters here.  tst-dlopenfaillinkmod.so's soname ensures
a5ececf
+# a run-time loader failure.
a5ececf
+$(objpfx)tst-dlopenfailmod1.so: \
a5ececf
+  $(shared-thread-library) $(objpfx)tst-dlopenfaillinkmod.so
a5ececf
+LDFLAGS-tst-dlopenfaillinkmod.so = -Wl,-soname,tst-dlopenfail-missingmod.so
a5ececf
+$(objpfx)tst-dlopenfailmod2.so: $(shared-thread-library)
a5ececf
diff --git a/elf/dl-close.c b/elf/dl-close.c
a5ececf
index bdb9158eef5f0473..0ec9d58d095f24d7 100644
a5ececf
--- a/elf/dl-close.c
a5ececf
+++ b/elf/dl-close.c
a5ececf
@@ -168,14 +168,6 @@ _dl_close_worker (struct link_map *map, bool force)
a5ececf
   char done[nloaded];
a5ececf
   struct link_map *maps[nloaded];
a5ececf
 
a5ececf
-  /* Clear DF_1_NODELETE to force object deletion.  We don't need to touch
a5ececf
-     l_tls_dtor_count because forced object deletion only happens when an
a5ececf
-     error occurs during object load.  Destructor registration for TLS
a5ececf
-     non-POD objects should not have happened till then for this
a5ececf
-     object.  */
a5ececf
-  if (force)
a5ececf
-    map->l_flags_1 &= ~DF_1_NODELETE;
a5ececf
-
a5ececf
   /* Run over the list and assign indexes to the link maps and enter
a5ececf
      them into the MAPS array.  */
a5ececf
   int idx = 0;
a5ececf
@@ -205,7 +197,7 @@ _dl_close_worker (struct link_map *map, bool force)
a5ececf
       /* Check whether this object is still used.  */
a5ececf
       if (l->l_type == lt_loaded
a5ececf
 	  && l->l_direct_opencount == 0
a5ececf
-	  && (l->l_flags_1 & DF_1_NODELETE) == 0
a5ececf
+	  && l->l_nodelete != link_map_nodelete_active
a5ececf
 	  /* See CONCURRENCY NOTES in cxa_thread_atexit_impl.c to know why
a5ececf
 	     acquire is sufficient and correct.  */
a5ececf
 	  && atomic_load_acquire (&l->l_tls_dtor_count) == 0
a5ececf
@@ -288,7 +280,7 @@ _dl_close_worker (struct link_map *map, bool force)
a5ececf
       if (!used[i])
a5ececf
 	{
a5ececf
 	  assert (imap->l_type == lt_loaded
a5ececf
-		  && (imap->l_flags_1 & DF_1_NODELETE) == 0);
a5ececf
+		  && imap->l_nodelete != link_map_nodelete_active);
a5ececf
 
a5ececf
 	  /* Call its termination function.  Do not do it for
a5ececf
 	     half-cooked objects.  Temporarily disable exception
a5ececf
@@ -828,7 +820,7 @@ _dl_close (void *_map)
a5ececf
      before we took the lock. There is no way to detect this (see below)
a5ececf
      so we proceed assuming this isn't the case.  First see whether we
a5ececf
      can remove the object at all.  */
a5ececf
-  if (__glibc_unlikely (map->l_flags_1 & DF_1_NODELETE))
a5ececf
+  if (__glibc_unlikely (map->l_nodelete == link_map_nodelete_active))
a5ececf
     {
a5ececf
       /* Nope.  Do nothing.  */
a5ececf
       __rtld_lock_unlock_recursive (GL(dl_load_lock));
a5ececf
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
a5ececf
index 587d9f526183f683..1de29d93611125e3 100644
a5ececf
--- a/elf/dl-lookup.c
a5ececf
+++ b/elf/dl-lookup.c
a5ececf
@@ -192,9 +192,10 @@ enter_unique_sym (struct unique_sym *table, size_t size,
a5ececf
    Return the matching symbol in RESULT.  */
a5ececf
 static void
a5ececf
 do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
a5ececf
-		  const struct link_map *map, struct sym_val *result,
a5ececf
+		  struct link_map *map, struct sym_val *result,
a5ececf
 		  int type_class, const ElfW(Sym) *sym, const char *strtab,
a5ececf
-		  const ElfW(Sym) *ref, const struct link_map *undef_map)
a5ececf
+		  const ElfW(Sym) *ref, const struct link_map *undef_map,
a5ececf
+		  int flags)
a5ececf
 {
a5ececf
   /* We have to determine whether we already found a symbol with this
a5ececf
      name before.  If not then we have to add it to the search table.
a5ececf
@@ -222,7 +223,7 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
a5ececf
 		     copy from the copy addressed through the
a5ececf
 		     relocation.  */
a5ececf
 		  result->s = sym;
a5ececf
-		  result->m = (struct link_map *) map;
a5ececf
+		  result->m = map;
a5ececf
 		}
a5ececf
 	      else
a5ececf
 		{
a5ececf
@@ -311,9 +312,19 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
a5ececf
                         new_hash, strtab + sym->st_name, sym, map);
a5ececf
 
a5ececf
       if (map->l_type == lt_loaded)
a5ececf
-	/* Make sure we don't unload this object by
a5ececf
-	   setting the appropriate flag.  */
a5ececf
-	((struct link_map *) map)->l_flags_1 |= DF_1_NODELETE;
a5ececf
+	{
a5ececf
+	  /* Make sure we don't unload this object by
a5ececf
+	     setting the appropriate flag.  */
a5ececf
+	  if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
a5ececf
+	      && map->l_nodelete == link_map_nodelete_inactive)
a5ececf
+	    _dl_debug_printf ("\
a5ececf
+marking %s [%lu] as NODELETE due to unique symbol\n",
a5ececf
+			      map->l_name, map->l_ns);
a5ececf
+	  if (flags & DL_LOOKUP_FOR_RELOCATE)
a5ececf
+	    map->l_nodelete = link_map_nodelete_pending;
a5ececf
+	  else
a5ececf
+	    map->l_nodelete = link_map_nodelete_active;
a5ececf
+	}
a5ececf
     }
a5ececf
   ++tab->n_elements;
a5ececf
 
a5ececf
@@ -525,8 +536,9 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
a5ececf
 	      return 1;
a5ececf
 
a5ececf
 	    case STB_GNU_UNIQUE:;
a5ececf
-	      do_lookup_unique (undef_name, new_hash, map, result, type_class,
a5ececf
-				sym, strtab, ref, undef_map);
a5ececf
+	      do_lookup_unique (undef_name, new_hash, (struct link_map *) map,
a5ececf
+				result, type_class, sym, strtab, ref,
a5ececf
+				undef_map, flags);
a5ececf
 	      return 1;
a5ececf
 
a5ececf
 	    default:
a5ececf
@@ -568,9 +580,13 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
a5ececf
   if (undef_map == map)
a5ececf
     return 0;
a5ececf
 
a5ececf
-  /* Avoid references to objects which cannot be unloaded anyway.  */
a5ececf
+  /* Avoid references to objects which cannot be unloaded anyway.  We
a5ececf
+     do not need to record dependencies if this object goes away
a5ececf
+     during dlopen failure, either.  IFUNC resolvers with relocation
a5ececf
+     dependencies may pick an dependency which can be dlclose'd, but
a5ececf
+     such IFUNC resolvers are undefined anyway.  */
a5ececf
   assert (map->l_type == lt_loaded);
a5ececf
-  if ((map->l_flags_1 & DF_1_NODELETE) != 0)
a5ececf
+  if (map->l_nodelete != link_map_nodelete_inactive)
a5ececf
     return 0;
a5ececf
 
a5ececf
   struct link_map_reldeps *l_reldeps
a5ececf
@@ -678,16 +694,33 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
a5ececf
 
a5ececf
       /* Redo the NODELETE check, as when dl_load_lock wasn't held
a5ececf
 	 yet this could have changed.  */
a5ececf
-      if ((map->l_flags_1 & DF_1_NODELETE) != 0)
a5ececf
+      if (map->l_nodelete != link_map_nodelete_inactive)
a5ececf
 	goto out;
a5ececf
 
a5ececf
       /* If the object with the undefined reference cannot be removed ever
a5ececf
 	 just make sure the same is true for the object which contains the
a5ececf
 	 definition.  */
a5ececf
       if (undef_map->l_type != lt_loaded
a5ececf
-	  || (undef_map->l_flags_1 & DF_1_NODELETE) != 0)
a5ececf
+	  || (undef_map->l_nodelete != link_map_nodelete_inactive))
a5ececf
 	{
a5ececf
-	  map->l_flags_1 |= DF_1_NODELETE;
a5ececf
+	  if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
a5ececf
+	      && map->l_nodelete == link_map_nodelete_inactive)
a5ececf
+	    {
a5ececf
+	      if (undef_map->l_name[0] == '\0')
a5ececf
+		_dl_debug_printf ("\
a5ececf
+marking %s [%lu] as NODELETE due to reference to main program\n",
a5ececf
+				  map->l_name, map->l_ns);
a5ececf
+	      else
a5ececf
+		_dl_debug_printf ("\
a5ececf
+marking %s [%lu] as NODELETE due to reference to %s [%lu]\n",
a5ececf
+				  map->l_name, map->l_ns,
a5ececf
+				  undef_map->l_name, undef_map->l_ns);
a5ececf
+	    }
a5ececf
+
a5ececf
+	  if (flags & DL_LOOKUP_FOR_RELOCATE)
a5ececf
+	    map->l_nodelete = link_map_nodelete_pending;
a5ececf
+	  else
a5ececf
+	    map->l_nodelete = link_map_nodelete_active;
a5ececf
 	  goto out;
a5ececf
 	}
a5ececf
 
a5ececf
@@ -712,7 +745,18 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
a5ececf
 		 no fatal problem.  We simply make sure the referenced object
a5ececf
 		 cannot be unloaded.  This is semantically the correct
a5ececf
 		 behavior.  */
a5ececf
-	      map->l_flags_1 |= DF_1_NODELETE;
a5ececf
+	      if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
a5ececf
+		  && map->l_nodelete == link_map_nodelete_inactive)
a5ececf
+		_dl_debug_printf ("\
a5ececf
+marking %s [%lu] as NODELETE due to memory allocation failure\n",
a5ececf
+				  map->l_name, map->l_ns);
a5ececf
+	      if (flags & DL_LOOKUP_FOR_RELOCATE)
a5ececf
+		/* In case of non-lazy binding, we could actually
a5ececf
+		   report the memory allocation error, but for now, we
a5ececf
+		   use the conservative approximation as well. */
a5ececf
+		map->l_nodelete = link_map_nodelete_pending;
a5ececf
+	      else
a5ececf
+		map->l_nodelete = link_map_nodelete_active;
a5ececf
 	      goto out;
a5ececf
 	    }
a5ececf
 	  else
a5ececf
diff --git a/elf/dl-open.c b/elf/dl-open.c
a5ececf
index 49ed8b12ecb7117a..361482e37e8a8e63 100644
a5ececf
--- a/elf/dl-open.c
a5ececf
+++ b/elf/dl-open.c
a5ececf
@@ -424,6 +424,40 @@ TLS generation counter wrapped!  Please report this."));
a5ececf
     }
a5ececf
 }
a5ececf
 
a5ececf
+/* Mark the objects as NODELETE if required.  This is delayed until
a5ececf
+   after dlopen failure is not possible, so that _dl_close can clean
a5ececf
+   up objects if necessary.  */
a5ececf
+static void
a5ececf
+activate_nodelete (struct link_map *new, int mode)
a5ececf
+{
a5ececf
+  if (mode & RTLD_NODELETE || new->l_nodelete == link_map_nodelete_pending)
a5ececf
+    {
a5ececf
+      if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES))
a5ececf
+	_dl_debug_printf ("activating NODELETE for %s [%lu]\n",
a5ececf
+			  new->l_name, new->l_ns);
a5ececf
+      new->l_nodelete = link_map_nodelete_active;
a5ececf
+    }
a5ececf
+
a5ececf
+  for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
a5ececf
+    {
a5ececf
+      struct link_map *imap = new->l_searchlist.r_list[i];
a5ececf
+      if (imap->l_nodelete == link_map_nodelete_pending)
a5ececf
+	{
a5ececf
+	  if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES))
a5ececf
+	    _dl_debug_printf ("activating NODELETE for %s [%lu]\n",
a5ececf
+			      imap->l_name, imap->l_ns);
a5ececf
+
a5ececf
+	  /* Only new objects should have set
a5ececf
+	     link_map_nodelete_pending.  Existing objects should not
a5ececf
+	     have gained any new dependencies and therefore cannot
a5ececf
+	     reach NODELETE status.  */
a5ececf
+	  assert (!imap->l_init_called || imap->l_type != lt_loaded);
a5ececf
+
a5ececf
+	  imap->l_nodelete = link_map_nodelete_active;
a5ececf
+	}
a5ececf
+     }
a5ececf
+}
a5ececf
+
a5ececf
 /* struct dl_init_args and call_dl_init are used to call _dl_init with
a5ececf
    exception handling disabled.  */
a5ececf
 struct dl_init_args
a5ececf
@@ -493,12 +527,6 @@ dl_open_worker (void *a)
a5ececf
       return;
a5ececf
     }
a5ececf
 
a5ececf
-  /* Mark the object as not deletable if the RTLD_NODELETE flags was passed.
a5ececf
-     Do this early so that we don't skip marking the object if it was
a5ececf
-     already loaded.  */
a5ececf
-  if (__glibc_unlikely (mode & RTLD_NODELETE))
a5ececf
-    new->l_flags_1 |= DF_1_NODELETE;
a5ececf
-
a5ececf
   if (__glibc_unlikely (mode & __RTLD_SPROF))
a5ececf
     /* This happens only if we load a DSO for 'sprof'.  */
a5ececf
     return;
a5ececf
@@ -514,19 +542,37 @@ dl_open_worker (void *a)
a5ececf
 	_dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
a5ececf
 			  new->l_name, new->l_ns, new->l_direct_opencount);
a5ececf
 
a5ececf
-      /* If the user requested the object to be in the global namespace
a5ececf
-	 but it is not so far, add it now.  */
a5ececf
+      /* If the user requested the object to be in the global
a5ececf
+	 namespace but it is not so far, prepare to add it now.  This
a5ececf
+	 can raise an exception to do a malloc failure.  */
a5ececf
       if ((mode & RTLD_GLOBAL) && new->l_global == 0)
a5ececf
+	add_to_global_resize (new);
a5ececf
+
a5ececf
+      /* Mark the object as not deletable if the RTLD_NODELETE flags
a5ececf
+	 was passed.  */
a5ececf
+      if (__glibc_unlikely (mode & RTLD_NODELETE))
a5ececf
 	{
a5ececf
-	  add_to_global_resize (new);
a5ececf
-	  add_to_global_update (new);
a5ececf
+	  if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES)
a5ececf
+	      && new->l_nodelete == link_map_nodelete_inactive)
a5ececf
+	    _dl_debug_printf ("marking %s [%lu] as NODELETE\n",
a5ececf
+			      new->l_name, new->l_ns);
a5ececf
+	  new->l_nodelete = link_map_nodelete_active;
a5ececf
 	}
a5ececf
 
a5ececf
+      /* Finalize the addition to the global scope.  */
a5ececf
+      if ((mode & RTLD_GLOBAL) && new->l_global == 0)
a5ececf
+	add_to_global_update (new);
a5ececf
+
a5ececf
       assert (_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT);
a5ececf
 
a5ececf
       return;
a5ececf
     }
a5ececf
 
a5ececf
+  /* Schedule NODELETE marking for the directly loaded object if
a5ececf
+     requested.  */
a5ececf
+  if (__glibc_unlikely (mode & RTLD_NODELETE))
a5ececf
+    new->l_nodelete = link_map_nodelete_pending;
a5ececf
+
a5ececf
   /* Load that object's dependencies.  */
a5ececf
   _dl_map_object_deps (new, NULL, 0, 0,
a5ececf
 		       mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT));
a5ececf
@@ -598,6 +644,14 @@ dl_open_worker (void *a)
a5ececf
 
a5ececf
   int relocation_in_progress = 0;
a5ececf
 
a5ececf
+  /* Perform relocation.  This can trigger lazy binding in IFUNC
a5ececf
+     resolvers.  For NODELETE mappings, these dependencies are not
a5ececf
+     recorded because the flag has not been applied to the newly
a5ececf
+     loaded objects.  This means that upon dlopen failure, these
a5ececf
+     NODELETE objects can be unloaded despite existing references to
a5ececf
+     them.  However, such relocation dependencies in IFUNC resolvers
a5ececf
+     are undefined anyway, so this is not a problem.  */
a5ececf
+
a5ececf
   for (unsigned int i = nmaps; i-- > 0; )
a5ececf
     {
a5ececf
       l = maps[i];
a5ececf
@@ -627,7 +681,7 @@ dl_open_worker (void *a)
a5ececf
 	      _dl_start_profile ();
a5ececf
 
a5ececf
 	      /* Prevent unloading the object.  */
a5ececf
-	      GL(dl_profile_map)->l_flags_1 |= DF_1_NODELETE;
a5ececf
+	      GL(dl_profile_map)->l_nodelete = link_map_nodelete_active;
a5ececf
 	    }
a5ececf
 	}
a5ececf
       else
a5ececf
@@ -658,6 +712,8 @@ dl_open_worker (void *a)
a5ececf
      All memory allocations for new objects must have happened
a5ececf
      before.  */
a5ececf
 
a5ececf
+  activate_nodelete (new, mode);
a5ececf
+
a5ececf
   /* Second stage after resize_scopes: Actually perform the scope
a5ececf
      update.  After this, dlsym and lazy binding can bind to new
a5ececf
      objects.  */
a5ececf
@@ -817,6 +873,10 @@ no more namespaces available for dlmopen()"));
a5ececf
 	    GL(dl_tls_dtv_gaps) = true;
a5ececf
 
a5ececf
 	  _dl_close_worker (args.map, true);
a5ececf
+
a5ececf
+	  /* All link_map_nodelete_pending objects should have been
a5ececf
+	     deleted at this point, which is why it is not necessary
a5ececf
+	     to reset the flag here.  */
a5ececf
 	}
a5ececf
 
a5ececf
       assert (_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT);
a5ececf
diff --git a/elf/get-dynamic-info.h b/elf/get-dynamic-info.h
a5ececf
index 75fbb88f78241485..f7cc5a809ddc3043 100644
a5ececf
--- a/elf/get-dynamic-info.h
a5ececf
+++ b/elf/get-dynamic-info.h
a5ececf
@@ -163,6 +163,8 @@ elf_get_dynamic_info (struct link_map *l, ElfW(Dyn) *temp)
a5ececf
   if (info[VERSYMIDX (DT_FLAGS_1)] != NULL)
a5ececf
     {
a5ececf
       l->l_flags_1 = info[VERSYMIDX (DT_FLAGS_1)]->d_un.d_val;
a5ececf
+      if (l->l_flags_1 & DF_1_NODELETE)
a5ececf
+	l->l_nodelete = link_map_nodelete_pending;
a5ececf
 
a5ececf
       /* Only DT_1_SUPPORTED_MASK bits are supported, and we would like
a5ececf
 	 to assert this, but we can't. Users have been setting
a5ececf
diff --git a/elf/tst-dlopenfail.c b/elf/tst-dlopenfail.c
a5ececf
new file mode 100644
a5ececf
index 0000000000000000..ce3140c899562ca8
a5ececf
--- /dev/null
a5ececf
+++ b/elf/tst-dlopenfail.c
a5ececf
@@ -0,0 +1,79 @@
a5ececf
+/* Test dlopen rollback after failures involving NODELETE objects (bug 20839).
a5ececf
+   Copyright (C) 2019 Free Software Foundation, Inc.
a5ececf
+   This file is part of the GNU C Library.
a5ececf
+
a5ececf
+   The GNU C Library is free software; you can redistribute it and/or
a5ececf
+   modify it under the terms of the GNU Lesser General Public
a5ececf
+   License as published by the Free Software Foundation; either
a5ececf
+   version 2.1 of the License, or (at your option) any later version.
a5ececf
+
a5ececf
+   The GNU C Library is distributed in the hope that it will be useful,
a5ececf
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
a5ececf
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
a5ececf
+   Lesser General Public License for more details.
a5ececf
+
a5ececf
+   You should have received a copy of the GNU Lesser General Public
a5ececf
+   License along with the GNU C Library; if not, see
a5ececf
+   <https://www.gnu.org/licenses/>.  */
a5ececf
+
a5ececf
+#include <dlfcn.h>
a5ececf
+#include <errno.h>
a5ececf
+#include <gnu/lib-names.h>
a5ececf
+#include <stddef.h>
a5ececf
+#include <stdio.h>
a5ececf
+#include <string.h>
a5ececf
+#include <support/check.h>
a5ececf
+#include <support/xdlfcn.h>
a5ececf
+
a5ececf
+static int
a5ececf
+do_test (void)
a5ececf
+{
a5ececf
+  /* This test uses libpthread as the canonical NODELETE module.  If
a5ececf
+     libpthread is no longer NODELETE because it has been merged into
a5ececf
+     libc, the test needs to be updated.  */
a5ececf
+  TEST_VERIFY (dlsym (NULL, "pthread_create") == NULL);
a5ececf
+
a5ececf
+  /* This is expected to fail because of the missing dependency.  */
a5ececf
+  puts ("info: attempting to load tst-dlopenfailmod1.so");
a5ececf
+  TEST_VERIFY (dlopen ("tst-dlopenfailmod1.so", RTLD_LAZY) == NULL);
a5ececf
+  const char *message = dlerror ();
a5ececf
+  TEST_COMPARE_STRING (message,
a5ececf
+                       "tst-dlopenfail-missingmod.so:"
a5ececf
+                       " cannot open shared object file:"
a5ececf
+                       " No such file or directory");
a5ececf
+
a5ececf
+  /* Do not probe for the presence of libpthread at this point because
a5ececf
+     that might trigger relocation if bug 20839 is present, obscuring
a5ececf
+     a subsequent crash.  */
a5ececf
+
a5ececf
+  /* This is expected to succeed.  */
a5ececf
+  puts ("info: loading tst-dlopenfailmod2.so");
a5ececf
+  void *handle = xdlopen ("tst-dlopenfailmod2.so", RTLD_NOW);
a5ececf
+  xdlclose (handle);
a5ececf
+
a5ececf
+  /* libpthread should remain loaded.  */
a5ececf
+  TEST_VERIFY (dlopen (LIBPTHREAD_SO, RTLD_LAZY | RTLD_NOLOAD) != NULL);
a5ececf
+  TEST_VERIFY (dlsym (NULL, "pthread_create") == NULL);
a5ececf
+
a5ececf
+  /* We can make libpthread global, and then the symbol should become
a5ececf
+     available.  */
a5ececf
+  TEST_VERIFY (dlopen (LIBPTHREAD_SO, RTLD_LAZY | RTLD_GLOBAL) != NULL);
a5ececf
+  TEST_VERIFY (dlsym (NULL, "pthread_create") != NULL);
a5ececf
+
a5ececf
+  /* sem_open is sufficiently complex to depend on relocations.  */
a5ececf
+  void *(*sem_open_ptr) (const char *, int flag, ...)
a5ececf
+    = dlsym (NULL, "sem_open");
a5ececf
+  if (sem_open_ptr == NULL)
a5ececf
+    /* Hurd does not implement sem_open.  */
a5ececf
+    puts ("warning: sem_open not found, further testing not possible");
a5ececf
+  else
a5ececf
+    {
a5ececf
+      errno = 0;
a5ececf
+      TEST_VERIFY (sem_open_ptr ("/", 0) == NULL);
a5ececf
+      TEST_COMPARE (errno, EINVAL);
a5ececf
+    }
a5ececf
+
a5ececf
+  return 0;
a5ececf
+}
a5ececf
+
a5ececf
+#include <support/test-driver.c>
a5ececf
diff --git a/elf/tst-dlopenfaillinkmod.c b/elf/tst-dlopenfaillinkmod.c
a5ececf
new file mode 100644
a5ececf
index 0000000000000000..3b14b02bc9a12c0b
a5ececf
--- /dev/null
a5ececf
+++ b/elf/tst-dlopenfaillinkmod.c
a5ececf
@@ -0,0 +1,17 @@
a5ececf
+/* Empty module with a soname which is not available at run time.
a5ececf
+   Copyright (C) 2019 Free Software Foundation, Inc.
a5ececf
+   This file is part of the GNU C Library.
a5ececf
+
a5ececf
+   The GNU C Library is free software; you can redistribute it and/or
a5ececf
+   modify it under the terms of the GNU Lesser General Public
a5ececf
+   License as published by the Free Software Foundation; either
a5ececf
+   version 2.1 of the License, or (at your option) any later version.
a5ececf
+
a5ececf
+   The GNU C Library is distributed in the hope that it will be useful,
a5ececf
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
a5ececf
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
a5ececf
+   Lesser General Public License for more details.
a5ececf
+
a5ececf
+   You should have received a copy of the GNU Lesser General Public
a5ececf
+   License along with the GNU C Library; if not, see
a5ececf
+   <https://www.gnu.org/licenses/>.  */
a5ececf
diff --git a/elf/tst-dlopenfailmod1.c b/elf/tst-dlopenfailmod1.c
a5ececf
new file mode 100644
a5ececf
index 0000000000000000..6ef48829899a5a64
a5ececf
--- /dev/null
a5ececf
+++ b/elf/tst-dlopenfailmod1.c
a5ececf
@@ -0,0 +1,36 @@
a5ececf
+/* Module which depends on two modules: one NODELETE, one missing.
a5ececf
+   Copyright (C) 2019 Free Software Foundation, Inc.
a5ececf
+   This file is part of the GNU C Library.
a5ececf
+
a5ececf
+   The GNU C Library is free software; you can redistribute it and/or
a5ececf
+   modify it under the terms of the GNU Lesser General Public
a5ececf
+   License as published by the Free Software Foundation; either
a5ececf
+   version 2.1 of the License, or (at your option) any later version.
a5ececf
+
a5ececf
+   The GNU C Library is distributed in the hope that it will be useful,
a5ececf
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
a5ececf
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
a5ececf
+   Lesser General Public License for more details.
a5ececf
+
a5ececf
+   You should have received a copy of the GNU Lesser General Public
a5ececf
+   License along with the GNU C Library; if not, see
a5ececf
+   <https://www.gnu.org/licenses/>.  */
a5ececf
+
a5ececf
+/* Note: Due to the missing second module, this object cannot be
a5ececf
+   loaded at run time.  */
a5ececf
+
a5ececf
+#include <pthread.h>
a5ececf
+#include <stdio.h>
a5ececf
+#include <unistd.h>
a5ececf
+
a5ececf
+/* Force linking against libpthread.  */
a5ececf
+void *pthread_create_reference = pthread_create;
a5ececf
+
a5ececf
+/* The constructor will never be executed because the module cannot be
a5ececf
+   loaded.  */
a5ececf
+static void __attribute__ ((constructor))
a5ececf
+init (void)
a5ececf
+{
a5ececf
+  puts ("tst-dlopenfailmod1 constructor executed");
a5ececf
+  _exit (1);
a5ececf
+}
a5ececf
diff --git a/elf/tst-dlopenfailmod2.c b/elf/tst-dlopenfailmod2.c
a5ececf
new file mode 100644
a5ececf
index 0000000000000000..7d600386c13b98bd
a5ececf
--- /dev/null
a5ececf
+++ b/elf/tst-dlopenfailmod2.c
a5ececf
@@ -0,0 +1,29 @@
a5ececf
+/* Module which depends on on a NODELETE module, and can be loaded.
a5ececf
+   Copyright (C) 2019 Free Software Foundation, Inc.
a5ececf
+   This file is part of the GNU C Library.
a5ececf
+
a5ececf
+   The GNU C Library is free software; you can redistribute it and/or
a5ececf
+   modify it under the terms of the GNU Lesser General Public
a5ececf
+   License as published by the Free Software Foundation; either
a5ececf
+   version 2.1 of the License, or (at your option) any later version.
a5ececf
+
a5ececf
+   The GNU C Library is distributed in the hope that it will be useful,
a5ececf
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
a5ececf
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
a5ececf
+   Lesser General Public License for more details.
a5ececf
+
a5ececf
+   You should have received a copy of the GNU Lesser General Public
a5ececf
+   License along with the GNU C Library; if not, see
a5ececf
+   <https://www.gnu.org/licenses/>.  */
a5ececf
+
a5ececf
+#include <pthread.h>
a5ececf
+#include <stdio.h>
a5ececf
+
a5ececf
+/* Force linking against libpthread.  */
a5ececf
+void *pthread_create_reference = pthread_create;
a5ececf
+
a5ececf
+static void __attribute__ ((constructor))
a5ececf
+init (void)
a5ececf
+{
a5ececf
+  puts ("info: tst-dlopenfailmod2.so constructor invoked");
a5ececf
+}
a5ececf
diff --git a/include/link.h b/include/link.h
a5ececf
index 736e1d72aec8baed..ef89e6b8ae8254b3 100644
a5ececf
--- a/include/link.h
a5ececf
+++ b/include/link.h
a5ececf
@@ -79,6 +79,21 @@ struct r_search_path_struct
a5ececf
     int malloced;
a5ececf
   };
a5ececf
 
a5ececf
+/* Type used by the l_nodelete member.  */
a5ececf
+enum link_map_nodelete
a5ececf
+{
a5ececf
+ /* This link map can be deallocated.  */
a5ececf
+ link_map_nodelete_inactive = 0, /* Zero-initialized in _dl_new_object.  */
a5ececf
+
a5ececf
+ /* This link map cannot be deallocated.  */
a5ececf
+ link_map_nodelete_active,
a5ececf
+
a5ececf
+ /* This link map cannot be deallocated after dlopen has succeded.
a5ececf
+    dlopen turns this into link_map_nodelete_active.  dlclose treats
a5ececf
+    this intermediate state as link_map_nodelete_active.  */
a5ececf
+ link_map_nodelete_pending,
a5ececf
+};
a5ececf
+
a5ececf
 
a5ececf
 /* Structure describing a loaded shared object.  The `l_next' and `l_prev'
a5ececf
    members form a chain of all the shared objects loaded at startup.
a5ececf
@@ -203,6 +218,11 @@ struct link_map
a5ececf
 				       freed, ie. not allocated with
a5ececf
 				       the dummy malloc in ld.so.  */
a5ececf
 
a5ececf
+    /* Actually of type enum link_map_nodelete.  Separate byte due to
a5ececf
+       a read in add_dependency in elf/dl-lookup.c outside the loader
a5ececf
+       lock.  Only valid for l_type == lt_loaded.  */
a5ececf
+    unsigned char l_nodelete;
a5ececf
+
a5ececf
 #include <link_map.h>
a5ececf
 
a5ececf
     /* Collected information about own RPATH directories.  */