>From ceddf789dcd662d87d081e783c7c4bbc46eeb5d7 Mon Sep 17 00:00:00 2001
From: Florian Weimer <fweimer@redhat.com>
Date: Wed, 28 Oct 2015 20:39:38 +0100
Subject: [PATCH] Resolves: #1276112

Prevent malloc arena free list from becoming cyclic.
---
 glibc-rh1276112.patch | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++
 glibc.spec            |   9 +-
 2 files changed, 229 insertions(+), 1 deletion(-)
 create mode 100644 glibc-rh1276112.patch

diff --git a/glibc-rh1276112.patch b/glibc-rh1276112.patch
new file mode 100644
index 0000000..7cc3b9e
--- /dev/null
+++ b/glibc-rh1276112.patch
@@ -0,0 +1,221 @@
+commit 85ee11e317058d44d5c6c29bb4c53acc6c0e22c9
+Author: Florian Weimer <fweimer@redhat.com>
+Date:   Wed Oct 28 19:32:46 2015 +0100
+
+    malloc: Prevent arena free_list from turning cyclic [BZ #19048]
+    
+        [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.
+
+(Backport reintroduces tsd_getspecific, tsd_setspecific.)
+
+diff -up glibc-2.21/malloc/arena.c.bz1276112 glibc-2.21/malloc/arena.c
+--- glibc-2.21/malloc/arena.c.bz1276112	2015-10-28 20:04:37.043402183 +0100
++++ glibc-2.21/malloc/arena.c	2015-10-28 20:19:32.570584876 +0100
+@@ -67,6 +67,12 @@ extern int sanity_check_heap_info_alignm
+ /* Thread specific data */
+ 
+ static tsd_key_t arena_key;
++
++/* 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;
+ static mstate free_list;
+@@ -233,7 +239,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.  */
+   tsd_getspecific (arena_key, save_arena);
+   tsd_setspecific (arena_key, ATFORK_ARENA_PTR);
+ out:
+@@ -251,6 +260,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.  */
+   tsd_setspecific (arena_key, save_arena);
+   __malloc_hook = save_malloc_hook;
+   __free_hook = save_free_hook;
+@@ -282,12 +294,19 @@ ptmalloc_unlock_all2 (void)
+   tsd_setspecific (arena_key, 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;
+         }
+@@ -714,6 +733,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)
+ {
+@@ -735,6 +770,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;
+@@ -748,12 +784,19 @@ _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;
++  {
++    void *vptr = NULL;
++    replaced_arena = tsd_getspecific (arena_key, vptr);
++  }
+   tsd_setspecific (arena_key, (void *) 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 ();
+@@ -768,13 +811,24 @@ _int_new_arena (size_t size)
+ static mstate
+ get_free_list (void)
+ {
++  void *vptr = NULL;
++  mstate replaced_arena = tsd_getspecific (arena_key, vptr);
+   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)
+@@ -819,6 +873,15 @@ reused_arena (mstate avoid_arena)
+   (void) mutex_lock (&result->mutex);
+ 
+ out:
++  {
++    void *vptr = NULL;
++    mstate replaced_arena = tsd_getspecific (arena_key, vptr);
++    (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);
+   tsd_setspecific (arena_key, (void *) result);
+   next_to_use = result->next;
+@@ -912,8 +975,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);
+     }
+ }
+diff -up glibc-2.21/malloc/malloc.c.bz1276112 glibc-2.21/malloc/malloc.c
+--- glibc-2.21/malloc/malloc.c.bz1276112	2015-10-28 20:04:45.171449221 +0100
++++ glibc-2.21/malloc/malloc.c	2015-10-28 20:05:39.124761465 +0100
+@@ -1700,9 +1700,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;
+@@ -1746,7 +1752,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.  */
diff --git a/glibc.spec b/glibc.spec
index c637a00..0bcccbb 100644
--- a/glibc.spec
+++ b/glibc.spec
@@ -1,6 +1,6 @@
 %define glibcsrcdir  glibc-2.21
 %define glibcversion 2.21
-%define glibcrelease 8%{?dist}
+%define glibcrelease 9%{?dist}
 # Pre-release tarballs are pulled in from git using a command that is
 # effectively:
 #
@@ -254,6 +254,9 @@ Patch2031: %{name}-rh1070416.patch
 Patch2033: %{name}-aarch64-tls-fixes.patch
 Patch2034: %{name}-aarch64-workaround-nzcv-clobber-in-tlsdesc.patch
 
+# Upstream BZ 19048
+Patch2035: %{name}-rh1276112.patch
+
 ##############################################################################
 # End of glibc patches.
 ##############################################################################
@@ -583,6 +586,7 @@ package or when debugging this package.
 %patch0047 -p1
 %patch2033 -p1
 %patch2034 -p1
+%patch2035 -p1
 %patch0050 -p1
 %patch0052 -p1
 %patch0053 -p1
@@ -1762,6 +1766,9 @@ rm -f *.filelist*
 %endif
 
 %changelog
+* Wed Oct 28 2015 Florian Weimer <fweimer@redhat.com> - 2.21-9
+- Prevent malloc arena free list from becoming cyclic.  (#1276112)
+
 * Wed Sep 16 2015 Mike FABIAN <mfabian@redhat.com> - 2.22-8
 - build-locale-archive sometimes created empty archives (fixed by David Shea) (#1262040)
 
-- 
2.4.3

