I was impressed by the fact that CHERI detected the multithread-safety
bug of gnulib's use of rand() in the test suite.

Now I'd like to try CHERI on packages like gettext, and see whether
it finds bugs that neither valgrind nor the gcc bounds-checking options
can detect.

For this purpose, it is useful if all functions that allocate memory
blocks return bounds for these memory blocks that are as tight as possible.
malloc(), realloc(), reallocarray(), alloca() already do so.
(To convince yourself, use a C program that makes use of these functions,
and print the return values from within gdb. gdb prints pointers with bounds.)

This set of patches handles most memory allocators that we have in gnulib.

The API is documented in
<https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-947.pdf>.


2023-11-11  Bruno Haible  <br...@clisp.org>

        malloca: Take advantage of CHERI bounds-checking.
        * lib/malloca.h: Include <cheri.h>.
        (malloca) [CHERI]: In the stack-allocation case, return a pointer with
        a tight lower bound and a tight upper bound.
        * lib/malloca.c: Include <cheri.h>.
        (small_t) [CHERI]: Define as uintptr_t.
        (mmalloca) [CHERI]: Return a pointer with a tight upper bound.
        (freea) [CHERI]: Update.

2023-11-11  Bruno Haible  <br...@clisp.org>

        safe-alloc: Take advantage of CHERI bounds-checking.
        * lib/safe-alloc.h: Include <cheri.h>.
        (safe_alloc_realloc_n): When count or size is 0, return a pointer whose
        bounds are of size 0, not 1.

2023-11-11  Bruno Haible  <br...@clisp.org>

        ialloc: Take advantage of CHERI bounds-checking.
        * lib/ialloc.h: Include <cheri.h>.
        (irealloc): When s is 0, return a pointer whose bounds are of size 0,
        not 1.
        (ireallocarray): When n or s is 0, return a pointer whose bounds are of
        size 0, not 1.

2023-11-11  Bruno Haible  <br...@clisp.org>

        eealloc: Take advantage of CHERI bounds-checking.
        * lib/eealloc.h: Include <cheri.h>.
        (eemalloc): When n is 0, return a pointer whose bounds are of size 0,
        not 1.
        (eerealloc): Likewise.

2023-11-11  Bruno Haible  <br...@clisp.org>

        alignalloc: Take advantage of CHERI bounds-checking.
        * lib/alignalloc.h: Include <cheri.h>.
        (alignalloc): When size is 0, return a pointer whose bounds are of
        size 0, not 1.

>From dba81b1764b575d119fd2ee86f386d461e83be76 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 11 Nov 2023 19:28:26 +0100
Subject: [PATCH 1/5] alignalloc: Take advantage of CHERI bounds-checking.

* lib/alignalloc.h: Include <cheri.h>.
(alignalloc): When size is 0, return a pointer whose bounds are of
size 0, not 1.
---
 ChangeLog        | 7 +++++++
 lib/alignalloc.h | 7 +++++++
 2 files changed, 14 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index 3ed337f691..b0f6f9f404 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2023-11-11  Bruno Haible  <br...@clisp.org>
+
+	alignalloc: Take advantage of CHERI bounds-checking.
+	* lib/alignalloc.h: Include <cheri.h>.
+	(alignalloc): When size is 0, return a pointer whose bounds are of
+	size 0, not 1.
+
 2023-11-11  Bruno Haible  <br...@clisp.org>
 
 	rawmemchr tests: Add test case for last commit.
diff --git a/lib/alignalloc.h b/lib/alignalloc.h
index 6a01aacb4f..cb40b344e8 100644
--- a/lib/alignalloc.h
+++ b/lib/alignalloc.h
@@ -29,6 +29,9 @@
 #include <errno.h>
 #include <stdlib.h>
 #include "idx.h"
+#if defined __CHERI__
+# include <cheri.h>
+#endif
 
 _GL_INLINE_HEADER_BEGIN
 #ifndef ALIGNALLOC_INLINE
