* lib/obstack.c: Include stddef.h, for offsetof.
(align_size_up): New static function.
(_obstack_begin_worker, _obstack_newchunk):
Ensure that the chunk limit is aligned, too.
(_obstack_begin_worker): Don’t assume that 4096 is large enough,
as the alignment could be larger.
* lib/obstack.in.h (obstack_finish): Remove code that could have
set next_free outside the limits of the allocated storage, which
had undefined behavior.  This code is no longer needed now that
chunk_limit has proper alignment.
---
 ChangeLog        | 12 +++++++++++
 lib/obstack.c    | 56 +++++++++++++++++++++++++++++++++++++++---------
 lib/obstack.in.h |  6 ------
 3 files changed, 58 insertions(+), 16 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index c0691ce564..8967f3d54f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2025-05-05  Paul Eggert  <egg...@cs.ucla.edu>
 
+       obstack: fix undefined behavior if big alignment
+       * lib/obstack.c: Include stddef.h, for offsetof.
+       (align_size_up): New static function.
+       (_obstack_begin_worker, _obstack_newchunk):
+       Ensure that the chunk limit is aligned, too.
+       (_obstack_begin_worker): Don’t assume that 4096 is large enough,
+       as the alignment could be larger.
+       * lib/obstack.in.h (obstack_finish): Remove code that could have
+       set next_free outside the limits of the allocated storage, which
+       had undefined behavior.  This code is no longer needed now that
+       chunk_limit has proper alignment.
+
        libc-config, obstack: Oracle __extension__ support
        * lib/cdefs.h, lib/obstack.in.h (__extension__):
        Oracle Developer Studio 12.6 also supports __extension__.
diff --git a/lib/obstack.c b/lib/obstack.c
index 9877207cb1..b319c8f7d3 100644
--- a/lib/obstack.c
+++ b/lib/obstack.c
@@ -36,6 +36,7 @@
 #endif
 
 #include <limits.h>
+#include <stddef.h>
 #include <stdint.h>
 #include <stdlib.h>
 
@@ -48,6 +49,14 @@
  #error "SIZE_MAX <= INT_MAX"
 #endif
 
+/* Return the least multiple of MASK + 1 that is not less than SIZE.
+   MASK + 1 must be a power of 2.  On overflow, return zero.  */
+static size_t
+align_size_up (size_t mask, size_t size)
+{
+  return size + (mask & -size);
+}
+
 #ifndef MAX
 # define MAX(a,b) ((a) > (b) ? (a) : (b))
 #endif
@@ -89,7 +98,7 @@ call_freefun (struct obstack *h, void *old_chunk)
 }
 
 
-/* Initialize an obstack H for use.  Specify chunk size SIZE (0 means default).
+/* Initialize an obstack H for use, with given CHUNK_SIZE (0 means default).
    Objects start on multiples of ALIGNMENT (0 means use default).
 
    Return nonzero if successful, calls obstack_alloc_failed_handler if
@@ -97,15 +106,32 @@ call_freefun (struct obstack *h, void *old_chunk)
 
 static int
 _obstack_begin_worker (struct obstack *h,
-                       _OBSTACK_INDEX_T size, _OBSTACK_INDEX_T alignment)
+                       _OBSTACK_INDEX_T chunk_size, _OBSTACK_INDEX_T alignment)
 {
   struct _obstack_chunk *chunk; /* points to new chunk */
 
   if (alignment == 0)
     alignment = DEFAULT_ALIGNMENT;
