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