@@ -93,6 +96,10 @@ alignalloc (idx_t alignment, idx_t size)
   if (alignment < sizeof (void *))
     alignment = sizeof (void *);
   errno = posix_memalign (&ptr, alignment, size | !size);
+#  if defined __CHERI__
+  if (ptr != NULL)
+    ptr = cheri_bounds_set (ptr, size);
+#  endif
   return ptr;
 # endif
 }
-- 
2.34.1

>From 4daae72c47a59e20f5312c1febb84e4c187e6acc Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 11 Nov 2023 19:31:56 +0100
Subject: [PATCH 2/5] eealloc: Take advantage of CHERI bounds-checking.

* lib/eealloc.h: Include <cheri.h>.
(eemalloc): When n is 0, return a pointer whose bounds are of size 0,
not 1.
(eerealloc): Likewise.
---
 ChangeLog     |  8 ++++++++
 lib/eealloc.h | 23 +++++++++++++++++++----
 2 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b0f6f9f404..60aea347a3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2023-11-11  Bruno Haible  <br...@clisp.org>
+
+	eealloc: Take advantage of CHERI bounds-checking.
+	* lib/eealloc.h: Include <cheri.h>.
+	(eemalloc): When n is 0, return a pointer whose bounds are of size 0,
+	not 1.
+	(eerealloc): Likewise.
+
 2023-11-11  Bruno Haible  <br...@clisp.org>
 
 	alignalloc: Take advantage of CHERI bounds-checking.
diff --git a/lib/eealloc.h b/lib/eealloc.h
index 6666f172c6..bae3915146 100644
--- a/lib/eealloc.h
+++ b/lib/eealloc.h
@@ -36,6 +36,9 @@
 #endif
 
 #include <stdlib.h>
+#if defined __CHERI__
+# include <cheri.h>
+#endif
 
 _GL_INLINE_HEADER_BEGIN
 #ifndef EEALLOC_INLINE
@@ -52,9 +55,15 @@ EEALLOC_INLINE void *
 eemalloc (size_t n)
 {
   /* If n is zero, allocate a 1-byte block.  */
+  size_t nx = n;
   if (n == 0)
-    n = 1;
-  return malloc (n);
+    nx = 1;
+  void *ptr = malloc (nx);
+# if defined __CHERI__
+  if (ptr != NULL)
+    ptr = cheri_bounds_set (ptr, n);
+# endif
+  return ptr;
 }
 #endif
 
@@ -67,9 +76,15 @@ EEALLOC_INLINE void *
 eerealloc (void *p, size_t n)
 {
   /* If n is zero, allocate or keep a 1-byte block.  */
+  size_t nx = n;
   if (n == 0)
-    n = 1;
-  return realloc (p, n);
+    nx = 1;
+  void *ptr = realloc (p, nx);
+# if defined __CHERI__
+  if (ptr != NULL)
+    ptr = cheri_bounds_set (ptr, n);
+# endif
+  return ptr;
 }
 #endif
 
-- 
2.34.1

>From aa4ce6623ca357c592cf602b197900207ee38062 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 11 Nov 2023 19:34:26 +0100
Subject: [PATCH 3/5] ialloc: Take advantage of CHERI bounds-checking.

* lib/ialloc.h: Include <cheri.h>.
(irealloc): When s is 0, return a pointer whose bounds are of size 0,
not 1.
(ireallocarray): When n or s is 0, return a pointer whose bounds are of
size 0, not 1.
---
 ChangeLog    |  9 +++++++++
 lib/ialloc.h | 43 +++++++++++++++++++++++++++++++++----------
 2 files changed, 42 insertions(+), 10 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 60aea347a3..c166fae715 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2023-11-11  Bruno Haible  <br...@clisp.org>
