Problem discovered on cfarm240 (CheriBSD 15.0) when building
GNU Tar with ‘./configure 'CC=cc -march=morello -mabi=purecap'’.
The problem is that using cheri_bounds_set on a pointer derived
from posix_memalign, and later passing that pointer to ‘free’,
corrupts the heap and can cause a later ‘free (NULL)’ to crash.
Work around this CHERI bug via simplifying the workaround
for the size-zero allocation glitch.  The following changes take
effect only if __CHERI_PURE_CAPABILITY__ is defined.
* lib/alignalloc.h (alignalloc):
* lib/eealloc.h (eemalloc):
* lib/realloc.c (rpl_realloc):
Do not add 1 to size if the size is zero, and do not call
cheri_bounds_set on the result.  Do not include <cheri.h>.
---
 ChangeLog        | 17 +++++++++++++++++
 lib/alignalloc.h | 18 +++++++++---------
 lib/eealloc.h    | 36 ++++++++++++++++--------------------
 lib/realloc.c    |  9 ---------
 4 files changed, 42 insertions(+), 38 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index e0b841eb3d..18b17897db 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2026-08-20  Paul Eggert  <[email protected]>
+
+       alignalloc: work around CHERI ‘free’ bug
+       Problem discovered on cfarm240 (CheriBSD 15.0) when building
+       GNU Tar with ‘./configure 'CC=cc -march=morello -mabi=purecap'’.
+       The problem is that using cheri_bounds_set on a pointer derived
+       from posix_memalign, and later passing that pointer to ‘free’,
+       corrupts the heap and can cause a later ‘free (NULL)’ to crash.
+       Work around this CHERI bug via simplifying the workaround
+       for the size-zero allocation glitch.  The following changes take
+       effect only if __CHERI_PURE_CAPABILITY__ is defined.
+       * lib/alignalloc.h (alignalloc):
+       * lib/eealloc.h (eemalloc):
+       * lib/realloc.c (rpl_realloc):
+       Do not add 1 to size if the size is zero, and do not call
+       cheri_bounds_set on the result.  Do not include <cheri.h>.
+
 2026-08-20  Bruno Haible  <[email protected]>
 
        physmem: Clarify relation with getrlimit().
diff --git a/lib/alignalloc.h b/lib/alignalloc.h
index b0f3f607cd..f09cd26141 100644
--- a/lib/alignalloc.h
+++ b/lib/alignalloc.h
@@ -29,9 +29,6 @@
 #include <errno.h>
 #include <stdlib.h>
 #include "idx.h"
-#if defined __CHERI_PURE_CAPABILITY__
-# include <cheri.h>
-#endif
 
 _GL_INLINE_HEADER_BEGIN
 #ifndef ALIGNALLOC_INLINE
@@ -100,13 +97,16 @@ alignalloc (idx_t alignment, idx_t size)
   if (alignment < sizeof (void *))
     alignment = sizeof (void *);
   void *ptr = NULL;
-  /* Work around posix_memalign glitch by treating a 0 size as if it were 1,
-     so that returning NULL is equivalent to failing.  */
-  errno = posix_memalign (&ptr, alignment, size ? size : 1);
-#  if defined __CHERI_PURE_CAPABILITY__
-  if (ptr != NULL)
-    ptr = cheri_bounds_set (ptr, size);
+
+  /* Work around POSIX allocator glitch by treating a 0 size as if it were 1,
+     so that returning NULL is equivalent to failing.  Skip this workaround
+     on CHERI, though, as it yields non-NULL anyway and adding 1 would
+     cause it to yield a too-generous (dereferencable) pointer.  */
+#  ifndef __CHERI_PURE_CAPABILITY__
+  size |= !size;
 #  endif
+
+  errno = posix_memalign (&ptr, alignment, size);
   return ptr;
 # endif
 }
diff --git a/lib/eealloc.h b/lib/eealloc.h
index 62692f0688..b8cd86c4de 100644
--- a/lib/eealloc.h
+++ b/lib/eealloc.h
@@ -46,9 +46,6 @@
 #endif
 
 #include <stdlib.h>
-#if defined __CHERI_PURE_CAPABILITY__
-# include <cheri.h>
-#endif
 
 _GL_INLINE_HEADER_BEGIN
 #ifndef EEALLOC_INLINE
@@ -69,16 +66,15 @@ EEALLOC_INLINE void *eemalloc (size_t n)
 EEALLOC_INLINE void *
 eemalloc (size_t n)
 {
-  /* If n is zero, allocate a 1-byte block.  */
-  size_t nx = n;
-  if (n == 0)
-    nx = 1;
-  void *ptr = malloc (nx);
-# if defined __CHERI_PURE_CAPABILITY__
-  if (ptr != NULL)
-    ptr = cheri_bounds_set (ptr, n);
+  /* Work around POSIX allocator glitch by treating a 0 size as if it were 1,
+     so that returning NULL is equivalent to failing.  Skip this workaround
+     on CHERI, though, as it yields non-NULL anyway and adding 1 would
+     cause it to yield a too-generous (dereferencable) pointer.  */
+# ifndef __CHERI_PURE_CAPABILITY__
+  n |= !n;
 # endif
-  return ptr;
+
+  return malloc (n);
 }
 #endif
 
@@ -90,15 +86,15 @@ EEALLOC_INLINE void *eerealloc (void *p, size_t n)
 EEALLOC_INLINE void *
 eerealloc (void *p, size_t n)
 {
-  /* Work around realloc glitch by treating a 0 size as if it were 1,
-     to avoid undefined behavior in strict C23 platforms,
-     and so that returning NULL is equivalent to failing.  */
-  void *ptr = realloc (p, n ? n : 1);
-# if defined __CHERI_PURE_CAPABILITY__
-  if (ptr != NULL)
-    ptr = cheri_bounds_set (ptr, n);
+  /* Work around POSIX allocator glitch by treating a 0 size as if it were 1,
+     so that returning NULL is equivalent to failing.  Skip this workaround
+     on CHERI, though, as it yields non-NULL anyway and adding 1 would
+     cause it to yield a too-generous (dereferencable) pointer.  */
+# ifndef __CHERI_PURE_CAPABILITY__
+  n |= !n;
 # endif
-  return ptr;
+
+  return realloc (p, n);
 }
 #endif
 
diff --git a/lib/realloc.c b/lib/realloc.c
index 9b35c23510..0b42582c64 100644
--- a/lib/realloc.c
+++ b/lib/realloc.c
@@ -28,10 +28,6 @@
 #include <errno.h>
 #include <stdckdint.h>
 
-#ifdef __CHERI_PURE_CAPABILITY__
-# include <cheri.h>
-#endif
-
 #ifndef _GL_INLINE_RPL_REALLOC
 
 /* Change the size of an allocated block of memory P to N bytes,
@@ -99,11 +95,6 @@ rpl_realloc (void *p, size_t n)
     errno = ENOMEM;
 # endif
 
-# ifdef __CHERI_PURE_CAPABILITY__
-  if (result != NULL)
-    result = cheri_bounds_set (result, n);
-# endif
-
   return result;
 }
 
-- 
2.55.0


Reply via email to