5f7b84
commit f8ed116aa574435c6e28260f21963233682d3b57
5f7b84
Author: Florian Weimer <fweimer@redhat.com>
5f7b84
Date:   Fri Dec 13 10:18:46 2019 +0100
5f7b84
5f7b84
    dlopen: Rework handling of pending NODELETE status
5f7b84
    
5f7b84
    Commit a2e8aa0d9ea648068d8be52dd7b15f1b6a008e23 ("Block signals during
5f7b84
    the initial part of dlopen") was deemed necessary because of
5f7b84
    read-modify-write operations like the one in  add_dependency in
5f7b84
    elf/dl-lookup.c.  In the old code, we check for any kind of NODELETE
5f7b84
    status and bail out:
5f7b84
    
5f7b84
          /* Redo the NODELETE check, as when dl_load_lock wasn't held
5f7b84
             yet this could have changed.  */
5f7b84
          if (map->l_nodelete != link_map_nodelete_inactive)
5f7b84
            goto out;
5f7b84
    
5f7b84
    And then set pending status (during relocation):
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
    
5f7b84
    If a signal arrives during relocation and the signal handler, through
5f7b84
    lazy binding, adds a global scope dependency on the same map, it will
5f7b84
    set map->l_nodelete to link_map_nodelete_active.  This will be
5f7b84
    overwritten with link_map_nodelete_pending by the dlopen relocation
5f7b84
    code.
5f7b84
    
5f7b84
    To avoid such problems in relation to the l_nodelete member, this
5f7b84
    commit introduces two flags for active NODELETE status (irrevocable)
5f7b84
    and pending NODELETE status (revocable until activate_nodelete is
5f7b84
    invoked).  As a result, NODELETE processing in dlopen does not
5f7b84
    introduce further reasons why lazy binding from signal handlers
5f7b84
    is unsafe during dlopen, and a subsequent commit can remove signal
5f7b84
    blocking from dlopen.
5f7b84
    
5f7b84
    This does not address pre-existing issues (unrelated to the NODELETE
5f7b84
    changes) which make lazy binding in a signal handler during dlopen
5f7b84
    unsafe, such as the use of malloc in both cases.
5f7b84
    
5f7b84
    Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
5f7b84
    Reviewed-by: Carlos O'Donell <carlos@redhat.com>
5f7b84
5f7b84
diff --git a/elf/dl-close.c b/elf/dl-close.c
5f7b84
index 243a028c443173c1..fa7f3e8174576e46 100644
5f7b84
--- a/elf/dl-close.c
5f7b84
+++ b/elf/dl-close.c
5f7b84
@@ -197,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_nodelete != link_map_nodelete_active
5f7b84
+	  && !l->l_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
@@ -279,8 +279,7 @@ _dl_close_worker (struct link_map *map, bool force)
5f7b84
 
5f7b84
       if (!used[i])
5f7b84
 	{
5f7b84
-	  assert (imap->l_type == lt_loaded
5f7b84
-		  && imap->l_nodelete != link_map_nodelete_active);
5f7b84
+	  assert (imap->l_type == lt_loaded && !imap->l_nodelete_active);
5f7b84
 
5f7b84
 	  /* Call its termination function.  Do not do it for
5f7b84
 	     half-cooked objects.  Temporarily disable exception
5f7b84
@@ -820,7 +819,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_nodelete == link_map_nodelete_active))
5f7b84
+  if (__glibc_unlikely (map->l_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 35a3f96a6296294a..01724a54f8840f9f 100644
5f7b84
--- a/elf/dl-lookup.c
5f7b84
+++ b/elf/dl-lookup.c
5f7b84
@@ -187,6 +187,28 @@ enter_unique_sym (struct unique_sym *table, size_t size,
5f7b84
   table[idx].map = map;
5f7b84
 }
5f7b84
 
5f7b84
+/* Mark MAP as NODELETE according to the lookup mode in FLAGS.  During
5f7b84
+   initial relocation, NODELETE state is pending only.  */
5f7b84
+static void
5f7b84
+mark_nodelete (struct link_map *map, int flags)
5f7b84
+{
5f7b84
+  if (flags & DL_LOOKUP_FOR_RELOCATE)
5f7b84
+    map->l_nodelete_pending = true;
5f7b84
+  else
5f7b84
+    map->l_nodelete_active = true;
5f7b84
+}
5f7b84
+
5f7b84
+/* Return true if MAP is marked as NODELETE according to the lookup
5f7b84
+   mode in FLAGS> */
5f7b84
+static bool
5f7b84
+is_nodelete (struct link_map *map, int flags)
5f7b84
+{
5f7b84
+  /* Non-pending NODELETE always counts.  Pending NODELETE only counts
5f7b84
+     during initial relocation processing.  */
5f7b84
+  return map->l_nodelete_active
5f7b84
+    || ((flags & DL_LOOKUP_FOR_RELOCATE) && map->l_nodelete_pending);
5f7b84
+}
5f7b84
+
5f7b84
 /* Utility function for do_lookup_x. Lookup an STB_GNU_UNIQUE symbol
5f7b84
    in the unique symbol table, creating a new entry if necessary.
5f7b84
    Return the matching symbol in RESULT.  */
5f7b84
@@ -311,8 +333,7 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
5f7b84
       enter_unique_sym (entries, size,
5f7b84
                         new_hash, strtab + sym->st_name, sym, map);
5f7b84
 
5f7b84
-      if (map->l_type == lt_loaded
5f7b84
-	  && map->l_nodelete == link_map_nodelete_inactive)
5f7b84
+      if (map->l_type == lt_loaded && !is_nodelete (map, flags))
5f7b84
 	{
5f7b84
 	  /* Make sure we don't unload this object by
5f7b84
 	     setting the appropriate flag.  */
5f7b84
@@ -320,10 +341,7 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
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
+	  mark_nodelete (map, flags);
5f7b84
 	}
5f7b84
     }
5f7b84
   ++tab->n_elements;
5f7b84
@@ -586,7 +604,7 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
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_nodelete != link_map_nodelete_inactive)
5f7b84
+  if (is_nodelete (map, flags))
5f7b84
     return 0;
