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