+
+	ialloc: Take advantage of CHERI bounds-checking.
+	* lib/ialloc.h: Include <cheri.h>.
+	(irealloc): When s is 0, return a pointer whose bounds are of size 0,
+	not 1.
+	(ireallocarray): When n or s is 0, return a pointer whose bounds are of
+	size 0, not 1.
+
 2023-11-11  Bruno Haible  <br...@clisp.org>
 
 	eealloc: Take advantage of CHERI bounds-checking.
diff --git a/lib/ialloc.h b/lib/ialloc.h
index 22f57a47d8..527b1f48be 100644
--- a/lib/ialloc.h
+++ b/lib/ialloc.h
@@ -29,6 +29,9 @@
 #include <errno.h>
 #include <stdint.h>
 #include <stdlib.h>
+#if defined __CHERI__
+# include <cheri.h>
+#endif
 
 _GL_INLINE_HEADER_BEGIN
 #ifndef IALLOC_INLINE
@@ -65,9 +68,19 @@ IALLOC_INLINE
 void *
 irealloc (void *p, idx_t s)
 {
-  /* Work around GNU realloc glitch by treating a zero size as if it
-     were 1, so that returning NULL is equivalent to failing.  */
-  return s <= SIZE_MAX ? realloc (p, s | !s) : _gl_alloc_nomem ();
+  if (s <= SIZE_MAX)
+    {
+      /* Work around GNU realloc glitch by treating a zero size as if it
+         were 1, so that returning NULL is equivalent to failing.  */
+      p = realloc (p, s | !s);
+#if defined __CHERI__
+      if (p != NULL)
+        p = cheri_bounds_set (p, s);
+#endif
+      return p;
+    }
+  else
+    return _gl_alloc_nomem ();
 }
 
 /* icalloc (num, size) is like calloc (num, size).
@@ -99,13 +112,23 @@ icalloc (idx_t n, idx_t s)
 IALLOC_INLINE void *
 ireallocarray (void *p, idx_t n, idx_t s)
 {
-  /* Work around GNU reallocarray glitch by treating a zero size as if
-     it were 1, so that returning NULL is equivalent to failing.  */
-  if (n == 0 || s == 0)
-    n = s = 1;
-  return (n <= SIZE_MAX && s <= SIZE_MAX
-          ? reallocarray (p, n, s)
-          : _gl_alloc_nomem ());
+  if (n <= SIZE_MAX && s <= SIZE_MAX)
+    {
+      /* Work around GNU reallocarray glitch by treating a zero size as if
+         it were 1, so that returning NULL is equivalent to failing.  */
+      size_t nx = n;
+      size_t sx = s;
+      if (n == 0 || s == 0)
+        nx = sx = 1;
+      p = reallocarray (p, nx, sx);
+#if defined __CHERI__
+      if (p != NULL && (n == 0 || s == 0))
+        p = cheri_bounds_set (p, 0);
+#endif
+      return p;
+    }
+  else
+    return _gl_alloc_nomem ();
 }
 
 #ifdef __cplusplus
-- 
2.34.1

>From 2d9d3ddae5dde383bac8f133f0551ce511f05f2d Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 11 Nov 2023 19:36:36 +0100
Subject: [PATCH 4/5] safe-alloc: Take advantage of CHERI bounds-checking.

* lib/safe-alloc.h: Include <cheri.h>.
(safe_alloc_realloc_n): When count or size is 0, return a pointer whose
bounds are of size 0, not 1.
---
 ChangeLog        |  7 +++++++
 lib/safe-alloc.h | 14 ++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index c166fae715..1fd39a4a44 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2023-11-11  Bruno Haible  <br...@clisp.org>
+
+	safe-alloc: Take advantage of CHERI bounds-checking.
+	* lib/safe-alloc.h: Include <cheri.h>.
+	(safe_alloc_realloc_n): When count or size is 0, return a pointer whose
+	bounds are of size 0, not 1.
+
 2023-11-11  Bruno Haible  <br...@clisp.org>
 
 	ialloc: Take advantage of CHERI bounds-checking.
