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