This bug is not plausible on any Gnulib platform, but it could happen in glibc if the requested size is UINT_MAX. * lib/obstack.c (_obstack_newchunk): Fail if requested size is 0. * lib/obstack.in.h (obstack_grow0): Check for room <= len, not room <= len + 1 where len + 1 could overflow. --- ChangeLog | 9 +++++++++ lib/obstack.c | 19 ++++++++++++++++--- lib/obstack.in.h | 4 ++-- 3 files changed, 27 insertions(+), 5 deletions(-)
diff --git a/ChangeLog b/ChangeLog index adf06a2645..9ee3f6eea5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2025-05-05 Paul Eggert <egg...@cs.ucla.edu> + + obstack: fix unlikely buffer overrun in glibc + This bug is not plausible on any Gnulib platform, + but it could happen in glibc if the requested size is UINT_MAX. + * lib/obstack.c (_obstack_newchunk): Fail if requested size is 0. + * lib/obstack.in.h (obstack_grow0): Check for room <= len, + not room <= len + 1 where len + 1 could overflow. + 2025-05-05 Bruno Haible <br...@clisp.org> windows-cygpath: Make it work with the native Windows 'git' port. diff --git a/lib/obstack.c b/lib/obstack.c index 0f6c5f98d7..5839e04f2f 100644 --- a/lib/obstack.c +++ b/lib/obstack.c @@ -34,8 +34,19 @@ # include <alignof.h> # define __alignof__(type) alignof_type (type) #endif -#include <stdlib.h> + +#include <limits.h> #include <stdint.h> +#include <stdlib.h> + +/* Some work would need to be done to port this module + to unusual platforms where size_t fits in int. + For now, document the assumption that INT_MAX < SIZE_MAX, + and therefore size_t calculations are modulo SIZE_MAX + 1 + instead of having undefined behavior on overflow. */ +#if SIZE_MAX <= INT_MAX + #error "SIZE_MAX <= INT_MAX" +#endif #ifndef MAX # define MAX(a,b) ((a) > (b) ? (a) : (b)) @@ -154,6 +165,7 @@ _obstack_begin_1 (struct obstack *h, /* Allocate a new current chunk for the obstack *H on the assumption that LENGTH bytes need to be added to the current object, or a new object of length LENGTH allocated. + Fail if LENGTH <= 0, as this means obstack_grow0's length overflowed. Copies any partial object from the end of the old chunk to the beginning of the new one. */ @@ -174,8 +186,9 @@ _obstack_newchunk (struct obstack *h, _OBSTACK_SIZE_T length) if (new_size < h->chunk_size) new_size = h->chunk_size; - /* Allocate and initialize the new chunk. */ - if (obj_size <= sum1 && sum1 <= sum2) + /* Allocate and initialize the new chunk, + checking for overflow and for nonpositive LENGTH. */ + if (obj_size < sum1 && sum1 <= sum2) new_chunk = call_chunkfun (h, new_size); if (!new_chunk) (*obstack_alloc_failed_handler)(); diff --git a/lib/obstack.in.h b/lib/obstack.in.h index 5296cf5820..1d6ff923b4 100644 --- a/lib/obstack.in.h +++ b/lib/obstack.in.h @@ -348,7 +348,7 @@ extern int obstack_exit_failure; __extension__ \ ({ struct obstack *__o = (OBSTACK); \ _OBSTACK_SIZE_T __len = (length); \ - if (obstack_room (__o) < __len + 1) \ + if (obstack_room (__o) <= __len) \ _obstack_newchunk (__o, __len + 1); \ memcpy (__o->next_free, where, __len); \ __o->next_free += __len; \ @@ -484,7 +484,7 @@ extern int obstack_exit_failure; # define obstack_grow0(h, where, length) \ ((h)->temp.i = (length), \ - ((obstack_room (h) < (h)->temp.i + 1) \ + ((obstack_room (h) <= (h)->temp.i) \ ? (_obstack_newchunk ((h), (h)->temp.i + 1), 0) : 0), \ memcpy ((h)->next_free, where, (h)->temp.i), \ (h)->next_free += (h)->temp.i, \ -- 2.49.0