-  if (size == 0)
-    /* Default size is what GNU malloc can fit in a 4096-byte block.  */
+
+  /* The minimum size to request from the allocator, such that the
+     result is guaranteed to have enough room to start with the struct
+     _obstack_chunk sans contents, followed by minimal padding, up to
+     but possibly not including the start of an aligned object.
+     This value is zero if no size is large enough.  */
+  size_t aligned_prefix_size
+    = align_size_up (alignment - 1,
+                     (alignment - 1
+                      + offsetof (struct _obstack_chunk, contents)));
+
+  size_t size = chunk_size;
+  if (!aligned_prefix_size)
+    size = 0;
+  else if (size < aligned_prefix_size)
     {
+      size = aligned_prefix_size;
+
+      /* For speed in the typical case, allocate at least a "good" size.  */
+
       /* 12 is sizeof (mhead) and 4 is EXTRA from GNU malloc.
          Use the values for range checking, because if range checking is off,
          the extra bytes won't be missed terribly, but if range checking is on
@@ -117,18 +143,23 @@ _obstack_begin_worker (struct obstack *h,
       int extra = ((((12 + DEFAULT_ROUNDING - 1) & ~(DEFAULT_ROUNDING - 1))
                     + 4 + DEFAULT_ROUNDING - 1)
                    & ~(DEFAULT_ROUNDING - 1));
-      size = 4096 - extra;
+      int good_size = 4096 - extra;
+      if (0 <= good_size && size < good_size)
+        size = good_size;
     }
 
   h->chunk_size = size;
   h->alignment_mask = alignment - 1;
 
-  chunk = h->chunk = call_chunkfun (h, h->chunk_size);
+  chunk = h->chunk = (0 < h->chunk_size && h->chunk_size == size
+                      ? call_chunkfun (h, size) : NULL);
   if (!chunk)
     (*obstack_alloc_failed_handler) ();
   h->next_free = h->object_base = __PTR_ALIGN ((char *) chunk, chunk->contents,
                                                alignment - 1);
-  h->chunk_limit = chunk->limit = (char *) chunk + h->chunk_size;
+  h->chunk_limit = chunk->limit =
+    __PTR_ALIGN ((char *) chunk, (char *) chunk + size - (alignment - 1),
+                 alignment - 1);
   chunk->prev = NULL;
   /* The initial chunk now contains no empty object.  */
   h->maybe_empty_object = 0;
@@ -179,7 +210,9 @@ _obstack_newchunk (struct obstack *h, _OBSTACK_INDEX_T 
length)
 
   /* Compute size for new chunk.  */
   size_t sum1 = obj_size + length;
-  size_t sum2 = sum1 + h->alignment_mask;
+  size_t sum1a = align_size_up (h->alignment_mask, sum1);
+  size_t sum2 = (offsetof (struct _obstack_chunk, contents)
+                 + h->alignment_mask + sum1a);
   size_t new_size = sum2 + (obj_size >> 3) + 100;
   if (new_size < sum2)
     new_size = sum2;
@@ -188,13 +221,16 @@ _obstack_newchunk (struct obstack *h, _OBSTACK_INDEX_T 
length)
 
   /* Allocate and initialize the new chunk,
      checking for overflow and for nonpositive LENGTH.  */
-  if (obj_size < sum1 && sum1 <= sum2)
+  if (obj_size < sum1 && sum1a && sum1a < sum2)
     new_chunk = call_chunkfun (h, new_size);
   if (!new_chunk)
     (*obstack_alloc_failed_handler)();
   h->chunk = new_chunk;
   new_chunk->prev = old_chunk;
-  new_chunk->limit = h->chunk_limit = (char *) new_chunk + new_size;
+  new_chunk->limit = h->chunk_limit =
+    __PTR_ALIGN ((char *) new_chunk,
+                 (char *) new_chunk + new_size - h->alignment_mask,
+                 h->alignment_mask);
 
   /* Compute an aligned object_base in the new chunk */
   object_base =
diff --git a/lib/obstack.in.h b/lib/obstack.in.h
index 5e0db5f88e..232414b6b4 100644
--- a/lib/obstack.in.h
+++ b/lib/obstack.in.h
@@ -463,9 +463,6 @@ extern int obstack_exit_failure;
        __o1->next_free                                                       \
          = __PTR_ALIGN (__o1->object_base, __o1->next_free,                  \
                         __o1->alignment_mask);                               \
-       if ((size_t) (__o1->next_free - (char *) __o1->chunk)                 \
-           > (size_t) (__o1->chunk_limit - (char *) __o1->chunk))            \
-         __o1->next_free = __o1->chunk_limit;                                \
        __o1->object_base = __o1->next_free;                                  \
        __value; })
 
@@ -569,9 +566,6 @@ extern int obstack_exit_failure;
    (h)->next_free                                                            \
      = __PTR_ALIGN ((h)->object_base, (h)->next_free,                        \
                     (h)->alignment_mask),                                    \
-   (((size_t) ((h)->next_free - (char *) (h)->chunk)                         \
-     > (size_t) ((h)->chunk_limit - (char *) (h)->chunk))                    \
-   ? ((h)->next_free = (h)->chunk_limit) : 0),                               \
    (h)->object_base = (h)->next_free,                                        \
    (h)->temp.tempptr)
 
-- 
2.49.0


Reply via email to