]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
malloc: Prevent arena free_list from turning cyclic [BZ #19048]
authorFlorian Weimer <fweimer@redhat.com>
Wed, 28 Oct 2015 18:32:46 +0000 (19:32 +0100)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Fri, 27 Jan 2017 15:16:27 +0000 (13:16 -0200)
[BZ# 19048]
* malloc/malloc.c (struct malloc_state): Update comment.  Add
attached_threads member.
(main_arena): Initialize attached_threads.
* malloc/arena.c (list_lock): Update comment.
(ptmalloc_lock_all, ptmalloc_unlock_all): Likewise.
(ptmalloc_unlock_all2): Reinitialize arena reference counts.
(deattach_arena): New function.
(_int_new_arena): Initialize arena reference count and deattach
replaced arena.
(get_free_list, reused_arena): Update reference count and deattach
replaced arena.
(arena_thread_freeres): Update arena reference count and only put
unreferenced arenas on the free list.

ChangeLog
NEWS
malloc/arena.c
malloc/malloc.c

index 1a7d1fc149877201371be6aed03d4a2b83b228b4..75615ab91e9c2a2894913f2c8b4c7ad0459eaec7 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2015-10-28  Florian Weimer  <fweimer@redhat.com>
+
+       [BZ# 19048]
+       * malloc/malloc.c (struct malloc_state): Update comment.  Add
+       attached_threads member.
+       (main_arena): Initialize attached_threads.
+       * malloc/arena.c (list_lock): Update comment.
+       (ptmalloc_lock_all, ptmalloc_unlock_all): Likewise.
+       (ptmalloc_unlock_all2): Reinitialize arena reference counts.
+       (deattach_arena): New function.
+       (_int_new_arena): Initialize arena reference count and deattach
+       replaced arena.
+       (get_free_list, reused_arena): Update reference count and deattach
+       replaced arena.
+       (arena_thread_freeres): Update arena reference count and only put
+       unreferenced arenas on the free list.
+
 2015-10-17  Florian Weimer  <fweimer@redhat.com>
 
        malloc: Rewrite with explicit TLS access using __thread.
diff --git a/NEWS b/NEWS
index c27c29ff5a1afbd319348e7e4d2ed3b2b8baf34b..147d5ffdde887bf0daa81a6eb09efc67355e22b5 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -10,7 +10,7 @@ Version 2.21.1
 * The following bugs are resolved with this release:
 
   17269, 17905, 17949, 18007, 18032, 18080, 18240, 18287, 18508, 18694,
-  18887, 18985, 19682.
+  18887, 18985, 19048, 19682.
 
 * A stack-based buffer overflow was found in libresolv when invoked from
   libnss_dns, allowing specially crafted DNS responses to seize control
index c726ab647394e643368c6644da87b4a4315d9b11..dff98702f168cc033eee24fd634339c1070e522f 100644 (file)
@@ -68,7 +68,10 @@ extern int sanity_check_heap_info_alignment[(sizeof (heap_info)
 
 static __thread mstate thread_arena attribute_tls_model_ie;
 
-/* Arena free list.  */
+/* Arena free list.  list_lock protects the free_list variable below,
+   and the next_free and attached_threads members of the mstate
+   objects.  No other (malloc) locks must be taken while list_lock is
+   active, otherwise deadlocks may occur.  */
 
 static mutex_t list_lock = MUTEX_INITIALIZER;
 static size_t narenas = 1;
@@ -225,7 +228,10 @@ ptmalloc_lock_all (void)
   save_free_hook = __free_hook;
   __malloc_hook = malloc_atfork;
   __free_hook = free_atfork;
-  /* Only the current thread may perform malloc/free calls now. */
+  /* Only the current thread may perform malloc/free calls now.
+     save_arena will be reattached to the current thread, in
+     ptmalloc_lock_all, so save_arena->attached_threads is not
+     updated.  */
   save_arena = thread_arena;
   thread_arena = ATFORK_ARENA_PTR;
 out:
@@ -243,6 +249,9 @@ ptmalloc_unlock_all (void)
   if (--atfork_recursive_cntr != 0)
     return;
 
+  /* Replace ATFORK_ARENA_PTR with save_arena.
+     save_arena->attached_threads was not changed in ptmalloc_lock_all
+     and is still correct.  */
   thread_arena = save_arena;
   __malloc_hook = save_malloc_hook;
   __free_hook = save_free_hook;
@@ -274,12 +283,19 @@ ptmalloc_unlock_all2 (void)
   thread_arena = save_arena;
   __malloc_hook = save_malloc_hook;
   __free_hook = save_free_hook;
+
+  /* Push all arenas to the free list, except save_arena, which is
+     attached to the current thread.  */
+  if (save_arena != NULL)
+    ((mstate) save_arena)->attached_threads = 1;
   free_list = NULL;
   for (ar_ptr = &main_arena;; )
     {
       mutex_init (&ar_ptr->mutex);
       if (ar_ptr != save_arena)
         {
+         /* This arena is no longer attached to any thread.  */
+         ar_ptr->attached_threads = 0;
           ar_ptr->next_free = free_list;
           free_list = ar_ptr;
         }
@@ -705,6 +721,22 @@ heap_trim (heap_info *heap, size_t pad)
 
 /* Create a new arena with initial size "size".  */
 
+/* If REPLACED_ARENA is not NULL, detach it from this thread.  Must be
+   called while list_lock is held.  */
+static void
+detach_arena (mstate replaced_arena)
+{
+  if (replaced_arena != NULL)
+    {
+      assert (replaced_arena->attached_threads > 0);
+      /* The current implementation only detaches from main_arena in
+        case of allocation failure.  This means that it is likely not
+        beneficial to put the arena on free_list even if the
+        reference count reaches zero.  */
+      --replaced_arena->attached_threads;
+    }
+}
+
 static mstate
 _int_new_arena (size_t size)
 {
@@ -726,6 +758,7 @@ _int_new_arena (size_t size)
     }
   a = h->ar_ptr = (mstate) (h + 1);
   malloc_init_state (a);
+  a->attached_threads = 1;
   /*a->next = NULL;*/
   a->system_mem = a->max_system_mem = h->size;
   arena_mem += h->size;
@@ -739,12 +772,15 @@ _int_new_arena (size_t size)
   set_head (top (a), (((char *) h + h->size) - ptr) | PREV_INUSE);
 
   LIBC_PROBE (memory_arena_new, 2, a, size);
+  mstate replaced_arena = thread_arena;
   thread_arena = a;
   mutex_init (&a->mutex);
   (void) mutex_lock (&a->mutex);
 
   (void) mutex_lock (&list_lock);
 
+  detach_arena (replaced_arena);
+
   /* Add the new arena to the global list.  */
   a->next = main_arena.next;
   atomic_write_barrier ();
@@ -759,13 +795,23 @@ _int_new_arena (size_t size)
 static mstate
 get_free_list (void)
 {
+  mstate replaced_arena = thread_arena;
   mstate result = free_list;
   if (result != NULL)
     {
       (void) mutex_lock (&list_lock);
       result = free_list;
       if (result != NULL)
-        free_list = result->next_free;
+       {
+         free_list = result->next_free;
+
+         /* Arenas on the free list are not attached to any thread.  */
+         assert (result->attached_threads == 0);
+         /* But the arena will now be attached to this thread.  */
+         result->attached_threads = 1;
+
+         detach_arena (replaced_arena);
+       }
       (void) mutex_unlock (&list_lock);
 
       if (result != NULL)
@@ -810,6 +856,14 @@ reused_arena (mstate avoid_arena)
   (void) mutex_lock (&result->mutex);
 
 out:
+  {
+    mstate replaced_arena = thread_arena;
+    (void) mutex_lock (&list_lock);
+    detach_arena (replaced_arena);
+    ++result->attached_threads;
+    (void) mutex_unlock (&list_lock);
+  }
+
   LIBC_PROBE (memory_arena_reuse, 2, result, avoid_arena);
   thread_arena = result;
   next_to_use = result->next;
@@ -902,8 +956,14 @@ arena_thread_freeres (void)
   if (a != NULL)
     {
       (void) mutex_lock (&list_lock);
-      a->next_free = free_list;
-      free_list = a;
+      /* If this was the last attached thread for this arena, put the
+        arena on the free list.  */
+      assert (a->attached_threads > 0);
+      if (--a->attached_threads == 0)
+       {
+         a->next_free = free_list;
+         free_list = a;
+       }
       (void) mutex_unlock (&list_lock);
     }
 }
index d2d80388ca90ecaee17798aa1901364d805789a4..94066d2e8c66fc98572ecee6bd2ff4687b6b553f 100644 (file)
@@ -1696,9 +1696,15 @@ struct malloc_state
   /* Linked list */
   struct malloc_state *next;
 
-  /* Linked list for free arenas.  */
+  /* Linked list for free arenas.  Access to this field is serialized
+     by list_lock in arena.c.  */
   struct malloc_state *next_free;
 
+  /* Number of threads attached to this arena.  0 if the arena is on
+     the free list.  Access to this field is serialized by list_lock
+     in arena.c.  */
+  INTERNAL_SIZE_T attached_threads;
+
   /* Memory allocated from the system in this arena.  */
   INTERNAL_SIZE_T system_mem;
   INTERNAL_SIZE_T max_system_mem;
@@ -1742,7 +1748,8 @@ struct malloc_par
 static struct malloc_state main_arena =
 {
   .mutex = MUTEX_INITIALIZER,
-  .next = &main_arena
+  .next = &main_arena,
+  .attached_threads = 1
 };
 
 /* There is only one instance of the malloc parameters.  */