diff --git a/lib/safe-alloc.h b/lib/safe-alloc.h
index 46079d5c69..27049d3836 100644
--- a/lib/safe-alloc.h
+++ b/lib/safe-alloc.h
@@ -27,6 +27,9 @@
 #endif
 
 #include <stdlib.h>
+#if defined __CHERI__
+# include <cheri.h>
+#endif
 
 _GL_INLINE_HEADER_BEGIN
 #ifndef SAFE_ALLOC_INLINE
@@ -37,9 +40,16 @@ _GL_INLINE_HEADER_BEGIN
 SAFE_ALLOC_INLINE void *
 safe_alloc_realloc_n (void *ptr, size_t count, size_t size)
 {
+  size_t countx = count;
+  size_t sizex = size;
   if (count == 0 || size == 0)
-    count = size = 1;
-  return reallocarray (ptr, count, size);
+    countx = sizex = 1;
+  ptr = reallocarray (ptr, countx, sizex);
+#if defined __CHERI__
+  if (ptr != NULL && (count == 0 || size == 0))
+    ptr = cheri_bounds_set (ptr, 0);
+#endif
+  return ptr;
 }
 _GL_ATTRIBUTE_NODISCARD SAFE_ALLOC_INLINE int
 safe_alloc_check (void *ptr)
-- 
2.34.1

From 5c8fc31ab5db3372739a7ae9bf579cef946408ad Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sat, 11 Nov 2023 20:04:02 +0100
Subject: [PATCH 5/5] malloca: Take advantage of CHERI bounds-checking.

* lib/malloca.h: Include <cheri.h>.
(malloca) [CHERI]: In the stack-allocation case, return a pointer with
a tight lower bound and a tight upper bound.
* lib/malloca.c: Include <cheri.h>.
(small_t) [CHERI]: Define as uintptr_t.
(mmalloca) [CHERI]: Return a pointer with a tight upper bound.
(freea) [CHERI]: Update.
---
 ChangeLog     | 11 +++++++++++
 lib/malloca.c | 28 ++++++++++++++++++++++++----
 lib/malloca.h | 27 +++++++++++++++++++++------
 3 files changed, 56 insertions(+), 10 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 1fd39a4a44..af8ead5fc2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2023-11-11  Bruno Haible  <br...@clisp.org>
+
+	malloca: Take advantage of CHERI bounds-checking.
+	* lib/malloca.h: Include <cheri.h>.
+	(malloca) [CHERI]: In the stack-allocation case, return a pointer with
+	a tight lower bound and a tight upper bound.
+	* lib/malloca.c: Include <cheri.h>.
+	(small_t) [CHERI]: Define as uintptr_t.
+	(mmalloca) [CHERI]: Return a pointer with a tight upper bound.
+	(freea) [CHERI]: Update.
+
 2023-11-11  Bruno Haible  <br...@clisp.org>
 
 	safe-alloc: Take advantage of CHERI bounds-checking.
diff --git a/lib/malloca.c b/lib/malloca.c
index 690ce2324b..f98fdf152d 100644
--- a/lib/malloca.c
+++ b/lib/malloca.c
@@ -22,6 +22,9 @@
 #include "malloca.h"
 
 #include <stdckdint.h>
+#if defined __CHERI__
+# include <cheri.h>
+#endif
 
 #include "idx.h"
 
@@ -36,10 +39,15 @@
        allocation.
      - NULL comes from a failed heap allocation.  */
 
+#if defined __CHERI__
+/* Type for holding the original malloc() result.  */
+typedef uintptr_t small_t;
+#else
 /* Type for holding very small pointer differences.  */
 typedef unsigned char small_t;
 /* Verify that it is wide enough.  */
 static_assert (2 * sa_alignment_max - 1 <= (small_t) -1);
