diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 2b52babc98ee..d0c6f751e518 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -1825,6 +1825,10 @@ static zend_never_inline void *zend_mm_realloc_huge(zend_mm_heap *heap, void *pt #if ZEND_DEBUG real_size = size; size = ZEND_MM_ALIGNED_SIZE(size) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info)); + if (UNEXPECTED(size < real_size)) { + zend_mm_delayed_error_or_panic(heap->fallible, "Possible integer overflow in memory allocation (%zu + %zu)", ZEND_MM_ALIGNED_SIZE(real_size), ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + return NULL; + } #endif if (size > ZEND_MM_MAX_LARGE_SIZE) { #if ZEND_DEBUG @@ -1835,10 +1839,15 @@ static zend_never_inline void *zend_mm_realloc_huge(zend_mm_heap *heap, void *pt * We allocate them with 2MB size granularity, to avoid many * reallocations when they are extended by small pieces */ - new_size = ZEND_MM_ALIGNED_SIZE_EX(size, MAX(REAL_PAGE_SIZE, ZEND_MM_CHUNK_SIZE)); + size_t alignment = MAX(REAL_PAGE_SIZE, ZEND_MM_CHUNK_SIZE); #else - new_size = ZEND_MM_ALIGNED_SIZE_EX(size, REAL_PAGE_SIZE); + size_t alignment = REAL_PAGE_SIZE; #endif + new_size = ZEND_MM_ALIGNED_SIZE_EX(size, alignment); + if (UNEXPECTED(new_size < size)) { + zend_mm_delayed_error_or_panic(heap->fallible, "Possible integer overflow in memory allocation (%zu + %zu)", size, alignment); + return NULL; + } if (new_size == old_size) { #if ZEND_DEBUG zend_mm_change_huge_block_size(heap, ptr, new_size, real_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); @@ -1920,6 +1929,10 @@ static zend_always_inline void *zend_mm_realloc_heap(zend_mm_heap *heap, void *p size_t real_size = size; size = ZEND_MM_ALIGNED_SIZE(size) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info)); + if (UNEXPECTED(size < real_size)) { + zend_mm_delayed_error_or_panic(heap->fallible, "Possible integer overflow in memory allocation (%zu + %zu)", ZEND_MM_ALIGNED_SIZE(real_size), ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + return NULL; + } #endif ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 5bd805aa8ddb..91edd5b5ba15 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -131,6 +131,16 @@ static zend_always_inline zend_string *ZSTR_KNOWN(size_t idx) { #define ZSTR_MAX_OVERHEAD (ZEND_MM_ALIGNED_SIZE(_ZSTR_HEADER_SIZE + 1)) #define ZSTR_MAX_LEN (SIZE_MAX - ZSTR_MAX_OVERHEAD) +/* Offset for safe_*alloc(n, m, offset) to size the string like zend_string_alloc(n * m + l). */ +static zend_always_inline size_t zend_string_safe_offset(size_t n, size_t m, size_t l) +{ + if (UNEXPECTED(l > ZSTR_MAX_LEN)) { + return SIZE_MAX; /* Rejected by the allocator. */ + } + /* Works even if n * m wraps, allocator detects that overflow. */ + return ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(n * m + l)) - n * m; +} + #define ZSTR_ALLOCA_ALLOC(str, _len, use_heap) do { \ (str) = (zend_string *)do_alloca(ZEND_MM_ALIGNED_SIZE_EX(_ZSTR_STRUCT_SIZE(_len), 8), (use_heap)); \ GC_SET_REFCOUNT(str, 1); \ @@ -209,10 +219,12 @@ static zend_always_inline zend_string *zend_string_alloc(size_t len, bool persis /* Fallible variant of zend_string_safe_alloc */ static zend_always_inline zend_string *zend_string_safe_falloc(size_t n, size_t m, size_t l, bool persistent, bool fallible) { - zend_string *ret = (zend_string *)safe_fpemalloc(n, m, ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(l)), persistent, fallible); + zend_string *ret = (zend_string *)safe_fpemalloc(n, m, zend_string_safe_offset(n, m, l), persistent, fallible); if (ZEND_CONST_COND(fallible, true) && UNEXPECTED(ret == NULL)) { return ret; } + /* GCC can't see that allocation fails if l is too large, so make it explicit. */ + ZEND_ASSERT(l <= ZSTR_MAX_LEN); GC_SET_REFCOUNT(ret, 1); GC_TYPE_INFO(ret) = GC_STRING | ((persistent ? IS_STR_PERSISTENT : 0) << GC_FLAGS_SHIFT); @@ -394,10 +406,12 @@ static zend_always_inline zend_string *zend_string_safe_frealloc(zend_string *s, if (!ZSTR_IS_INTERNED(s)) { if (GC_REFCOUNT(s) == 1) { - ret = (zend_string *)safe_fperealloc(s, n, m, ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(l)), persistent, fallible); + ret = (zend_string *)safe_fperealloc(s, n, m, zend_string_safe_offset(n, m, l), persistent, fallible); if (ZEND_CONST_COND(fallible, true) && !ret) { return NULL; } + /* GCC can't see that allocation fails if l is too large, so make it explicit. */ + ZEND_ASSERT(l <= ZSTR_MAX_LEN); ZSTR_LEN(ret) = (n * m) + l; zend_string_forget_hash_val(ret); return ret; diff --git a/ext/mbstring/libmbfl/mbfl/mbfl_encoding.h b/ext/mbstring/libmbfl/mbfl/mbfl_encoding.h index e71737318bf7..594e2f3ebd2a 100644 --- a/ext/mbstring/libmbfl/mbfl/mbfl_encoding.h +++ b/ext/mbstring/libmbfl/mbfl/mbfl_encoding.h @@ -155,7 +155,7 @@ typedef zend_string* (*mb_cut_fn)(unsigned char *str, size_t from, size_t len, u static inline void mb_convert_buf_init(mb_convert_buf *buf, size_t initsize, uint32_t repl_char, unsigned int err_mode) { buf->state = buf->errors = 0; - buf->str = emalloc(_ZSTR_STRUCT_SIZE(initsize)); + buf->str = emalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(initsize))); buf->out = (unsigned char*)ZSTR_VAL(buf->str); buf->limit = buf->out + initsize; buf->replacement_char = repl_char; @@ -167,16 +167,18 @@ static inline zend_result mb_convert_buf_ensure(mb_convert_buf *buf, unsigned ch ZEND_ASSERT(*out <= *limit); if ((size_t)(*limit - *out) < needed) { size_t oldsize = *limit - (unsigned char*)ZSTR_VAL((buf)->str); - size_t newsize = oldsize + MAX(oldsize >> 1, needed); - zend_string *newstr = safe_ferealloc((buf)->str, 1, newsize, _ZSTR_STRUCT_SIZE(0), /* fallible */ true); + size_t growth = MAX(oldsize >> 1, needed); + zend_string *newstr = safe_ferealloc((buf)->str, 1, oldsize, zend_string_safe_offset(1, oldsize, growth), /* fallible */ true); if (!newstr) { /* An earlier growth may have moved the buffer. */ buf->out = *out; buf->limit = *limit; return FAILURE; } + /* GCC can't see that allocation fails if growth is too large, so make it explicit. */ + ZEND_ASSERT(growth <= ZSTR_MAX_LEN); *out = (unsigned char*)ZSTR_VAL(newstr) + (*out - (unsigned char*)ZSTR_VAL((buf)->str)); - *limit = (unsigned char*)ZSTR_VAL(newstr) + newsize; + *limit = (unsigned char*)ZSTR_VAL(newstr) + oldsize + growth; (buf)->str = newstr; } return SUCCESS; diff --git a/ext/standard/tests/strings/chunk_split_variation2_32bit.phpt b/ext/standard/tests/strings/chunk_split_variation2_32bit.phpt index c83a37f00edb..1f378a739d53 100644 --- a/ext/standard/tests/strings/chunk_split_variation2_32bit.phpt +++ b/ext/standard/tests/strings/chunk_split_variation2_32bit.phpt @@ -16,4 +16,4 @@ var_dump(chunk_split($a,$b,$c)); --EXPECTF-- *** Testing chunk_split() : unexpected large 'end' string argument variation 2 *** -Fatal error: Possible integer overflow in memory allocation (65537 * 65537 + %r65556|65560%r) in %s on line %d +Fatal error: Possible integer overflow in memory allocation (65537 * 65537 + %d) in %s on line %d