5f7b84
 
5f7b84
   struct link_map_reldeps *l_reldeps
5f7b84
@@ -694,17 +712,16 @@ 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_nodelete != link_map_nodelete_inactive)
5f7b84
+      if (is_nodelete (map, flags))
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_nodelete != link_map_nodelete_inactive))
5f7b84
+      if (undef_map->l_type != lt_loaded || is_nodelete (map, flags))
5f7b84
 	{
5f7b84
 	  if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
5f7b84
-	      && map->l_nodelete == link_map_nodelete_inactive)
5f7b84
+	      && !is_nodelete (map, flags))
5f7b84
 	    {
5f7b84
 	      if (undef_map->l_name[0] == '\0')
5f7b84
 		_dl_debug_printf ("\
5f7b84
@@ -716,11 +733,7 @@ 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
+	  mark_nodelete (map, flags);
5f7b84
 	  goto out;
5f7b84
 	}
5f7b84
 
5f7b84
@@ -746,17 +759,14 @@ marking %s [%lu] as NODELETE due to reference to %s [%lu]\n",
5f7b84
 		 cannot be unloaded.  This is semantically the correct
5f7b84
 		 behavior.  */
5f7b84
 	      if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
5f7b84
-		  && map->l_nodelete == link_map_nodelete_inactive)
5f7b84
+		  && !is_nodelete (map, flags))
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
+	      /* In case of non-lazy binding, we could actually report
5f7b84
+		 the memory allocation error, but for now, we use the
5f7b84
+		 conservative approximation as well.  */
5f7b84
+	      mark_nodelete (map, flags);
5f7b84
 	      goto out;
5f7b84
 	    }
5f7b84
 	  else
5f7b84
diff --git a/elf/dl-open.c b/elf/dl-open.c
5f7b84
index c7ed85b7ee99a296..a382bfae8aa3a2f8 100644
5f7b84
--- a/elf/dl-open.c
5f7b84
+++ b/elf/dl-open.c
5f7b84
@@ -440,13 +440,17 @@ activate_nodelete (struct link_map *new)
5f7b84
      NODELETE status for objects outside the local scope.  */
5f7b84
   for (struct link_map *l = GL (dl_ns)[new->l_ns]._ns_loaded; l != NULL;
5f7b84
        l = l->l_next)
