about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--malloc/arena.c17
-rw-r--r--malloc/malloc.c167
-rw-r--r--sysdeps/aarch64/morello/libc-cap.h295
-rw-r--r--sysdeps/generic/libc-cap.h9
4 files changed, 470 insertions, 18 deletions
diff --git a/malloc/arena.c b/malloc/arena.c
index b1f5b4117f..894f49b911 100644
--- a/malloc/arena.c
+++ b/malloc/arena.c
@@ -191,6 +191,7 @@ __malloc_fork_lock_parent (void)
       if (ar_ptr == &main_arena)
         break;
     }
+  cap_fork_lock ();
 }
 
 void
@@ -199,6 +200,8 @@ __malloc_fork_unlock_parent (void)
   if (!__malloc_initialized)
     return;
 
+  cap_fork_unlock_parent ();
+
   for (mstate ar_ptr = &main_arena;; )
     {
       __libc_lock_unlock (ar_ptr->mutex);
@@ -215,6 +218,8 @@ __malloc_fork_unlock_child (void)
   if (!__malloc_initialized)
     return;
 
+  cap_fork_unlock_child ();
+
   /* Push all arenas to the free list, except thread_arena, which is
      attached to the current thread.  */
   __libc_lock_init (free_list_lock);
@@ -321,6 +326,8 @@ ptmalloc_init (void)
   tcache_key_initialize ();
 #endif
 
+  cap_init ();
+
 #ifdef USE_MTAG
   if ((TUNABLE_GET_FULL (glibc, mem, tagging, int32_t, NULL) & 1) != 0)
     {
@@ -526,6 +533,9 @@ alloc_new_heap  (size_t size, size_t top_pad, size_t pagesize,
           else
             aligned_heap_area = p2 + max_size;
           __munmap (p2 + max_size, max_size - ul);
+#ifdef __CHERI_PURE_CAPABILITY__
+	  p2 = __builtin_cheri_bounds_set_exact (p2, max_size);
+#endif
         }
       else
         {
@@ -548,6 +558,12 @@ alloc_new_heap  (size_t size, size_t top_pad, size_t pagesize,
       return 0;
     }
 
+  if (!cap_map_add (p2))
+    {
+      __munmap (p2, max_size);
+      return 0;
+    }
+
   madvise_thp (p2, size);
 
   h = (heap_info *) p2;
@@ -670,6 +686,7 @@ heap_trim (heap_info *heap, size_t pad)
       LIBC_PROBE (memory_heap_free, 2, heap, heap->size);
       if ((char *) heap + max_size == aligned_heap_area)
 	aligned_heap_area = NULL;
+      cap_map_del (heap);
       __munmap (heap, max_size);
       heap = prev_heap;
       if (!prev_inuse (p)) /* consolidate backward */
diff --git a/malloc/malloc.c b/malloc/malloc.c
index 0467e46e38..392116a5ac 100644
--- a/malloc/malloc.c
+++ b/malloc/malloc.c
@@ -492,6 +492,49 @@ static bool cap_narrowing_enabled = true;
 # define cap_narrowing_enabled 0
 #endif
 
+static __always_inline void
+cap_init (void)
+{
+  if (cap_narrowing_enabled)
+    assert (__libc_cap_init ());
+}
+
+static __always_inline void
+cap_fork_lock (void)
+{
+  if (cap_narrowing_enabled)
+    __libc_cap_fork_lock ();
+}
+
+static __always_inline void
+cap_fork_unlock_parent (void)
+{
+  if (cap_narrowing_enabled)
+    __libc_cap_fork_unlock_parent ();
+}
+
+static __always_inline void
+cap_fork_unlock_child (void)
+{
+  if (cap_narrowing_enabled)
+    __libc_cap_fork_unlock_child ();
+}
+
+static __always_inline bool
+cap_map_add (void *p)
+{
+  if (cap_narrowing_enabled)
+    return __libc_cap_map_add (p);
+  return true;
+}
+
+static __always_inline void
+cap_map_del (void *p)
+{
+  if (cap_narrowing_enabled)
+    __libc_cap_map_del (p);
+}
+
 /* Round up size so capability bounds can be represented.  */
 static __always_inline size_t
 cap_roundup (size_t n)
@@ -510,12 +553,48 @@ cap_align (size_t n)
   return 1;
 }
 
-/* Narrow the bounds of p to [p, p+n) exactly unless p is NULL.  */
+/* Narrow the bounds of p to [p, p+n) exactly unless p is NULL.
+   Must match a previous cap_reserve call.  */
 static __always_inline void *
 cap_narrow (void *p, size_t n)
 {
-  if (cap_narrowing_enabled && p != NULL)
-    return __libc_cap_narrow (p, n);
+  if (cap_narrowing_enabled)
+    {
+      if (p == NULL)
+	__libc_cap_unreserve ();
+      else
+        p = __libc_cap_narrow (p, n);
+    }
+  return p;
+}
+
+/* Used in realloc if p is already narrowed or NULL.
+   Must match a previous cap_reserve call.  */
+static __always_inline bool
+cap_narrow_check (void *p, void *oldp)
+{
+  if (cap_narrowing_enabled)
+    {
+      if (p == NULL)
+	(void) __libc_cap_narrow (oldp, 0);
+      else
+	__libc_cap_unreserve ();
+    }
+  return p != NULL;
+}
+
+/* Used in realloc if p is new allocation or NULL but not yet narrowed.
+   Must match a previous cap_reserve call.  */
+static __always_inline void *
+cap_narrow_try (void *p, size_t n, void *oldp)
+{
+  if (cap_narrowing_enabled)
+    {
+      if (p == NULL)
+	(void) __libc_cap_narrow (oldp, 0);
+      else
+	p = __libc_cap_narrow (p, n);
+    }
   return p;
 }
 
@@ -528,6 +607,31 @@ cap_widen (void *p)
   return p;
 }
 
+/* Reserve memory for the following cap_narrow, this may fail with ENOMEM.  */
+static __always_inline bool
+cap_reserve (void)
+{
+  if (cap_narrowing_enabled)
+    return __libc_cap_reserve ();
+  return true;
+}
+
+/* Release the reserved memory by cap_reserve.  */
+static __always_inline void
+cap_unreserve (void)
+{
+  if (cap_narrowing_enabled)
+    __libc_cap_unreserve ();
+}
+
+/* Remove p so cap_widen no longer works on it.  */
+static __always_inline void
+cap_drop (void *p)
+{
+  if (cap_narrowing_enabled)
+    __libc_cap_drop (p);
+}
+
 #include <string.h>
 
 /*
@@ -2508,6 +2612,12 @@ sysmalloc_mmap (INTERNAL_SIZE_T nb, size_t pagesize, int extra_flags, mstate av)
   if (mm == MAP_FAILED)
     return mm;
 
+  if (!cap_map_add (mm))
+    {
+      __munmap (mm, size);
+      return MAP_FAILED;
+    }
+
 #ifdef MAP_HUGETLB
   if (!(extra_flags & MAP_HUGETLB))
     madvise_thp (mm, size);
@@ -2593,6 +2703,12 @@ sysmalloc_mmap_fallback (long int *s, INTERNAL_SIZE_T nb,
   if (mbrk == MAP_FAILED)
     return MAP_FAILED;
 
+  if (!cap_map_add (mbrk))
+    {
+      __munmap (mbrk, size);
+      return MAP_FAILED;
+    }
+
 #ifdef MAP_HUGETLB
   if (!(extra_flags & MAP_HUGETLB))
     madvise_thp (mbrk, size);
@@ -3126,6 +3242,8 @@ munmap_chunk (mchunkptr p)
   atomic_decrement (&mp_.n_mmaps);
   atomic_add (&mp_.mmapped_mem, -total_size);
 
+  cap_map_del ((void *) block);
+
   /* If munmap failed the process virtual memory address space is in a
      bad shape.  Just leave the block hanging around, the process will
      terminate shortly anyway since not much can be done.  */
@@ -3164,6 +3282,9 @@ mremap_chunk (mchunkptr p, size_t new_size)
   if (cp == MAP_FAILED)
     return 0;
 
+  cap_map_del ((void *) block);
+  cap_map_add (cp);
+
   madvise_thp (cp, new_size);
 
   p = (mchunkptr) (cp + offset);
@@ -3409,6 +3530,8 @@ __libc_malloc (size_t bytes)
       && tcache
       && tcache->counts[tc_idx] > 0)
     {
+      if (!cap_reserve ())
+	return NULL;
       victim = tcache_get (tc_idx);
       victim = tag_new_usable (victim);
       victim = cap_narrow (victim, bytes);
@@ -3420,6 +3543,9 @@ __libc_malloc (size_t bytes)
   if (align > MALLOC_ALIGNMENT)
     return _mid_memalign (align, bytes, 0);
 
+  if (!cap_reserve ())
+    return NULL;
+
   if (SINGLE_THREAD_P)
     {
       victim = tag_new_usable (_int_malloc (&main_arena, bytes));
@@ -3463,6 +3589,7 @@ __libc_free (void *mem)
     return;
 
   mem = cap_widen (mem);
+  cap_drop (mem);
 
   /* Quickly check that the freed pointer matches the tag for the memory.
      This gives a useful double-free detection.  */
@@ -3562,6 +3689,11 @@ __libc_realloc (void *oldmem, size_t bytes)
       return NULL;
     }
 
+  /* Every return path below should unreserve using the cap_narrow* apis.  */
+  if (!cap_reserve ())
+    return NULL;
+  cap_drop (oldmem);
+
   if (chunk_is_mmapped (oldp))
     {
       void *newmem;
@@ -3585,7 +3717,7 @@ __libc_realloc (void *oldmem, size_t bytes)
 	     caller for doing this, so we might want to
 	     reconsider.  */
 	  newmem = tag_new_usable (newmem);
-	  newmem = cap_narrow (newmem, bytes);
+	  newmem = cap_narrow_try (newmem, bytes, oldmem);
 	  return newmem;
 	}
 #endif
@@ -3610,7 +3742,7 @@ __libc_realloc (void *oldmem, size_t bytes)
       else
 #endif
       newmem = __libc_malloc (bytes);
-      if (newmem == 0)
+      if (!cap_narrow_check (newmem, oldmem))
         return 0;              /* propagate failure */
 
 #ifdef __CHERI_PURE_CAPABILITY__
@@ -3628,7 +3760,7 @@ __libc_realloc (void *oldmem, size_t bytes)
     {
       /* Use memalign, copy, free.  */
       void *newmem = _mid_memalign (align, bytes, 0);
-      if (newmem == NULL)
+      if (!cap_narrow_check (newmem, oldmem))
 	return newmem;
       size_t sz = oldsize - CHUNK_HDR_SZ;
       memcpy (newmem, oldmem, sz < bytes ? sz : bytes);
@@ -3642,8 +3774,7 @@ __libc_realloc (void *oldmem, size_t bytes)
       newp = _int_realloc (ar_ptr, oldp, oldsize, nb);
       assert (!newp || chunk_is_mmapped (mem2chunk (newp)) ||
 	      ar_ptr == arena_for_chunk (mem2chunk (newp)));
-
-      return cap_narrow (newp, bytes);
+      return cap_narrow_try (newp, bytes, oldmem);
     }
 
   __libc_lock_lock (ar_ptr->mutex);
@@ -3659,13 +3790,12 @@ __libc_realloc (void *oldmem, size_t bytes)
       /* Try harder to allocate memory in other arenas.  */
       LIBC_PROBE (memory_realloc_retry, 2, bytes, oldmem);
       newp = __libc_malloc (bytes);
-      if (newp != NULL)
-        {
-	  size_t sz = memsize (oldp);
-	  memcpy (newp, oldmem, sz);
-	  (void) tag_region (chunk2mem (oldp), sz);
-          _int_free (ar_ptr, oldp, 0);
-        }
+      if (!cap_narrow_check (newp, oldmem))
+	return NULL;
+      size_t sz = memsize (oldp);
+      memcpy (newp, oldmem, sz);
+      (void) tag_region (chunk2mem (oldp), sz);
+      _int_free (ar_ptr, oldp, 0);
     }
   else
     newp = cap_narrow (newp, bytes);
