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