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