@@ -3711,6 +3841,8 @@ _mid_memalign (size_t alignment, size_t bytes, void *address)
       return 0;
     }
 
+  if (!cap_reserve ())
+    return NULL;
 
   /* Make sure alignment is power of 2.  */
   if (!powerof2 (alignment))
@@ -3833,6 +3965,9 @@ __libc_calloc (size_t n, size_t elem_size)
 
   MAYBE_INIT_TCACHE ();
 
+  if (!cap_reserve ())
+    return NULL;
+
   if (SINGLE_THREAD_P)
     av = &main_arena;
   else
@@ -3885,6 +4020,8 @@ __libc_calloc (size_t n, size_t elem_size)
 
   /* Allocation failed even after a retry.  */
   if (mem == 0)
+    cap_unreserve ();
+  if (mem == 0)
     return 0;
 
   mchunkptr p = mem2chunk (mem);
diff --git a/sysdeps/aarch64/morello/libc-cap.h b/sysdeps/aarch64/morello/libc-cap.h
index d772e36dee..19ccc47ada 100644
--- a/sysdeps/aarch64/morello/libc-cap.h
+++ b/sysdeps/aarch64/morello/libc-cap.h
@@ -19,6 +19,268 @@
 #ifndef _AARCH64_MORELLO_LIBC_CAP_H
 #define _AARCH64_MORELLO_LIBC_CAP_H 1
 
