]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - elf/dl-close.c
nptl/tst-cancel25 needs to be an internal test
[thirdparty/glibc.git] / elf / dl-close.c
index 229e288ef6a9a7df605d96c4cf3a06d0c0b2ac66..de91c28f728d0c0f8c0895053803dc4d5a6045d4 100644 (file)
@@ -1,5 +1,5 @@
 /* Close a shared object opened by `_dl_open'.
-   Copyright (C) 1996-2007, 2009, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 1996-2019 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -13,9 +13,8 @@
    Lesser General Public License for more details.
 
    You should have received a copy of the GNU Lesser General Public
-   License along with the GNU C Library; if not, write to the Free
-   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
-   02111-1307 USA.  */
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
 
 #include <assert.h>
 #include <dlfcn.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include <bits/libc-lock.h>
+#include <libc-lock.h>
 #include <ldsodefs.h>
 #include <sys/types.h>
 #include <sys/mman.h>
 #include <sysdep-cancel.h>
 #include <tls.h>
+#include <stap-probe.h>
+
+#include <dl-unmap-segments.h>
 
 
 /* Type of the constructor functions.  */
@@ -73,7 +75,7 @@ remove_slotinfo (size_t idx, struct dtv_slotinfo_list *listp, size_t disp,
 
       /* The entry might still be in its unused state if we are closing an
         object that wasn't fully set up.  */
-      if (__builtin_expect (old_map != NULL, 1))
+      if (__glibc_likely (old_map != NULL))
        {
          assert (old_map->l_tls_modid == idx);
 
@@ -106,7 +108,7 @@ remove_slotinfo (size_t idx, struct dtv_slotinfo_list *listp, size_t disp,
 
 
 void
-_dl_close_worker (struct link_map *map)
+_dl_close_worker (struct link_map *map, bool force)
 {
   /* One less direct use.  */
   --map->l_direct_opencount;
@@ -119,20 +121,11 @@ _dl_close_worker (struct link_map *map)
   if (map->l_direct_opencount > 0 || map->l_type != lt_loaded
       || dl_close_state != not_pending)
     {
-      if (map->l_direct_opencount == 0)
-       {
-         if (map->l_type == lt_loaded)
-           dl_close_state = rerun;
-         else if (map->l_type == lt_library)
-           {
-             struct link_map **oldp = map->l_initfini;
-             map->l_initfini = map->l_orig_initfini;
-             _dl_scope_free (oldp);
-           }
-       }
+      if (map->l_direct_opencount == 0 && map->l_type == lt_loaded)
+       dl_close_state = rerun;
 
       /* There are still references to this object.  Do nothing more.  */
-      if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_FILES, 0))
+      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
        _dl_debug_printf ("\nclosing file=%s; direct_opencount=%u\n",
                          map->l_name, map->l_direct_opencount);
 
@@ -151,6 +144,14 @@ _dl_close_worker (struct link_map *map)
   char done[nloaded];
   struct link_map *maps[nloaded];
 
+  /* Clear DF_1_NODELETE to force object deletion.  We don't need to touch
+     l_tls_dtor_count because forced object deletion only happens when an
+     error occurs during object load.  Destructor registration for TLS
+     non-POD objects should not have happened till then for this
+     object.  */
+  if (force)
+    map->l_flags_1 &= ~DF_1_NODELETE;
+
   /* Run over the list and assign indexes to the link maps and enter
      them into the MAPS array.  */
   int idx = 0;
@@ -159,6 +160,7 @@ _dl_close_worker (struct link_map *map)
       l->l_idx = idx;
       maps[idx] = l;
       ++idx;
+
     }
   assert (idx == nloaded);
 
@@ -180,6 +182,9 @@ _dl_close_worker (struct link_map *map)
       if (l->l_type == lt_loaded
          && l->l_direct_opencount == 0
          && (l->l_flags_1 & DF_1_NODELETE) == 0
+         /* See CONCURRENCY NOTES in cxa_thread_atexit_impl.c to know why
+            acquire is sufficient and correct.  */
+         && atomic_load_acquire (&l->l_tls_dtor_count) == 0
          && !used[done_index])
        continue;
 
@@ -192,6 +197,8 @@ _dl_close_worker (struct link_map *map)
       /* Mark all dependencies as used.  */
       if (l->l_initfini != NULL)
        {
+         /* We are always the zeroth entry, and since we don't include
+            ourselves in the dependency analysis start at 1.  */
          struct link_map **lp = &l->l_initfini[1];
          while (*lp != NULL)
            {
@@ -202,6 +209,10 @@ _dl_close_worker (struct link_map *map)
                  if (!used[(*lp)->l_idx])
                    {
                      used[(*lp)->l_idx] = 1;
+                     /* If we marked a new object as used, and we've
+                        already processed it, then we need to go back
+                        and process again from that point forward to
+                        ensure we keep all of its dependencies also.  */
                      if ((*lp)->l_idx - 1 < done_index)
                        done_index = (*lp)->l_idx - 1;
                    }
@@ -230,8 +241,10 @@ _dl_close_worker (struct link_map *map)
          }
     }
 
-  /* Sort the entries.  */
-  _dl_sort_fini (ns->_ns_loaded, maps, nloaded, used, nsid);
+  /* Sort the entries.  We can skip looking for the binary itself which is
+     at the front of the search list for the main namespace.  */
+  _dl_sort_maps (maps + (nsid == LM_ID_BASE), nloaded - (nsid == LM_ID_BASE),
+                used + (nsid == LM_ID_BASE), true);
 
   /* Call all termination functions at once.  */
 #ifdef SHARED
@@ -277,14 +290,13 @@ _dl_close_worker (struct link_map *map)
 
              /* Next try the old-style destructor.  */
              if (imap->l_info[DT_FINI] != NULL)
-               (*(void (*) (void)) DL_DT_FINI_ADDRESS
-                (imap, ((void *) imap->l_addr
-                        + imap->l_info[DT_FINI]->d_un.d_ptr))) ();
+               DL_CALL_DT_FINI (imap, ((void *) imap->l_addr
+                        + imap->l_info[DT_FINI]->d_un.d_ptr));
            }
 
 #ifdef SHARED
          /* Auditing checkpoint: we remove an object.  */
-         if (__builtin_expect (do_audit, 0))
+         if (__glibc_unlikely (do_audit))
            {
              struct audit_ifaces *afct = GLRO(dl_audit);
              for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
@@ -430,6 +442,13 @@ _dl_close_worker (struct link_map *map)
 
              imap->l_scope_max = new_size;
            }
+         else if (new_list != NULL)
+           {
+             /* We didn't change the scope array, so reset the search
+                list.  */
+             imap->l_searchlist.r_list = NULL;
+             imap->l_searchlist.r_nlist = 0;
+           }
 
          /* The loader is gone, so mark the object as not having one.
             Note: l_idx != IDX_STILL_USED -> object will be removed.  */
@@ -449,7 +468,7 @@ _dl_close_worker (struct link_map *map)
 
 #ifdef SHARED
   /* Auditing checkpoint: we will start deleting objects.  */
-  if (__builtin_expect (do_audit, 0))
+  if (__glibc_unlikely (do_audit))
     {
       struct link_map *head = ns->_ns_loaded;
       struct audit_ifaces *afct = GLRO(dl_audit);
@@ -471,6 +490,7 @@ _dl_close_worker (struct link_map *map)
   struct r_debug *r = _dl_debug_initialize (0, nsid);
   r->r_state = RT_DELETE;
   _dl_debug_state ();
+  LIBC_PROBE (unmap_start, 2, nsid, r);
 
   if (unload_global)
     {
@@ -487,7 +507,7 @@ _dl_close_worker (struct link_map *map)
        /* Speed up removing most recently added objects.  */
        j = cnt;
       else
-       for (i = 0; i < cnt; i++)
+       for (i = 0; i < cnt; i++)
          if (ns_msl->r_list[i]->l_removed == 0)
            {
              if (i != j)
@@ -532,7 +552,7 @@ _dl_close_worker (struct link_map *map)
             object.  We can unmap it.  */
 
          /* Remove the object from the dtv slotinfo array if it uses TLS.  */
-         if (__builtin_expect (imap->l_tls_blocksize > 0, 0))
+         if (__glibc_unlikely (imap->l_tls_blocksize > 0))
            {
              any_tls = true;
 
@@ -629,21 +649,58 @@ _dl_close_worker (struct link_map *map)
                }
            }
 
+         /* Reset unique symbols if forced.  */
+         if (force)
+           {
+             struct unique_sym_table *tab = &ns->_ns_unique_sym_table;
+             __rtld_lock_lock_recursive (tab->lock);
+             struct unique_sym *entries = tab->entries;
+             if (entries != NULL)
+               {
+                 size_t idx, size = tab->size;
+                 for (idx = 0; idx < size; ++idx)
+                   {
+                     /* Clear unique symbol entries that belong to this
+                        object.  */
+                     if (entries[idx].name != NULL
+                         && entries[idx].map == imap)
+                       {
+                         entries[idx].name = NULL;
+                         entries[idx].hashval = 0;
+                         tab->n_elements--;
+                       }
+                   }
+               }
+             __rtld_lock_unlock_recursive (tab->lock);
+           }
+
          /* We can unmap all the maps at once.  We determined the
             start address and length when we loaded the object and
             the `munmap' call does the rest.  */
          DL_UNMAP (imap);
 
          /* Finally, unlink the data structure and free it.  */
-         if (imap->l_prev != NULL)
-           imap->l_prev->l_next = imap->l_next;
-         else
+#if DL_NNS == 1
+         /* The assert in the (imap->l_prev == NULL) case gives
+            the compiler license to warn that NS points outside
+            the dl_ns array bounds in that case (as nsid != LM_ID_BASE
+            is tantamount to nsid >= DL_NNS).  That should be impossible
+            in this configuration, so just assert about it instead.  */
+         assert (nsid == LM_ID_BASE);
+         assert (imap->l_prev != NULL);
+#else
+         if (imap->l_prev == NULL)
            {
-#ifdef SHARED
              assert (nsid != LM_ID_BASE);
-#endif
              ns->_ns_loaded = imap->l_next;
+
+             /* Update the pointer to the head of the list
+                we leave for debuggers to examine.  */
+             r->r_map = (void *) ns->_ns_loaded;
            }
+         else
+#endif
+           imap->l_prev->l_next = imap->l_next;
 
          --ns->_ns_nloaded;
          if (imap->l_next != NULL)
@@ -656,7 +713,7 @@ _dl_close_worker (struct link_map *map)
          free (imap->l_reldeps);
 
          /* Print debugging message.  */
-         if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_FILES, 0))
+         if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
            _dl_debug_printf ("\nfile=%s [%lu];  destroying link map\n",
                              imap->l_name, imap->l_ns);
 
@@ -698,8 +755,8 @@ _dl_close_worker (struct link_map *map)
   /* If we removed any object which uses TLS bump the generation counter.  */
   if (any_tls)
     {
-      if (__builtin_expect (++GL(dl_tls_generation) == 0, 0))
-       _dl_fatal_printf ("TLS generation counter wrapped!  Please report as described in <http://www.gnu.org/software/libc/bugs.html>.\n");
+      if (__glibc_unlikely (++GL(dl_tls_generation) == 0))
+       _dl_fatal_printf ("TLS generation counter wrapped!  Please report as described in "REPORT_BUGS_TO".\n");
 
       if (tls_free_end == GL(dl_tls_static_used))
        GL(dl_tls_static_used) = tls_free_start;
@@ -707,7 +764,7 @@ _dl_close_worker (struct link_map *map)
 
 #ifdef SHARED
   /* Auditing checkpoint: we have deleted all objects.  */
-  if (__builtin_expect (do_audit, 0))
+  if (__glibc_unlikely (do_audit))
     {
       struct link_map *head = ns->_ns_loaded;
       /* Do not call the functions for any auditing object.  */
@@ -728,18 +785,13 @@ _dl_close_worker (struct link_map *map)
   if (__builtin_expect (ns->_ns_loaded == NULL, 0)
       && nsid == GL(dl_nns) - 1)
     do
-      {
-       --GL(dl_nns);
-#ifndef SHARED
-       if (GL(dl_nns) == 0)
-         break;
-#endif
-      }
+      --GL(dl_nns);
     while (GL(dl_ns)[GL(dl_nns) - 1]._ns_loaded == NULL);
 
   /* Notify the debugger those objects are finalized and gone.  */
   r->r_state = RT_CONSISTENT;
   _dl_debug_state ();
+  LIBC_PROBE (unmap_complete, 2, nsid, r);
 
   /* Recheck if we need to retry, release the lock.  */
  out:
@@ -755,21 +807,39 @@ _dl_close (void *_map)
 {
   struct link_map *map = _map;
 
-  /* First see whether we can remove the object at all.  */
-  if (__builtin_expect (map->l_flags_1 & DF_1_NODELETE, 0))
+  /* We must take the lock to examine the contents of map and avoid
+     concurrent dlopens.  */
+  __rtld_lock_lock_recursive (GL(dl_load_lock));
+
+  /* At this point we are guaranteed nobody else is touching the list of
+     loaded maps, but a concurrent dlclose might have freed our map
+     before we took the lock. There is no way to detect this (see below)
+     so we proceed assuming this isn't the case.  First see whether we
+     can remove the object at all.  */
+  if (__glibc_unlikely (map->l_flags_1 & DF_1_NODELETE))
     {
-      assert (map->l_init_called);
       /* Nope.  Do nothing.  */
+      __rtld_lock_unlock_recursive (GL(dl_load_lock));
       return;
     }
 
+  /* At present this is an unreliable check except in the case where the
+     caller has recursively called dlclose and we are sure the link map
+     has not been freed.  In a non-recursive dlclose the map itself
+     might have been freed and this access is potentially a data race
+     with whatever other use this memory might have now, or worse we
+     might silently corrupt memory if it looks enough like a link map.
+     POSIX has language in dlclose that appears to guarantee that this
+     should be a detectable case and given that dlclose should be threadsafe
+     we need this to be a reliable detection.
+     This is bug 20990. */
   if (__builtin_expect (map->l_direct_opencount, 1) == 0)
-    GLRO(dl_signal_error) (0, map->l_name, NULL, N_("shared object not open"));
-
-  /* Acquire the lock.  */
-  __rtld_lock_lock_recursive (GL(dl_load_lock));
+    {
+      __rtld_lock_unlock_recursive (GL(dl_load_lock));
+      _dl_signal_error (0, map->l_name, NULL, N_("shared object not open"));
+    }
 
-  _dl_close_worker (map);
+  _dl_close_worker (map, false);
 
   __rtld_lock_unlock_recursive (GL(dl_load_lock));
 }