313519
From fc859c304898a5ec72e0ba5269ed136ed0ea10e1 Mon Sep 17 00:00:00 2001
313519
From: Siddhesh Poyarekar <siddhesh@sourceware.org>
313519
Date: Wed, 7 Jul 2021 23:02:46 +0530
313519
Subject: Harden tcache double-free check
313519
313519
The tcache allocator layer uses the tcache pointer as a key to
313519
identify a block that may be freed twice.  Since this is in the
313519
application data area, an attacker exploiting a use-after-free could
313519
potentially get access to the entire tcache structure through this
313519
key.  A detailed write-up was provided by Awarau here:
313519
313519
https://awaraucom.wordpress.com/2020/07/19/house-of-io-remastered/
313519
313519
Replace this static pointer use for key checking with one that is
313519
generated at malloc initialization.  The first attempt is through
313519
getrandom with a fallback to random_bits(), which is a simple
313519
pseudo-random number generator based on the clock.  The fallback ought
313519
to be sufficient since the goal of the randomness is only to make the
313519
key arbitrary enough that it is very unlikely to collide with user
313519
data.
313519
313519
Co-authored-by: Eyal Itkin <eyalit@checkpoint.com>
313519
313519
[note: context for arena.c chunk #2 changed to accomodate missing
313519
tagging support code - DJ]
313519
313519
diff -rup a/malloc/arena.c b/malloc/arena.c
313519
--- a/malloc/arena.c	2022-09-16 01:09:02.003843024 -0400
313519
+++ b/malloc/arena.c	2022-09-16 01:25:51.879994057 -0400
313519
@@ -286,6 +286,10 @@ extern struct dl_open_hook *_dl_open_hoo
313519
 libc_hidden_proto (_dl_open_hook);
313519
 #endif
313519
 
313519
+#if USE_TCACHE
313519
+static void tcache_key_initialize (void);
313519
+#endif
313519
+
313519
 static void
313519
 ptmalloc_init (void)
313519
 {
313519
@@ -294,6 +298,10 @@ ptmalloc_init (void)
313519
 
313519
   __malloc_initialized = 0;
313519
 
313519
+#if USE_TCACHE
313519
+  tcache_key_initialize ();
313519
+#endif
313519
+
313519
 #ifdef SHARED
313519
   /* In case this libc copy is in a non-default namespace, never use brk.
313519
      Likewise if dlopened from statically linked program.  */
313519
diff -rup a/malloc/malloc.c b/malloc/malloc.c
313519
--- a/malloc/malloc.c	2022-09-16 01:09:05.491977387 -0400
313519
+++ b/malloc/malloc.c	2022-09-16 01:25:51.883994213 -0400
313519
@@ -247,6 +247,10 @@
313519
 /* For SINGLE_THREAD_P.  */
313519
 #include <sysdep-cancel.h>
313519
 
313519
+/* For tcache double-free check.  */
313519
+#include <random-bits.h>
313519
+#include <sys/random.h>
313519
+
313519
 /*
313519
   Debugging:
313519
 
313519
@@ -2924,7 +2928,7 @@ typedef struct tcache_entry
313519
 {
313519
   struct tcache_entry *next;
313519
   /* This field exists to detect double frees.  */
313519
-  struct tcache_perthread_struct *key;
313519
+  uintptr_t key;
313519
 } tcache_entry;
313519
 
313519
 /* There is one of these for each thread, which contains the
313519
@@ -2941,6 +2945,31 @@ typedef struct tcache_perthread_struct
313519
 static __thread bool tcache_shutting_down = false;
313519
 static __thread tcache_perthread_struct *tcache = NULL;
313519
 
313519
+/* Process-wide key to try and catch a double-free in the same thread.  */
313519
+static uintptr_t tcache_key;
313519
+
313519
+/* The value of tcache_key does not really have to be a cryptographically
313519
+   secure random number.  It only needs to be arbitrary enough so that it does
313519
+   not collide with values present in applications.  If a collision does happen
313519
+   consistently enough, it could cause a degradation in performance since the
313519
+   entire list is checked to check if the block indeed has been freed the
313519
+   second time.  The odds of this happening are exceedingly low though, about 1
313519
+   in 2^wordsize.  There is probably a higher chance of the performance
313519
+   degradation being due to a double free where the first free happened in a
313519
+   different thread; that's a case this check does not cover.  */
313519
+static void
313519
+tcache_key_initialize (void)
313519
+{
313519
+  if (__getrandom (&tcache_key, sizeof(tcache_key), GRND_NONBLOCK)
313519
+      != sizeof (tcache_key))
313519
+    {
313519
+      tcache_key = random_bits ();
313519
+#if __WORDSIZE == 64
313519
+      tcache_key = (tcache_key << 32) | random_bits ();
313519
+#endif
313519
+    }
313519
+}
313519
+
313519
 /* Caller must ensure that we know tc_idx is valid and there's room
313519
    for more chunks.  */
313519
 static __always_inline void
313519
@@ -2950,7 +2979,7 @@ tcache_put (mchunkptr chunk, size_t tc_i
313519
 
313519
   /* Mark this chunk as "in the tcache" so the test in _int_free will
313519
      detect a double free.  */
313519
-  e->key = tcache;
313519
+  e->key = tcache_key;
313519
 
313519
   e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
313519
   tcache->entries[tc_idx] = e;
313519
@@ -2967,7 +2996,7 @@ tcache_get (size_t tc_idx)
313519
     malloc_printerr ("malloc(): unaligned tcache chunk detected");
313519
   tcache->entries[tc_idx] = REVEAL_PTR (e->next);
313519
   --(tcache->counts[tc_idx]);
313519
-  e->key = NULL;
313519
+  e->key = 0;
313519
   return (void *) e;
313519
 }
313519
 
313519
@@ -4231,7 +4260,7 @@ _int_free (mstate av, mchunkptr p, int h
313519
 	   trust it (it also matches random payload data at a 1 in
313519
 	   2^<size_t> chance), so verify it's not an unlikely
313519
 	   coincidence before aborting.  */
313519
-	if (__glibc_unlikely (e->key == tcache))
313519
+	if (__glibc_unlikely (e->key == tcache_key))
313519
 	  {
313519
 	    tcache_entry *tmp;
313519
 	    size_t cnt = 0;