5f7b84
-    if (l->l_nodelete == link_map_nodelete_pending)
5f7b84
+    if (l->l_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
 			    l->l_name, l->l_ns);
5f7b84
 
5f7b84
-	l->l_nodelete = link_map_nodelete_active;
5f7b84
+	l->l_nodelete_active = true;
5f7b84
+
5f7b84
+	/* This is just a debugging aid, to indicate that
5f7b84
+	   activate_nodelete has run for this map.  */
5f7b84
+	l->l_nodelete_pending = false;
5f7b84
       }
5f7b84
 }
5f7b84
 
5f7b84
@@ -549,10 +553,10 @@ dl_open_worker (void *a)
5f7b84
       if (__glibc_unlikely (mode & RTLD_NODELETE))
5f7b84
 	{
5f7b84
 	  if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES)
5f7b84
-	      && new->l_nodelete == link_map_nodelete_inactive)
5f7b84
+	      && !new->l_nodelete_active)
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
+	  new->l_nodelete_active = true;
5f7b84
 	}
5f7b84
 
5f7b84
       /* Finalize the addition to the global scope.  */
5f7b84
@@ -568,7 +572,7 @@ dl_open_worker (void *a)
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
+    new->l_nodelete_pending = true;
5f7b84
 
5f7b84
   /* Load that object's dependencies.  */
5f7b84
   _dl_map_object_deps (new, NULL, 0, 0,
5f7b84
@@ -680,7 +684,7 @@ dl_open_worker (void *a)
5f7b84
 	      _dl_start_profile ();
5f7b84
 
5f7b84
 	      /* Prevent unloading the object.  */
5f7b84
-	      GL(dl_profile_map)->l_nodelete = link_map_nodelete_active;
5f7b84
+	      GL(dl_profile_map)->l_nodelete_active = true;
5f7b84
 	    }
5f7b84
 	}
5f7b84
       else
5f7b84
@@ -879,9 +883,9 @@ no more namespaces available for dlmopen()"));
5f7b84
 	     happens inside dl_open_worker.  */
5f7b84
 	  __libc_signal_restore_set (&args.original_signal_mask);
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
+	  /* All l_nodelete_pending objects should have been deleted
5f7b84
+	     at this point, which is why it is not necessary to reset
5f7b84
+	     the flag here.  */
5f7b84
 	}
5f7b84
       else
5f7b84
 	__libc_signal_restore_set (&args.original_signal_mask);
5f7b84
diff --git a/elf/get-dynamic-info.h b/elf/get-dynamic-info.h
5f7b84
index ea286abaea0128d1..78ba7e76db9706cc 100644
5f7b84
--- a/elf/get-dynamic-info.h
5f7b84
+++ b/elf/get-dynamic-info.h
5f7b84
@@ -164,7 +164,7 @@ elf_get_dynamic_info (struct link_map *l, ElfW(Dyn) *temp)
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
+	l->l_nodelete_pending = true;
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/include/link.h b/include/link.h
5f7b84
index a277b77cad6b52b1..e90fa79a0b332087 100644
5f7b84
--- a/include/link.h
5f7b84
+++ b/include/link.h
5f7b84
@@ -79,22 +79,6 @@ 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
 
5f7b84
@@ -218,10 +202,17 @@ 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
+    /* NODELETE status of the map.  Only valid for maps of type
5f7b84
+       lt_loaded.  Lazy binding sets l_nodelete_active directly,
5f7b84
+       potentially from signal handlers.  Initial loading of an
5f7b84
+       DF_1_NODELETE object set l_nodelete_pending.  Relocation may
5f7b84
+       set l_nodelete_pending as well.  l_nodelete_pending maps are
5f7b84
+       promoted to l_nodelete_active status in the final stages of
5f7b84
+       dlopen, prior to calling ELF constructors.  dlclose only
5f7b84
+       refuses to unload l_nodelete_active maps, the pending status is
5f7b84
+       ignored.  */
5f7b84
+    bool l_nodelete_active;
5f7b84
+    bool l_nodelete_pending;
5f7b84
 
5f7b84
 #include <link_map.h>
5f7b84