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