Skip to content

Commit 2bf6c01

Browse files
Reuse safe allocators for aligned strings
Apply ndossche's suggestion and check realloc alignment overflow. php#23869 (comment)
1 parent 814300a commit 2bf6c01

4 files changed

Lines changed: 39 additions & 38 deletions

File tree

‎Zend/zend_alloc.c‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1825,6 +1825,10 @@ static zend_never_inline void *zend_mm_realloc_huge(zend_mm_heap *heap, void *pt
18251825
#if ZEND_DEBUG
18261826
real_size = size;
18271827
size = ZEND_MM_ALIGNED_SIZE(size) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info));
1828+
if (UNEXPECTED(size < real_size)) {
1829+
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)));
1830+
return NULL;
1831+
}
18281832
#endif
18291833
if (size > ZEND_MM_MAX_LARGE_SIZE) {
18301834
#if ZEND_DEBUG
@@ -1835,10 +1839,15 @@ static zend_never_inline void *zend_mm_realloc_huge(zend_mm_heap *heap, void *pt
18351839
* We allocate them with 2MB size granularity, to avoid many
18361840
* reallocations when they are extended by small pieces
18371841
*/
1838-
new_size = ZEND_MM_ALIGNED_SIZE_EX(size, MAX(REAL_PAGE_SIZE, ZEND_MM_CHUNK_SIZE));
1842+
size_t alignment = MAX(REAL_PAGE_SIZE, ZEND_MM_CHUNK_SIZE);
18391843
#else
1840-
new_size = ZEND_MM_ALIGNED_SIZE_EX(size, REAL_PAGE_SIZE);
1844+
size_t alignment = REAL_PAGE_SIZE;
18411845
#endif
1846+
new_size = ZEND_MM_ALIGNED_SIZE_EX(size, alignment);
1847+
if (UNEXPECTED(new_size < size)) {
1848+
zend_mm_delayed_error_or_panic(heap->fallible, "Possible integer overflow in memory allocation (%zu + %zu)", size, alignment);
1849+
return NULL;
1850+
}
18421851
if (new_size == old_size) {
18431852
#if ZEND_DEBUG
18441853
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
19201929
size_t real_size = size;
19211930

19221931
size = ZEND_MM_ALIGNED_SIZE(size) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info));
1932+
if (UNEXPECTED(size < real_size)) {
1933+
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)));
1934+
return NULL;
1935+
}
19231936
#endif
19241937

19251938
ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted");