+#endif
 
 void *
 mmalloca (size_t n)
@@ -56,20 +64,28 @@ mmalloca (size_t n)
 
       if (mem != NULL)
         {
-          uintptr_t umem = (uintptr_t)mem, umemplus;
+          uintptr_t umem = (uintptr_t) mem;
           /* The ckd_add avoids signed integer overflow on
              theoretical platforms where UINTPTR_MAX <= INT_MAX.  */
+          uintptr_t umemplus;
           ckd_add (&umemplus, umem, sizeof (small_t) + sa_alignment_max - 1);
           idx_t offset = (umemplus - umemplus % (2 * sa_alignment_max)
                           + sa_alignment_max - umem);
-          void *vp = mem + offset;
-          small_t *p = vp;
+          void *p = mem + offset;
           /* Here p >= mem + sizeof (small_t),
              and p <= mem + sizeof (small_t) + 2 * sa_alignment_max - 1
              hence p + n <= mem + nplus.
              So, the memory range [p, p+n) lies in the allocated memory range
              [mem, mem + nplus).  */
-          p[-1] = offset;
+          small_t *sp = p;
+# if defined __CHERI__
+          sp[-1] = umem;
+          p = (char *) cheri_bounds_set ((char *) p - sizeof (small_t),
+                                         sizeof (small_t) + n)
+              + sizeof (small_t);
+# else
+          sp[-1] = offset;
+# endif
           /* p ≡ sa_alignment_max mod 2*sa_alignment_max.  */
           return p;
         }
@@ -101,7 +117,11 @@ freea (void *p)
     {
       char *cp = p;
       small_t *sp = p;
+# if defined __CHERI__
+      void *mem = sp[-1];
+# else
       void *mem = cp - sp[-1];
+# endif
       free (mem);
     }
 }
diff --git a/lib/malloca.h b/lib/malloca.h
index f68ddfe010..120f406880 100644
--- a/lib/malloca.h
+++ b/lib/malloca.h
@@ -28,6 +28,9 @@
 #include <stddef.h>
 #include <stdlib.h>
 #include <stdint.h>
+#if defined __CHERI__
+# include <cheri.h>
+#endif
 
 #include "xalloc-oversized.h"
 
@@ -68,12 +71,24 @@ extern void freea (void *p);
    memory allocated on the stack, that must be freed using freea() before
    the function returns.  Upon failure, it returns NULL.  */
 #if HAVE_ALLOCA
-# define malloca(N) \
-  ((N) < 4032 - (2 * sa_alignment_max - 1)                                   \
-   ? (void *) (((uintptr_t) (char *) alloca ((N) + 2 * sa_alignment_max - 1) \
-                + (2 * sa_alignment_max - 1))                                \
-               & ~(uintptr_t)(2 * sa_alignment_max - 1))                     \
-   : mmalloca (N))
+# if defined __CHERI__
+#  define malloca(N) \
+    ((N) < 4032 - (2 * sa_alignment_max - 1)                                  \
+     ? cheri_bounds_set ((void *) (((uintptr_t)                               \
+                                     (char *)                                 \
+                                      alloca ((N) + 2 * sa_alignment_max - 1) \
+                                    + (2 * sa_alignment_max - 1))             \
+                                   & ~(uintptr_t)(2 * sa_alignment_max - 1)), \
+                         (N))                                                 \
+     : mmalloca (N))
+# else
+#  define malloca(N) \
+    ((N) < 4032 - (2 * sa_alignment_max - 1)                                   \
+     ? (void *) (((uintptr_t) (char *) alloca ((N) + 2 * sa_alignment_max - 1) \
+                  + (2 * sa_alignment_max - 1))                                \
+                 & ~(uintptr_t)(2 * sa_alignment_max - 1))                     \
+     : mmalloca (N))
+# endif
 #else
 # define malloca(N) \
   mmalloca (N)
-- 
2.34.1

Reply via email to