+#include <stdint.h>
+#include <sys/mman.h>
+#include <libc-lock.h>
+
+/* Hash table for __libc_cap_widen.  */
+
+#define HT_MIN_LEN (65536 / sizeof (struct htentry))
+#define HT_MAX_LEN (1UL << 58)
+
+struct htentry
+{
+  uint64_t key;
+  uint64_t unused;
+  void *value;
+};
+
+struct ht
+{
+  __libc_lock_define(,mutex);
+  size_t mask;    /* Length - 1, note: length is powerof2.  */
+  size_t fill;    /* Used + deleted entries.  */
+  size_t used;
+  size_t reserve; /* Planned adds.  */
+  struct htentry *tab;
+};
+
+static inline bool
+htentry_isempty (struct htentry *e)
+{
+  return e->key == 0;
+}
+
+static inline bool
+htentry_isdeleted (struct htentry *e)
+{
+  return e->key == -1;
+}
+
+static inline bool
+htentry_isused (struct htentry *e)
+{
+  return e->key != 0 && e->key != -1;
+}
+
+static inline uint64_t
+ht_key_hash (uint64_t key)
+{
+  return (key >> 4) ^ (key >> 18);
+}
+
+static struct htentry *
+ht_tab_alloc (size_t n)
+{
+  size_t size = n * sizeof (struct htentry);
+  assert (size && (size & 65535) == 0);
+  void *p = __mmap (0, size, PROT_READ|PROT_WRITE,
+		    MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
+  if (p == MAP_FAILED)
+    return NULL;
+  return p;
+}
+
+static void
+ht_tab_free (struct htentry *tab, size_t n)
+{
+  int r = __munmap (tab, n * sizeof (struct htentry));
+  assert (r == 0);
+}
+
+static bool
+ht_init (struct ht *ht)
+{
+  __libc_lock_init (ht->mutex);
+  ht->mask = HT_MIN_LEN - 1;
+  ht->fill = 0;
+  ht->used = 0;
+  ht->reserve = 0;
+  ht->tab = ht_tab_alloc (ht->mask + 1);
+  return ht->tab != NULL;
+}
+
+static struct htentry *
+ht_lookup (struct ht *ht, uint64_t key, uint64_t hash)
+{
+  size_t mask = ht->mask;
+  size_t i = hash;
+  size_t j;
+  struct htentry *e = ht->tab + (i & mask);
+  struct htentry *del;
+
+  if (e->key == key || htentry_isempty (e))
+    return e;
+  if (htentry_isdeleted (e))
+    del = e;
+  else
+    del = NULL;
+
+  /* Quadratic probing.  */
+  for (j =1, i += j++; ; i += j++)
+    {
+      e = ht->tab + (i & mask);
+      if (e->key == key)
+	return e;
+      if (htentry_isempty (e))
+	return del != NULL ? del : e;
+      if (del == NULL && htentry_isdeleted (e))
+	del = e;
+    }
+}
+
+static bool
+ht_resize (struct ht *ht)
+{
+  size_t len;
+  size_t used = ht->used;
+  size_t n = ht->used + ht->reserve;
+  size_t oldlen = ht->mask + 1;
+
+  if (2 * n >= HT_MAX_LEN)
+    len = HT_MAX_LEN;
+  else
+    for (len = HT_MIN_LEN; len < 2 * n; len *= 2);
+  struct htentry *newtab = ht_tab_alloc (len);
+  struct htentry *oldtab = ht->tab;
+  struct htentry *e;
+  if (newtab == NULL)
+    return false;
+
+  ht->tab = newtab;
+  ht->mask = len - 1;
+  ht->fill = ht->used;
+  for (e = oldtab; used > 0; e++)
+    {
+      if (htentry_isused (e))
+	{
+	  uint64_t hash = ht_key_hash (e->key);
+	  used--;
+	  *ht_lookup (ht, e->key, hash) = *e;
+	}
+    }
+  ht_tab_free (oldtab, oldlen);
+  return true;
+}
+
+static bool
+ht_reserve (struct ht *ht)
+{
+  bool r = true;
+  __libc_lock_lock (ht->mutex);
+  ht->reserve++;
+  size_t future_fill = ht->fill + ht->reserve;
+  size_t future_used = ht->used + ht->reserve;
+  /* Resize at 3/4 fill or if there are many deleted entries.  */
+  if (future_fill > ht->mask - ht->mask / 4
+      || future_fill > future_used * 4)
+    r = ht_resize (ht);
+  if (!r)
+    ht->reserve--;
+  __libc_lock_unlock (ht->mutex);
+  return r;
+}
+
+static void
+ht_unreserve (struct ht *ht)
+{
+  __libc_lock_lock (ht->mutex);
+  assert (ht->reserve > 0);
+  ht->reserve--;
+  __libc_lock_unlock (ht->mutex);
+}
+
+static bool
+ht_add (struct ht *ht, uint64_t key, void *value)
+{
+  __libc_lock_lock (ht->mutex);
+  assert (ht->reserve > 0);
+  ht->reserve--;
+  uint64_t hash = ht_key_hash (key);
+  struct htentry *e = ht_lookup (ht, key, hash);
+  bool r = false;
+  if (!htentry_isused (e))
+    {
+      if (htentry_isempty (e))
+        ht->fill++;
+      ht->used++;
+      e->key = key;
+      r = true;
+    }
+  e->value = value;
+  __libc_lock_unlock (ht->mutex);
+  return r;
+}
+
+static bool
+ht_del (struct ht *ht, uint64_t key)
+{
+  __libc_lock_lock (ht->mutex);
+  struct htentry *e = ht_lookup (ht, key, ht_key_hash (key));
+  bool r = htentry_isused (e);
+  if (r)
+    {
+      ht->used--;
+      e->key = -1;
+    }
+  __libc_lock_unlock (ht->mutex);
+  return r;
+}
+
+static void *
+ht_get (struct ht *ht, uint64_t key)
+{
+  __libc_lock_lock (ht->mutex);
+  struct htentry *e = ht_lookup (ht, key, ht_key_hash (key));
+  void *v = htentry_isused (e) ? e->value : NULL;
+  __libc_lock_unlock (ht->mutex);
+  return v;
+}
+
+/* Capability narrowing APIs.  */
+
+static struct ht __libc_cap_ht;
+
+static __always_inline bool
+__libc_cap_init (void)
+{
+  return ht_init (&__libc_cap_ht);
+}
+
+static __always_inline void
+__libc_cap_fork_lock (void)
+{
+  __libc_lock_lock (__libc_cap_ht.mutex);
+}
+
+static __always_inline void
+__libc_cap_fork_unlock_parent (void)
+{
+  __libc_lock_unlock (__libc_cap_ht.mutex);
+}
+
+static __always_inline void
+__libc_cap_fork_unlock_child (void)
+{
+  __libc_lock_init (__libc_cap_ht.mutex);
+}
+
+static __always_inline bool
+__libc_cap_map_add (void *p)
+{
+  assert (p != NULL);
+// TODO: depends on pcuabi
+//  assert (__builtin_cheri_base_get (p) == (uint64_t) p);
+  return true;
+}
+
+static __always_inline void
+__libc_cap_map_del (void *p)
+{
+  assert (p != NULL);
+//  assert (__builtin_cheri_base_get (p) == (uint64_t) p);
+}
+
 /* No special alignment is needed for n <= __CAP_ALIGN_THRESHOLD
    allocations, i.e. __libc_cap_align (n) <= MALLOC_ALIGNMENT.  */
 #define __CAP_ALIGN_THRESHOLD 32759
@@ -51,7 +313,11 @@ __libc_cap_align (size_t n)
 static __always_inline void *
 __libc_cap_narrow (void *p, size_t n)
 {
-  return __builtin_cheri_bounds_set_exact (p, n);
+  assert (p != NULL);
+  uint64_t key = (uint64_t)(uintptr_t) p;
+  assert (ht_add (&__libc_cap_ht, key, p));
+  void *narrow = __builtin_cheri_bounds_set_exact (p, n);
+  return narrow;
 }
 
 /* Given a p with narrowed bound (output of __libc_cap_narrow) return
@@ -59,8 +325,31 @@ __libc_cap_narrow (void *p, size_t n)
 static __always_inline void *
 __libc_cap_widen (void *p)
 {
-  void *cap = __builtin_cheri_global_data_get ();
-  return __builtin_cheri_address_set (cap, p);
+  assert (__builtin_cheri_tag_get (p) && __builtin_cheri_offset_get (p) == 0);
+  uint64_t key = (uint64_t)(uintptr_t) p;
+  void *cap = ht_get (&__libc_cap_ht, key);
+  assert (cap == p);
+  return cap;
+}
+
+static __always_inline bool
+__libc_cap_reserve (void)
+{
+  return ht_reserve (&__libc_cap_ht);
+}
+
+static __always_inline void
+__libc_cap_unreserve (void)
+{
+  ht_unreserve (&__libc_cap_ht);
+}
+
+static __always_inline void
+__libc_cap_drop (void *p)
+{
+  assert (p != NULL);
+  uint64_t key = (uint64_t)(uintptr_t) p;
+  assert (ht_del (&__libc_cap_ht, key));
 }
 
 #endif
diff --git a/sysdeps/generic/libc-cap.h b/sysdeps/generic/libc-cap.h
index 85ff2a6b61..9d93d61c9e 100644
--- a/sysdeps/generic/libc-cap.h
+++ b/sysdeps/generic/libc-cap.h
@@ -26,9 +26,18 @@
 void __libc_cap_link_error (void);
 
 #define __libc_cap_fail(rtype) (__libc_cap_link_error (), (rtype) 0)
+#define __libc_cap_init() __libc_cap_fail (bool)
+#define __libc_cap_fork_lock() __libc_cap_fail (void)
+#define __libc_cap_fork_unlock_parent() __libc_cap_fail (void)
+#define __libc_cap_fork_unlock_child() __libc_cap_fail (void)
+#define __libc_cap_map_add(p) __libc_cap_fail (bool)
+#define __libc_cap_map_del(p) __libc_cap_fail (void)
 #define __libc_cap_roundup(n) __libc_cap_fail (size_t)
 #define __libc_cap_align(n) __libc_cap_fail (size_t)
 #define __libc_cap_narrow(p, n) __libc_cap_fail (void *)
 #define __libc_cap_widen(p) __libc_cap_fail (void *)
+#define __libc_cap_reserve(p) __libc_cap_fail (bool)
+#define __libc_cap_unreserve(p) __libc_cap_fail (void)
+#define __libc_cap_drop(p) __libc_cap_fail (void)
 
 #endif