summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--malloc/malloc.c180
-rw-r--r--sysdeps/aarch64/morello/libc-cap.h66
-rw-r--r--sysdeps/generic/libc-cap.h34
3 files changed, 272 insertions, 8 deletions
diff --git a/malloc/malloc.c b/malloc/malloc.c
index 56d6116102..9d800aa916 100644
--- a/malloc/malloc.c
+++ b/malloc/malloc.c
@@ -244,6 +244,9 @@
/* For memory tagging. */
#include <libc-mtag.h>
+/* For CHERI capability narrowing. */
+#include <libc-cap.h>
+
#include <malloc/malloc-internal.h>
/* For SINGLE_THREAD_P. */
@@ -482,6 +485,49 @@ tag_at (void *ptr)
return ptr;
}
+/* CHERI capability narrowing support. */
+#ifdef __CHERI_PURE_CAPABILITY__
+static bool cap_narrowing_enabled = true;
+#else
+# define cap_narrowing_enabled 0
+#endif
+
+/* Round up size so capability bounds can be represented. */
+static __always_inline size_t
+cap_roundup (size_t n)
+{
+ if (cap_narrowing_enabled)
+ return __libc_cap_roundup (n);
+ return n;
+}
+
+/* Alignment such that capability bounds can be represented. */
+static __always_inline size_t
+cap_align (size_t n)
+{
+ if (cap_narrowing_enabled)
+ return __libc_cap_align (n);
+ return 1;
+}
+
+/* Narrow the bounds of p to [p, p+n) exactly unless p is NULL. */
+static __always_inline void *
+cap_narrow (void *p, size_t n)
+{
+ if (cap_narrowing_enabled && p != NULL)
+ return __libc_cap_narrow (p, n);
+ return p;
+}
+
+/* Widen back the bounds of a non-NULL p that was returned by cap_narrow. */
+static __always_inline void *
+cap_widen (void *p)
+{
+ if (cap_narrowing_enabled)
+ return __libc_cap_widen (p);
+ return p;
+}
+
#include <string.h>
/*
@@ -1292,6 +1338,18 @@ nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
_int_memalign: Returns untagged memory.
_mid_memalign: Returns tagged memory.
_int_realloc: Takes and returns tagged memory.
+
+ With CHERI capability narrowing enabled, public interfaces take and
+ return pointers with bounds that cannot access malloc internal chunk
+ headers. Narrowing CHERI capabilities in internal interfaces:
+
+ sysmalloc: Returns wide capability.
+ _int_malloc: Returns wide capability.
+ _int_free: Takes wide capability.
+ _int_memalign: Returns wide capability.
+ _int_memalign: Returns wide capability.
+ _mid_memalign: Returns narrow capability.
+ _int_realloc: Takes and returns wide capability.
*/
/* The chunk header is two SIZE_SZ elements, but this is used widely, so
@@ -3320,7 +3378,13 @@ __libc_malloc (size_t bytes)
if (!__malloc_initialized)
ptmalloc_init ();
+
+ size_t align = cap_align (bytes);
+ bytes = cap_roundup (bytes);
#if USE_TCACHE
+ _Static_assert (MAX_TCACHE_SIZE <= __CAP_ALIGN_THRESHOLD,
+ "tcache entries are already aligned for capability narrowing");
+
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes = checked_request2size (bytes);
if (tbytes == 0)
@@ -3338,16 +3402,22 @@ __libc_malloc (size_t bytes)
&& tcache->counts[tc_idx] > 0)
{
victim = tcache_get (tc_idx);
- return tag_new_usable (victim);
+ victim = tag_new_usable (victim);
+ victim = cap_narrow (victim, bytes);
+ return victim;
}
DIAG_POP_NEEDS_COMMENT;
#endif
+ if (align > MALLOC_ALIGNMENT)
+ return _mid_memalign (align, bytes, 0);
+
if (SINGLE_THREAD_P)
{
victim = tag_new_usable (_int_malloc (&main_arena, bytes));
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
&main_arena == arena_for_chunk (mem2chunk (victim)));
+ victim = cap_narrow (victim, bytes);
return victim;
}
@@ -3370,6 +3440,7 @@ __libc_malloc (size_t bytes)
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
+ victim = cap_narrow (victim, bytes);
return victim;
}
libc_hidden_def (__libc_malloc)
@@ -3383,6 +3454,8 @@ __libc_free (void *mem)
if (mem == 0) /* free(0) has no effect */
return;
+ mem = cap_widen (mem);
+
/* Quickly check that the freed pointer matches the tag for the memory.
This gives a useful double-free detection. */
if (__glibc_unlikely (mtag_enabled))
@@ -3444,6 +3517,8 @@ __libc_realloc (void *oldmem, size_t bytes)
if (oldmem == 0)
return __libc_malloc (bytes);
+ oldmem = cap_widen (oldmem);
+
/* Perform a quick check to ensure that the pointer's tag matches the
memory's tag. */
if (__glibc_unlikely (mtag_enabled))
@@ -3470,6 +3545,8 @@ __libc_realloc (void *oldmem, size_t bytes)
|| __builtin_expect (misaligned_chunk (oldp), 0)))
malloc_printerr ("realloc(): invalid pointer");
+ size_t align = cap_align (bytes);
+ bytes = cap_roundup (bytes);
nb = checked_request2size (bytes);
if (nb == 0)
{
@@ -3481,6 +3558,14 @@ __libc_realloc (void *oldmem, size_t bytes)
{
void *newmem;
+#ifdef __CHERI_PURE_CAPABILITY__
+ size_t pagesize = GLRO (dl_pagesize);
+
+ // TODO: with pcuabi kernel more cases can be handled by mremap
+ /* Don't try in-place realloc if oldmem is unaligned. */
+ if (align <= pagesize && ((size_t) oldmem & (align - 1)) == 0)
+ {
+#endif
#if HAVE_MREMAP
newp = mremap_chunk (oldp, nb);
if (newp)
@@ -3491,30 +3576,66 @@ __libc_realloc (void *oldmem, size_t bytes)
reused. There's a performance hit for both us and the
caller for doing this, so we might want to
reconsider. */
- return tag_new_usable (newmem);
+ newmem = tag_new_usable (newmem);
+ newmem = cap_narrow (newmem, bytes);
+ return newmem;
}
#endif
+#ifdef __CHERI_PURE_CAPABILITY__
+ }
+ size_t sz = oldsize - CHUNK_HDR_SZ;
+ /* In-place realloc if the size shrinks, but if it shrinks a lot
+ then don't waste the rest of the mapping. */
+ if (sz >= bytes && sz / 4 < bytes
+ && ((size_t) oldmem & (align - 1)) == 0)
+ return cap_narrow (oldmem, bytes);
+#else
/* Note the extra SIZE_SZ overhead. */
if (oldsize - SIZE_SZ >= nb)
return oldmem; /* do nothing */
+#endif
/* Must alloc, copy, free. */
+#ifdef __CHERI_PURE_CAPABILITY__
+ if (align > MALLOC_ALIGNMENT)
+ newmem = _mid_memalign (align, bytes, 0);
+ else
+#endif
newmem = __libc_malloc (bytes);
if (newmem == 0)
return 0; /* propagate failure */
+#ifdef __CHERI_PURE_CAPABILITY__
+ memcpy (newmem, oldmem, sz < bytes ? sz : bytes);
+#else
memcpy (newmem, oldmem, oldsize - CHUNK_HDR_SZ);
+#endif
munmap_chunk (oldp);
return newmem;
}
+ /* Large alignment is required and it's not possible to realloc in place. */
+ if (align > MALLOC_ALIGNMENT
+ && (oldsize < nb || ((size_t) oldmem & (align - 1)) != 0))
+ {
+ /* Use memalign, copy, free. */
+ void *newmem = _mid_memalign (align, bytes, 0);
+ if (newmem == NULL)
+ return newmem;
+ size_t sz = oldsize - CHUNK_HDR_SZ;
+ memcpy (newmem, oldmem, sz < bytes ? sz : bytes);
+ (void) tag_region (oldmem, sz);
+ _int_free (ar_ptr, oldp, 0);
+ return newmem;
+ }
+
if (SINGLE_THREAD_P)
{
newp = _int_realloc (ar_ptr, oldp, oldsize, nb);
assert (!newp || chunk_is_mmapped (mem2chunk (newp)) ||
ar_ptr == arena_for_chunk (mem2chunk (newp)));
- return newp;
+ return cap_narrow (newp, bytes);
}
__libc_lock_lock (ar_ptr->mutex);
@@ -3538,6 +3659,8 @@ __libc_realloc (void *oldmem, size_t bytes)
_int_free (ar_ptr, oldp, 0);
}
}
+ else
+ newp = cap_narrow (newp, bytes);
return newp;
}
@@ -3550,6 +3673,11 @@ __libc_memalign (size_t alignment, size_t bytes)
ptmalloc_init ();
void *address = RETURN_ADDRESS (0);
+
+ size_t align = cap_align (bytes);
+ bytes = cap_roundup (bytes);
+ if (align > alignment)
+ alignment = align;
return _mid_memalign (alignment, bytes, address);
}
@@ -3590,7 +3718,9 @@ _mid_memalign (size_t alignment, size_t bytes, void *address)
p = _int_memalign (&main_arena, alignment, bytes);
assert (!p || chunk_is_mmapped (mem2chunk (p)) ||
&main_arena == arena_for_chunk (mem2chunk (p)));
- return tag_new_usable (p);
+ p = tag_new_usable (p);
+ p = cap_narrow (p, bytes);
+ return p;
}
arena_get (ar_ptr, bytes + alignment + MINSIZE);
@@ -3608,7 +3738,9 @@ _mid_memalign (size_t alignment, size_t bytes, void *address)
assert (!p || chunk_is_mmapped (mem2chunk (p)) ||
ar_ptr == arena_for_chunk (mem2chunk (p)));
- return tag_new_usable (p);
+ p = tag_new_usable (p);
+ p = cap_narrow (p, bytes);
+ return p;
}
/* For ISO C11. */
weak_alias (__libc_memalign, aligned_alloc)
@@ -3622,6 +3754,10 @@ __libc_valloc (size_t bytes)
void *address = RETURN_ADDRESS (0);
size_t pagesize = GLRO (dl_pagesize);
+ size_t align = cap_align (bytes);
+ bytes = cap_roundup (bytes);
+ if (align > pagesize)
+ pagesize = align;
return _mid_memalign (pagesize, bytes, address);
}
@@ -3644,6 +3780,10 @@ __libc_pvalloc (size_t bytes)
}
rounded_bytes = rounded_bytes & -(pagesize - 1);
+ size_t align = cap_align (rounded_bytes);
+ rounded_bytes = cap_roundup (rounded_bytes);
+ if (align > pagesize)
+ pagesize = align;
return _mid_memalign (pagesize, rounded_bytes, address);
}
@@ -3666,10 +3806,23 @@ __libc_calloc (size_t n, size_t elem_size)
}
sz = bytes;
+ sz = cap_roundup (sz);
if (!__malloc_initialized)
ptmalloc_init ();
+ size_t align = cap_align (bytes);
+ if (align > MALLOC_ALIGNMENT)
+ {
+ mem = _mid_memalign (align, sz, 0);
+ if (mem == NULL)
+ return mem;
+ mchunkptr p = mem2chunk (cap_widen (mem));
+ if (chunk_is_mmapped (p) && __glibc_likely (perturb_byte == 0))
+ return mem;
+ return memset (mem, 0, sz);
+ }
+
MAYBE_INIT_TCACHE ();
if (SINGLE_THREAD_P)
@@ -3732,13 +3885,19 @@ __libc_calloc (size_t n, size_t elem_size)
regardless of MORECORE_CLEARS, so we zero the whole block while
doing so. */
if (__glibc_unlikely (mtag_enabled))
- return tag_new_zero_region (mem, memsize (p));
+ {
+ mem = tag_new_zero_region (mem, memsize (p));
+ mem = cap_narrow (mem, sz);
+ return mem;
+ }
INTERNAL_SIZE_T csz = chunksize (p);
/* Two optional cases in which clearing not necessary */
if (chunk_is_mmapped (p))
{
+ mem = cap_narrow (mem, sz);
+
if (__builtin_expect (perturb_byte, 0))
return memset (mem, 0, sz);
@@ -3762,8 +3921,7 @@ __libc_calloc (size_t n, size_t elem_size)
assert (nclears >= 3);
if (nclears > 9)
- return memset (d, 0, clearsize);
-
+ mem = memset (mem, 0, clearsize);
else
{
*(d + 0) = 0;
@@ -3786,6 +3944,7 @@ __libc_calloc (size_t n, size_t elem_size)
}
}
+ mem = cap_narrow (mem, sz);
return mem;
}
#endif /* IS_IN (libc) */
@@ -5167,6 +5326,7 @@ __malloc_usable_size (void *m)
{
if (m == NULL)
return 0;
+ m = cap_widen (m);
return musable (m);
}
#endif
@@ -5713,6 +5873,10 @@ __posix_memalign (void **memptr, size_t alignment, size_t size)
|| alignment == 0)
return EINVAL;
+ size_t align = cap_align (size);
+ size = cap_roundup (size);
+ if (align > alignment)
+ alignment = align;
void *address = RETURN_ADDRESS (0);
mem = _mid_memalign (alignment, size, address);
diff --git a/sysdeps/aarch64/morello/libc-cap.h b/sysdeps/aarch64/morello/libc-cap.h
new file mode 100644
index 0000000000..d772e36dee
--- /dev/null
+++ b/sysdeps/aarch64/morello/libc-cap.h
@@ -0,0 +1,66 @@
+/* Support for allocations with narrow capability bounds. Morello version.
+ Copyright (C) 2022 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _AARCH64_MORELLO_LIBC_CAP_H
+#define _AARCH64_MORELLO_LIBC_CAP_H 1
+
+/* No special alignment is needed for n <= __CAP_ALIGN_THRESHOLD
+ allocations, i.e. __libc_cap_align (n) <= MALLOC_ALIGNMENT. */
+#define __CAP_ALIGN_THRESHOLD 32759
+
+/* Round up the allocation size so the allocated pointer bounds
+ can be represented. Note: this may be called before any
+ checks on n, so it should work with all possible n values. */
+static __always_inline size_t
+__libc_cap_roundup (size_t n)
+{
+ if (__glibc_unlikely (n > PTRDIFF_MAX))
+ return n;
+ return __builtin_cheri_round_representable_length (n);
+}
+
+/* Returns the alignment requirement for an allocation size n such
+ that the allocated pointer bounds can be represented. Note:
+ same n should be used in __libc_cap_roundup and __libc_cap_align
+ and the results used together for __libc_cap_narrow. */
+static __always_inline size_t
+__libc_cap_align (size_t n)
+{
+ return -__builtin_cheri_representable_alignment_mask (n);
+}
+
+/* Narrow the bounds of p to be [p, p+n). Note: the original bounds
+ of p includes the entire mapping where p points to and that bound
+ should be recoverable by __libc_cap_widen. Called with p aligned
+ and n sized according to __libc_cap_align and __libc_cap_roundup. */
+static __always_inline void *
+__libc_cap_narrow (void *p, size_t n)
+{
+ return __builtin_cheri_bounds_set_exact (p, n);
+}
+
+/* Given a p with narrowed bound (output of __libc_cap_narrow) return
+ the same pointer with the internal wide bound. */
+static __always_inline void *
+__libc_cap_widen (void *p)
+{
+ void *cap = __builtin_cheri_global_data_get ();
+ return __builtin_cheri_address_set (cap, p);
+}
+
+#endif
diff --git a/sysdeps/generic/libc-cap.h b/sysdeps/generic/libc-cap.h
new file mode 100644
index 0000000000..85ff2a6b61
--- /dev/null
+++ b/sysdeps/generic/libc-cap.h
@@ -0,0 +1,34 @@
+/* Support for allocations with narrow capability bounds. Generic version.
+ Copyright (C) 2022 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#ifndef _LIBC_CAP_H
+#define _LIBC_CAP_H 1
+
+/* Larger allocations may need special alignment via cap_align. */
+#define __CAP_ALIGN_THRESHOLD SIZE_MAX
+
+/* Error if the identifiers bellow are used. */
+void __libc_cap_link_error (void);
+
+#define __libc_cap_fail(rtype) (__libc_cap_link_error (), (rtype) 0)
+#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 *)
+
+#endif