‎Zend/zend_string.h‎

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -131,24 +131,14 @@ static zend_always_inline zend_string *ZSTR_KNOWN(size_t idx) {
131131
#define ZSTR_MAX_OVERHEAD (ZEND_MM_ALIGNED_SIZE(_ZSTR_HEADER_SIZE + 1))
132132
#define ZSTR_MAX_LEN (SIZE_MAX - ZSTR_MAX_OVERHEAD)
133133

134-
/* Returns n * m + l, or SIZE_MAX on fallible overflow. Rejects lengths above
135-
* ZSTR_MAX_LEN so the aligned allocation size of the string can not wrap. */
136-
static zend_always_inline size_t zend_string_safe_len(size_t n, size_t m, size_t l, bool fallible)
137-
{
138-
bool overflow;
139-
size_t len = zend_safe_address(n, m, l, &overflow);
140-
141-
if (UNEXPECTED(overflow || len > ZSTR_MAX_LEN)) {
142-
zend_mm_delayed_error_or_panic(fallible, "Possible integer overflow in memory allocation (%zu * %zu + %zu)", n, m, l);
143-
if (!fallible) {
144-
/* zend_mm_delayed_error_or_panic() aborts for !fallible, but its body
145-
* is in zend_alloc.c and is not visible when compiling other source files.
146-
* Its declaration cannot use ZEND_NORETURN because the fallible path returns. */
147-
ZEND_UNREACHABLE();
148-
}
149-
return SIZE_MAX;
134+
/* Offset for safe_*alloc(n, m, offset) to size the string like zend_string_alloc(n * m + l). */
135+
static zend_always_inline size_t zend_string_safe_offset(size_t n, size_t m, size_t l)
136+
{
137+
if (UNEXPECTED(l > ZSTR_MAX_LEN)) {
138+
return SIZE_MAX; /* Rejected by the allocator. */
150139
}
151-
return len;
140+
/* Works even if n * m wraps, allocator detects that overflow. */
141+
return ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(n * m + l)) - n * m;
152142
}
153143

154144
#define ZSTR_ALLOCA_ALLOC(str, _len, use_heap) do { \
@@ -229,11 +219,16 @@ static zend_always_inline zend_string *zend_string_alloc(size_t len, bool persis
229219
/* Fallible variant of zend_string_safe_alloc */
230220
static zend_always_inline zend_string *zend_string_safe_falloc(size_t n, size_t m, size_t l, bool persistent, bool fallible)
231221
{
232-
size_t len = zend_string_safe_len(n, m, l, fallible && !persistent);
233-
if (UNEXPECTED(len == SIZE_MAX)) {
234-
return NULL;
222+
zend_string *ret = (zend_string *)safe_fpemalloc(n, m, zend_string_safe_offset(n, m, l), persistent, fallible);
223+
if (ZEND_CONST_COND(fallible, true) && UNEXPECTED(ret == NULL)) {
224+
return ret;
235225
}
236-
return zend_string_falloc(len, persistent, fallible);
226+
227+
GC_SET_REFCOUNT(ret, 1);
228+
GC_TYPE_INFO(ret) = GC_STRING | ((persistent ? IS_STR_PERSISTENT : 0) << GC_FLAGS_SHIFT);
229+
ZSTR_H(ret) = 0;
230+
ZSTR_LEN(ret) = (n * m) + l;
231+
return ret;
237232
}
238233

239234
static zend_always_inline zend_string *zend_string_safe_alloc(size_t n, size_t m, size_t l, bool persistent)
@@ -406,27 +401,23 @@ static zend_always_inline zend_string *zend_string_truncate(zend_string *s, size
406401
static zend_always_inline zend_string *zend_string_safe_frealloc(zend_string *s, size_t n, size_t m, size_t l, bool persistent, bool fallible)
407402
{
408403
zend_string *ret;
409-
size_t len = zend_string_safe_len(n, m, l, fallible && !persistent);
410-
if (UNEXPECTED(len == SIZE_MAX)) {
411-
return NULL;
412-
}
413404

414405
if (!ZSTR_IS_INTERNED(s)) {
415406
if (GC_REFCOUNT(s) == 1) {
416-
ret = (zend_string *)fperealloc(s, ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent, fallible);
407+
ret = (zend_string *)safe_fperealloc(s, n, m, zend_string_safe_offset(n, m, l), persistent, fallible);
417408
if (ZEND_CONST_COND(fallible, true) && !ret) {
418409
return NULL;
419410
}
420-
ZSTR_LEN(ret) = len;
411+
ZSTR_LEN(ret) = (n * m) + l;
421412
zend_string_forget_hash_val(ret);
422413
return ret;
423414
}
424415
}
425-
ret = zend_string_falloc(len, persistent, fallible);
416+
ret = zend_string_safe_falloc(n, m, l, persistent, fallible);
426417
if (ZEND_CONST_COND(fallible, true) && !ret) {
427418
return NULL;
428419
}
429-
memcpy(ZSTR_VAL(ret), ZSTR_VAL(s), MIN(len, ZSTR_LEN(s)) + 1);
420+
memcpy(ZSTR_VAL(ret), ZSTR_VAL(s), MIN((n * m) + l, ZSTR_LEN(s)) + 1);
430421
if (!ZSTR_IS_INTERNED(s)) {
431422
GC_DELREF(s);
432423
}

‎ext/mbstring/libmbfl/mbfl/mbfl_encoding.h‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,19 +167,16 @@ static inline zend_result mb_convert_buf_ensure(mb_convert_buf *buf, unsigned ch
167167
ZEND_ASSERT(*out <= *limit);
168168
if ((size_t)(*limit - *out) < needed) {
169169
size_t oldsize = *limit - (unsigned char*)ZSTR_VAL((buf)->str);
170-
size_t newsize = zend_string_safe_len(1, oldsize, MAX(oldsize >> 1, needed), /* fallible */ true);
171-
if (UNEXPECTED(newsize == SIZE_MAX)) {
172-
return FAILURE;
173-
}
174-
zend_string *newstr = ferealloc((buf)->str, ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(newsize)), /* fallible */ true);
170+
size_t growth = MAX(oldsize >> 1, needed);
171+
zend_string *newstr = safe_ferealloc((buf)->str, 1, oldsize, zend_string_safe_offset(1, oldsize, growth), /* fallible */ true);
175172
if (!newstr) {
176173
/* An earlier growth may have moved the buffer. */
177174
buf->out = *out;
178175
buf->limit = *limit;
179176
return FAILURE;
180177
}
181178
*out = (unsigned char*)ZSTR_VAL(newstr) + (*out - (unsigned char*)ZSTR_VAL((buf)->str));
182-
*limit = (unsigned char*)ZSTR_VAL(newstr) + newsize;
179+
*limit = (unsigned char*)ZSTR_VAL(newstr) + oldsize + growth;
183180
(buf)->str = newstr;
184181
}
185182
return SUCCESS;

‎ext/standard/tests/strings/chunk_split_variation2_32bit.phpt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ var_dump(chunk_split($a,$b,$c));
1616
--EXPECTF--
1717
*** Testing chunk_split() : unexpected large 'end' string argument variation 2 ***
1818

19-
Fatal error: Possible integer overflow in memory allocation (65537 * 65537 + 65537) in %s on line %d
19+
Fatal error: Possible integer overflow in memory allocation (65537 * 65537 + %d) in %s on line %d

0 commit comments

Comments
 (0)