From 8759ad62f7b2c5e65bfa0ffacbc4dd529a0025b0 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 22 May 2024 16:11:43 +0200 Subject: [PATCH 1/8] Remote heap spraying / feng shui protection Isolate request environment / input in separate chunks to makes it more difficult to remotely control the layout of the heap. --- Zend/tests/bug70258.phpt | 4 +- Zend/tests/closures/gh12073.phpt | 2 +- Zend/tests/fibers/out-of-memory-in-fiber.phpt | 2 +- .../fibers/out-of-memory-in-nested-fiber.phpt | 2 +- .../out-of-memory-in-recursive-fiber.phpt | 2 +- Zend/tests/gh11189.phpt | 2 +- Zend/tests/gh11189_1.phpt | 2 +- Zend/tests/new_oom.inc | 2 +- Zend/tests/object_gc_in_shutdown.phpt | 4 +- Zend/zend_alloc.c | 459 ++++++++++++------ Zend/zend_alloc.h | 3 + Zend/zend_compile.c | 6 + ext/opcache/ZendAccelerator.c | 2 + ext/spl/tests/SplObjectStorage/gh14639.phpt | 2 +- ext/standard/tests/streams/bug78902.phpt | 6 +- ext/zend_test/tests/observer_error_01.phpt | 6 +- main/main.c | 4 + tests/lang/bug45392.phpt | 4 +- 18 files changed, 351 insertions(+), 163 deletions(-) diff --git a/Zend/tests/bug70258.phpt b/Zend/tests/bug70258.phpt index d346dbdf3a35b..cf905317b974e 100644 --- a/Zend/tests/bug70258.phpt +++ b/Zend/tests/bug70258.phpt @@ -1,7 +1,7 @@ --TEST-- Bug #70258 (Segfault if do_resize fails to allocated memory) --INI-- -memory_limit=2M +memory_limit=4M --SKIPIF-- core(); ?> --EXPECTF-- -Fatal error: Allowed memory size of 2097152 bytes exhausted%s(tried to allocate %d bytes) in %s on line %d +Fatal error: Allowed memory size of 4194304 bytes exhausted%s(tried to allocate %d bytes) in %s on line %d diff --git a/Zend/tests/closures/gh12073.phpt b/Zend/tests/closures/gh12073.phpt index 811da788d03fc..59bdcc91ad6a9 100644 --- a/Zend/tests/closures/gh12073.phpt +++ b/Zend/tests/closures/gh12073.phpt @@ -19,7 +19,7 @@ function test() { }; } -ini_set('memory_limit', '2M'); +ini_set('memory_limit', '4M'); $array = []; for ($i = 0; $i < 10_000; $i++) { diff --git a/Zend/tests/fibers/out-of-memory-in-fiber.phpt b/Zend/tests/fibers/out-of-memory-in-fiber.phpt index 4d31aa6f76adc..ce7a729f4c41c 100644 --- a/Zend/tests/fibers/out-of-memory-in-fiber.phpt +++ b/Zend/tests/fibers/out-of-memory-in-fiber.phpt @@ -1,7 +1,7 @@ --TEST-- Out of Memory in a fiber --INI-- -memory_limit=2M +memory_limit=4M --SKIPIF-- --INI-- -memory_limit=2M +memory_limit=4M --FILE-- --INI-- -memory_limit=2M +memory_limit=4M --FILE-- zones[0]; \ + zend_mm_zone *__end = &__heap->zones[ZEND_MM_ZONES]; \ + for (; __zone != __end; __zone++) { \ + _zone = __zone; + +# define ZEND_MM_ZONE_FOREACH_END() \ + } \ + } while (0) + +# define ZEND_MM_ZONE_FREE_SLOT(heap, num) (&(heap)->free_slot[ZEND_MM_ZONE_LEN * num]) +# define ZEND_MM_CURRENT_ZONE(heap) (&(heap)->zones[(uintptr_t)(&(heap)->zone_free_slot[0] - &(heap)->free_slot[0]) / ZEND_MM_ZONE_LEN]) +# define ZEND_MM_FREE_SLOT(heap, bin_num) ((heap)->zone_free_slot[(bin_num)]) +# define ZEND_MM_FREE_SLOT_EX(heap, chunk, bin_num) ((chunk)->zone_free_slot[(bin_num)]) +# define ZEND_MM_CHUNK_ZONE(heap, chunk) ((chunk)->zone) + +#else /* ZEND_MM_HEAP_PROTECTION */ + +# define ZEND_MM_ZONES 1 + +# define ZEND_MM_ZONE_FOREACH(_heap, _zone) do { \ + _zone = &_heap->zones[0]; \ + +# define ZEND_MM_ZONE_FOREACH_END() \ + } while (0) + +# define ZEND_MM_CURRENT_ZONE(heap) (&(heap)->zones[0]) +# define ZEND_MM_FREE_SLOT(heap, bin_num) ((heap)->free_slot[(bin_num)]) +# define ZEND_MM_FREE_SLOT_EX(heap, chunk, bin_num) ZEND_MM_FREE_SLOT(heap, bin_num) +# define ZEND_MM_CHUNK_ZONE(heap, chunk) (&(heap)->zones[0]) + +#endif /* ZEND_MM_HEAP_PROTECTION */ + #if UINTPTR_MAX == UINT64_MAX # define BSWAPPTR(u) ZEND_BYTES_SWAP64(u) #else @@ -227,10 +275,15 @@ typedef struct _zend_mm_page zend_mm_page; typedef struct _zend_mm_bin zend_mm_bin; typedef struct _zend_mm_free_slot zend_mm_free_slot; typedef struct _zend_mm_chunk zend_mm_chunk; +typedef struct _zend_mm_zone zend_mm_zone; typedef struct _zend_mm_huge_list zend_mm_huge_list; static bool zend_mm_use_huge_pages = false; +struct _zend_mm_zone { + zend_mm_chunk *chunks; +}; + /* * Memory is retrieved from OS by chunks of fixed size 2MB. * Inside chunk it's managed by pages of fixed size 4096B. @@ -275,7 +328,10 @@ struct _zend_mm_heap { size_t peak; /* peak memory usage */ #endif uintptr_t shadow_key; /* free slot shadow ptr xor key */ - zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */ +#if ZEND_MM_HEAP_PROTECTION + zend_mm_free_slot **zone_free_slot; +#endif + zend_mm_free_slot *free_slot[ZEND_MM_FREE_SLOT_LEN]; /* free lists for small sizes */ #if ZEND_MM_STAT || ZEND_MM_LIMIT size_t real_size; /* current size of allocated pages */ #endif @@ -297,6 +353,7 @@ struct _zend_mm_heap { double avg_chunks_count; /* average number of chunks allocated per request */ int last_chunks_delete_boundary; /* number of chunks after last deletion */ int last_chunks_delete_count; /* number of deletion over the last boundary */ + zend_mm_zone zones[ZEND_MM_ZONES]; #if ZEND_MM_CUSTOM struct { void *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC); @@ -325,17 +382,26 @@ struct _zend_mm_heap { struct _zend_mm_chunk { zend_mm_heap *heap; +#if ZEND_MM_HEAP_PROTECTION + zend_mm_free_slot **zone_free_slot; +#endif zend_mm_chunk *next; zend_mm_chunk *prev; uint32_t free_pages; /* number of free pages */ uint32_t free_tail; /* number of free pages at the end of chunk */ uint32_t num; - char reserve[64 - (sizeof(void*) * 3 + sizeof(uint32_t) * 3)]; + /* align heap_slot to cache line boundary (assumed to be 64 bytes) */ + char reserve[64 - (sizeof(void*) * 4 + sizeof(uint32_t) * 3)]; zend_mm_heap heap_slot; /* used only in main chunk */ +#if ZEND_MM_HEAP_PROTECTION + zend_mm_zone *zone; +#endif zend_mm_page_map free_map; /* 512 bits or 64 bytes */ zend_mm_page_info map[ZEND_MM_PAGES]; /* 2 KB = 512 * 4 */ }; +ZEND_STATIC_ASSERT(sizeof(zend_mm_chunk) <= ZEND_MM_FIRST_PAGE * ZEND_MM_PAGE_SIZE, ""); + struct _zend_mm_page { char bytes[ZEND_MM_PAGE_SIZE]; }; @@ -885,13 +951,23 @@ static int zend_mm_chunk_extend(zend_mm_heap *heap, void *addr, size_t old_size, #endif } -static zend_always_inline void zend_mm_chunk_init(zend_mm_heap *heap, zend_mm_chunk *chunk) +static zend_always_inline void zend_mm_chunk_init(zend_mm_heap *heap, zend_mm_zone *zone, zend_mm_chunk *chunk) { chunk->heap = heap; - chunk->next = heap->main_chunk; - chunk->prev = heap->main_chunk->prev; - chunk->prev->next = chunk; - chunk->next->prev = chunk; + if (UNEXPECTED(zone->chunks == NULL)) { + zone->chunks = chunk; + chunk->next = chunk; + chunk->prev = chunk; + } else { + chunk->next = zone->chunks; + chunk->prev = zone->chunks->prev; + chunk->prev->next = chunk; + chunk->next->prev = chunk; + } +#if ZEND_MM_HEAP_PROTECTION + chunk->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(heap, (uintptr_t)(zone - &heap->zones[0])); + chunk->zone = zone; +#endif /* mark first pages as allocated */ chunk->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; chunk->free_tail = ZEND_MM_FIRST_PAGE; @@ -926,10 +1002,15 @@ static void *zend_mm_alloc_pages(zend_mm_heap *heap, uint32_t pages_count, size_ static void *zend_mm_alloc_pages(zend_mm_heap *heap, uint32_t pages_count ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) #endif { - zend_mm_chunk *chunk = heap->main_chunk; + zend_mm_zone *zone = ZEND_MM_CURRENT_ZONE(heap); + zend_mm_chunk *chunk = zone->chunks; uint32_t page_num, len; int steps = 0; + if (UNEXPECTED(!chunk)) { + goto get_chunk; + } + while (1) { if (UNEXPECTED(chunk->free_pages < pages_count)) { goto not_found; @@ -1045,7 +1126,7 @@ static void *zend_mm_alloc_pages(zend_mm_heap *heap, uint32_t pages_count ZEND_F } not_found: - if (chunk->next == heap->main_chunk) { + if (chunk->next == zone->chunks) { get_chunk: if (heap->cached_chunks) { heap->cached_chunks_count--; @@ -1099,7 +1180,7 @@ static void *zend_mm_alloc_pages(zend_mm_heap *heap, uint32_t pages_count ZEND_F if (heap->chunks_count > heap->peak_chunks_count) { heap->peak_chunks_count = heap->chunks_count; } - zend_mm_chunk_init(heap, chunk); + zend_mm_chunk_init(heap, zone, chunk); page_num = ZEND_MM_FIRST_PAGE; len = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; goto found; @@ -1117,8 +1198,8 @@ static void *zend_mm_alloc_pages(zend_mm_heap *heap, uint32_t pages_count ZEND_F /* move chunk into the head of the linked-list */ chunk->prev->next = chunk->next; chunk->next->prev = chunk->prev; - chunk->next = heap->main_chunk->next; - chunk->prev = heap->main_chunk; + chunk->next = zone->chunks->next; + chunk->prev = zone->chunks; chunk->prev->next = chunk; chunk->next->prev = chunk; } @@ -1161,8 +1242,13 @@ static zend_always_inline void zend_mm_delete_chunk(zend_mm_heap *heap, zend_mm_ ZEND_MM_CHECK(chunk->next->prev == chunk, "zend_mm_heap corrupted"); ZEND_MM_CHECK(chunk->prev->next == chunk, "zend_mm_heap corrupted"); - chunk->next->prev = chunk->prev; - chunk->prev->next = chunk->next; + if (ZEND_MM_CHUNK_ZONE(heap, chunk)->chunks == chunk && chunk->next == chunk) { + ZEND_ASSERT(chunk->prev == chunk); + ZEND_MM_CHUNK_ZONE(heap, chunk)->chunks = NULL; + } else { + chunk->next->prev = chunk->prev; + chunk->prev->next = chunk->next; + } heap->chunks_count--; if (heap->chunks_count + heap->cached_chunks_count < heap->avg_chunks_count + 0.1 || (heap->chunks_count == heap->last_chunks_delete_boundary @@ -1384,7 +1470,7 @@ static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint /* create a linked list of elements from 1 to last */ end = (zend_mm_free_slot*)((char*)bin + (bin_data_size[bin_num] * (bin_elements[bin_num] - 1))); - heap->free_slot[bin_num] = p = (zend_mm_free_slot*)((char*)bin + bin_data_size[bin_num]); + ZEND_MM_FREE_SLOT(heap, bin_num) = p = (zend_mm_free_slot*)((char*)bin + bin_data_size[bin_num]); do { zend_mm_set_next_free_slot(heap, bin_num, p, (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num])); #if ZEND_DEBUG @@ -1411,6 +1497,7 @@ static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, int bin_num ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { + ZEND_ASSERT(bin_num <= ZEND_MM_BINS); ZEND_ASSERT(bin_data_size[bin_num] >= ZEND_MM_MIN_USEABLE_BIN_SIZE); #if ZEND_MM_STAT @@ -1422,17 +1509,18 @@ static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, int bin_ } while (0); #endif - if (EXPECTED(heap->free_slot[bin_num] != NULL)) { - zend_mm_free_slot *p = heap->free_slot[bin_num]; - heap->free_slot[bin_num] = zend_mm_get_next_free_slot(heap, bin_num, p); + if (EXPECTED(ZEND_MM_FREE_SLOT(heap, bin_num) != NULL)) { + zend_mm_free_slot *p = ZEND_MM_FREE_SLOT(heap, bin_num); + ZEND_MM_FREE_SLOT(heap, bin_num) = zend_mm_get_next_free_slot(heap, bin_num, p); return p; } else { return zend_mm_alloc_small_slow(heap, bin_num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); } } -static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr, int bin_num) +static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, zend_mm_chunk *chunk, void *ptr, int bin_num) { + ZEND_ASSERT(bin_num <= ZEND_MM_BINS); ZEND_ASSERT(bin_data_size[bin_num] >= ZEND_MM_MIN_USEABLE_BIN_SIZE); zend_mm_free_slot *p; @@ -1449,8 +1537,8 @@ static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr, #endif p = (zend_mm_free_slot*)ptr; - zend_mm_set_next_free_slot(heap, bin_num, p, heap->free_slot[bin_num]); - heap->free_slot[bin_num] = p; + zend_mm_set_next_free_slot(heap, bin_num, p, ZEND_MM_FREE_SLOT_EX(heap, chunk, bin_num)); + ZEND_MM_FREE_SLOT_EX(heap, chunk, bin_num) = p; } /********/ @@ -1546,7 +1634,7 @@ static zend_always_inline void zend_mm_free_heap(zend_mm_heap *heap, void *ptr Z ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); if (EXPECTED(info & ZEND_MM_IS_SRUN)) { - zend_mm_free_small(heap, ptr, ZEND_MM_SRUN_BIN_NUM(info)); + zend_mm_free_small(heap, chunk, ptr, ZEND_MM_SRUN_BIN_NUM(info)); } else /* if (info & ZEND_MM_IS_LRUN) */ { int pages_count = ZEND_MM_LRUN_PAGES(info); @@ -1736,7 +1824,7 @@ static zend_always_inline void *zend_mm_realloc_heap(zend_mm_heap *heap, void *p ret = zend_mm_alloc_small(heap, ZEND_MM_SMALL_SIZE_TO_BIN(size) ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); copy_size = use_copy_size ? MIN(size, copy_size) : size; memcpy(ret, ptr, copy_size); - zend_mm_free_small(heap, ptr, old_bin_num); + zend_mm_free_small(heap, chunk, ptr, old_bin_num); } else { /* reallocation in-place */ ret = ptr; @@ -1751,7 +1839,7 @@ static zend_always_inline void *zend_mm_realloc_heap(zend_mm_heap *heap, void *p ret = zend_mm_alloc_small(heap, ZEND_MM_SMALL_SIZE_TO_BIN(size) ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); copy_size = use_copy_size ? MIN(old_size, copy_size) : old_size; memcpy(ret, ptr, copy_size); - zend_mm_free_small(heap, ptr, old_bin_num); + zend_mm_free_small(heap, chunk, ptr, old_bin_num); #if ZEND_MM_STAT heap->peak = MAX(orig_peak, heap->size); } while (0); @@ -2075,6 +2163,10 @@ static zend_mm_heap *zend_mm_init(void) } heap = &chunk->heap_slot; chunk->heap = heap; +#if ZEND_MM_HEAP_PROTECTION + chunk->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(heap, ZEND_MM_ZONE_DEFAULT); + chunk->zone = &heap->zones[0]; +#endif chunk->next = chunk; chunk->prev = chunk; chunk->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; @@ -2084,6 +2176,13 @@ static zend_mm_heap *zend_mm_init(void) chunk->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE); heap->main_chunk = chunk; heap->cached_chunks = NULL; +#if ZEND_MM_HEAP_PROTECTION + heap->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(heap, ZEND_MM_ZONE_DEFAULT); +#endif + heap->zones[0].chunks = chunk; +#if ZEND_MM_HEAP_PROTECTION + heap->zones[1].chunks = NULL; +#endif heap->chunks_count = 1; heap->peak_chunks_count = 1; heap->cached_chunks_count = 0; @@ -2123,9 +2222,10 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) size_t page_offset; int page_num; zend_mm_page_info info; - uint32_t i, free_counter; + uint32_t i, free_counter, bin_num; bool has_free_pages; size_t collected = 0; + zend_mm_zone *zone; #if ZEND_MM_CUSTOM if (heap->use_custom_heap) { @@ -2137,9 +2237,10 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) } #endif - for (i = 0; i < ZEND_MM_BINS; i++) { + for (i = 0; i < ZEND_MM_FREE_SLOT_LEN; i++) { has_free_pages = false; p = heap->free_slot[i]; + bin_num = i % ZEND_MM_ZONE_LEN; while (p != NULL) { chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(p, ZEND_MM_CHUNK_SIZE); ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); @@ -2154,13 +2255,13 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) ZEND_ASSERT(info & ZEND_MM_IS_SRUN); ZEND_ASSERT(!(info & ZEND_MM_IS_LRUN)); } - ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(info) == i); + ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(info) == bin_num); free_counter = ZEND_MM_SRUN_FREE_COUNTER(info) + 1; - if (free_counter == bin_elements[i]) { + if (free_counter == bin_elements[bin_num]) { has_free_pages = true; } - chunk->map[page_num] = ZEND_MM_SRUN_EX(i, free_counter); - p = zend_mm_get_next_free_slot(heap, i, p); + chunk->map[page_num] = ZEND_MM_SRUN_EX(bin_num, free_counter); + p = zend_mm_get_next_free_slot(heap, bin_num, p); } if (!has_free_pages) { @@ -2183,61 +2284,66 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) ZEND_ASSERT(info & ZEND_MM_IS_SRUN); ZEND_ASSERT(!(info & ZEND_MM_IS_LRUN)); } - ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(info) == i); - if (ZEND_MM_SRUN_FREE_COUNTER(info) == bin_elements[i]) { + ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(info) == bin_num); + if (ZEND_MM_SRUN_FREE_COUNTER(info) == bin_elements[bin_num]) { /* remove from cache */ - p = zend_mm_get_next_free_slot(heap, i, p); + p = zend_mm_get_next_free_slot(heap, bin_num, p); if (q == (zend_mm_free_slot*)&heap->free_slot[i]) { q->next_free_slot = p; } else { - zend_mm_set_next_free_slot(heap, i, q, p); + zend_mm_set_next_free_slot(heap, bin_num, q, p); } } else { q = p; if (q == (zend_mm_free_slot*)&heap->free_slot[i]) { p = q->next_free_slot; } else { - p = zend_mm_get_next_free_slot(heap, i, q); + p = zend_mm_get_next_free_slot(heap, bin_num, q); } } } } - chunk = heap->main_chunk; - do { - i = ZEND_MM_FIRST_PAGE; - while (i < chunk->free_tail) { - if (zend_mm_bitset_is_set(chunk->free_map, i)) { - info = chunk->map[i]; - if (info & ZEND_MM_IS_SRUN) { - int bin_num = ZEND_MM_SRUN_BIN_NUM(info); - int pages_count = bin_pages[bin_num]; - - if (ZEND_MM_SRUN_FREE_COUNTER(info) == bin_elements[bin_num]) { - /* all elements are free */ - zend_mm_free_pages_ex(heap, chunk, i, pages_count, 0); - collected += pages_count; - } else { - /* reset counter */ - chunk->map[i] = ZEND_MM_SRUN(bin_num); + ZEND_MM_ZONE_FOREACH(heap, zone) { + chunk = zone->chunks; + if (chunk == NULL) { + continue; + } + do { + i = ZEND_MM_FIRST_PAGE; + while (i < chunk->free_tail) { + if (zend_mm_bitset_is_set(chunk->free_map, i)) { + info = chunk->map[i]; + if (info & ZEND_MM_IS_SRUN) { + bin_num = ZEND_MM_SRUN_BIN_NUM(info); + int pages_count = bin_pages[bin_num]; + + if (ZEND_MM_SRUN_FREE_COUNTER(info) == bin_elements[bin_num]) { + /* all elements are free */ + zend_mm_free_pages_ex(heap, chunk, i, pages_count, 0); + collected += pages_count; + } else { + /* reset counter */ + chunk->map[i] = ZEND_MM_SRUN(bin_num); + } + i += bin_pages[bin_num]; + } else /* if (info & ZEND_MM_IS_LRUN) */ { + i += ZEND_MM_LRUN_PAGES(info); } - i += bin_pages[bin_num]; - } else /* if (info & ZEND_MM_IS_LRUN) */ { - i += ZEND_MM_LRUN_PAGES(info); + } else { + i++; } - } else { - i++; } - } - if (chunk->free_pages == ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE && chunk != heap->main_chunk) { - zend_mm_chunk *next_chunk = chunk->next; + if (chunk->free_pages == ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE && chunk != heap->main_chunk) { + zend_mm_chunk *next_chunk = chunk->next; - zend_mm_delete_chunk(heap, chunk); - chunk = next_chunk; - } else { - chunk = chunk->next; - } - } while (chunk != heap->main_chunk); + zend_mm_delete_chunk(heap, chunk); + chunk = next_chunk; + } else { + chunk = chunk->next; + } + } while (zone->chunks && chunk != zone->chunks); + } ZEND_MM_ZONE_FOREACH_END(); return collected * ZEND_MM_PAGE_SIZE; } @@ -2274,7 +2380,7 @@ static zend_long zend_mm_find_leaks_small(zend_mm_chunk *p, uint32_t i, uint32_t return count; } -static zend_long zend_mm_find_leaks(zend_mm_heap *heap, zend_mm_chunk *p, uint32_t i, zend_leak_info *leak) +static zend_long zend_mm_find_leaks(zend_mm_zone *zone, zend_mm_chunk *p, uint32_t i, zend_leak_info *leak) { zend_long count = 0; @@ -2301,7 +2407,7 @@ static zend_long zend_mm_find_leaks(zend_mm_heap *heap, zend_mm_chunk *p, uint32 } p = p->next; i = ZEND_MM_FIRST_PAGE; - } while (p != heap->main_chunk); + } while (p != zone->chunks); return count; } @@ -2334,6 +2440,7 @@ static void zend_mm_check_leaks(zend_mm_heap *heap) zend_long repeated = 0; uint32_t total = 0; uint32_t i, j; + zend_mm_zone *zone; /* find leaked huge blocks and free them */ list = heap->huge_list; @@ -2360,73 +2467,78 @@ static void zend_mm_check_leaks(zend_mm_heap *heap) zend_mm_free_heap(heap, q, NULL, 0, NULL, 0); } - /* for each chunk */ - p = heap->main_chunk; - do { - i = ZEND_MM_FIRST_PAGE; - while (i < p->free_tail) { - if (zend_mm_bitset_is_set(p->free_map, i)) { - if (p->map[i] & ZEND_MM_IS_SRUN) { - int bin_num = ZEND_MM_SRUN_BIN_NUM(p->map[i]); - zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * i + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); - - j = 0; - while (j < bin_elements[bin_num]) { - if (dbg->size != 0) { - leak.addr = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * i + bin_data_size[bin_num] * j); - leak.size = dbg->size; - leak.filename = dbg->filename; - leak.orig_filename = dbg->orig_filename; - leak.lineno = dbg->lineno; - leak.orig_lineno = dbg->orig_lineno; - - zend_message_dispatcher(ZMSG_LOG_SCRIPT_NAME, NULL); - zend_message_dispatcher(ZMSG_MEMORY_LEAK_DETECTED, &leak); - - dbg->size = 0; - dbg->filename = NULL; - dbg->lineno = 0; - - repeated = zend_mm_find_leaks_small(p, i, j + 1, &leak) + - zend_mm_find_leaks(heap, p, i + bin_pages[bin_num], &leak); - total += 1 + repeated; - if (repeated) { - zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, (void *)(uintptr_t)repeated); + ZEND_MM_ZONE_FOREACH(heap, zone) { + /* for each chunk */ + p = zone->chunks; + if (p == NULL) { + continue; + } + do { + i = ZEND_MM_FIRST_PAGE; + while (i < p->free_tail) { + if (zend_mm_bitset_is_set(p->free_map, i)) { + if (p->map[i] & ZEND_MM_IS_SRUN) { + int bin_num = ZEND_MM_SRUN_BIN_NUM(p->map[i]); + zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * i + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + + j = 0; + while (j < bin_elements[bin_num]) { + if (dbg->size != 0) { + leak.addr = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * i + bin_data_size[bin_num] * j); + leak.size = dbg->size; + leak.filename = dbg->filename; + leak.orig_filename = dbg->orig_filename; + leak.lineno = dbg->lineno; + leak.orig_lineno = dbg->orig_lineno; + + zend_message_dispatcher(ZMSG_LOG_SCRIPT_NAME, NULL); + zend_message_dispatcher(ZMSG_MEMORY_LEAK_DETECTED, &leak); + + dbg->size = 0; + dbg->filename = NULL; + dbg->lineno = 0; + + repeated = zend_mm_find_leaks_small(p, i, j + 1, &leak) + + zend_mm_find_leaks(zone, p, i + bin_pages[bin_num], &leak); + total += 1 + repeated; + if (repeated) { + zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, (void *)(uintptr_t)repeated); + } } + dbg = (zend_mm_debug_info*)((char*)dbg + bin_data_size[bin_num]); + j++; } - dbg = (zend_mm_debug_info*)((char*)dbg + bin_data_size[bin_num]); - j++; - } - i += bin_pages[bin_num]; - } else /* if (p->map[i] & ZEND_MM_IS_LRUN) */ { - int pages_count = ZEND_MM_LRUN_PAGES(p->map[i]); - zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * (i + pages_count) - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); - - leak.addr = (void*)((char*)p + ZEND_MM_PAGE_SIZE * i); - leak.size = dbg->size; - leak.filename = dbg->filename; - leak.orig_filename = dbg->orig_filename; - leak.lineno = dbg->lineno; - leak.orig_lineno = dbg->orig_lineno; - - zend_message_dispatcher(ZMSG_LOG_SCRIPT_NAME, NULL); - zend_message_dispatcher(ZMSG_MEMORY_LEAK_DETECTED, &leak); - - zend_mm_bitset_reset_range(p->free_map, i, pages_count); - - repeated = zend_mm_find_leaks(heap, p, i + pages_count, &leak); - total += 1 + repeated; - if (repeated) { - zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, (void *)(uintptr_t)repeated); + i += bin_pages[bin_num]; + } else /* if (p->map[i] & ZEND_MM_IS_LRUN) */ { + int pages_count = ZEND_MM_LRUN_PAGES(p->map[i]); + zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + ZEND_MM_PAGE_SIZE * (i + pages_count) - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + + leak.addr = (void*)((char*)p + ZEND_MM_PAGE_SIZE * i); + leak.size = dbg->size; + leak.filename = dbg->filename; + leak.orig_filename = dbg->orig_filename; + leak.lineno = dbg->lineno; + leak.orig_lineno = dbg->orig_lineno; + + zend_message_dispatcher(ZMSG_LOG_SCRIPT_NAME, NULL); + zend_message_dispatcher(ZMSG_MEMORY_LEAK_DETECTED, &leak); + + zend_mm_bitset_reset_range(p->free_map, i, pages_count); + + repeated = zend_mm_find_leaks(zone, p, i + pages_count, &leak); + total += 1 + repeated; + if (repeated) { + zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, (void *)(uintptr_t)repeated); + } + i += pages_count; } - i += pages_count; + } else { + i++; } - } else { - i++; } - } - p = p->next; - } while (p != heap->main_chunk); + p = p->next; + } while (p != zone->chunks); + } ZEND_MM_ZONE_FOREACH_END(); if (total) { zend_message_dispatcher(ZMSG_MEMORY_LEAKS_GRAND_TOTAL, &total); } @@ -2453,6 +2565,7 @@ ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent) { zend_mm_chunk *p; zend_mm_huge_list *list; + zend_mm_zone *zone; #if ZEND_MM_CUSTOM if (heap->use_custom_heap) { @@ -2505,16 +2618,25 @@ ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent) zend_mm_chunk_free(heap, q->ptr, q->size); } - /* move all chunks except of the first one into the cache */ - p = heap->main_chunk->next; - while (p != heap->main_chunk) { - zend_mm_chunk *q = p->next; - p->next = heap->cached_chunks; - heap->cached_chunks = p; - p = q; - heap->chunks_count--; - heap->cached_chunks_count++; - } + /* move all chunks except of the main one into the cache */ + ZEND_MM_ZONE_FOREACH(heap, zone) { + p = zone->chunks; + if (p == NULL) { + continue; + } + do { + zend_mm_chunk *q = p->next; + if (p == heap->main_chunk) { + p = q; + continue; + } + p->next = heap->cached_chunks; + heap->cached_chunks = p; + p = q; + heap->chunks_count--; + heap->cached_chunks_count++; + } while (p != zone->chunks); + } ZEND_MM_ZONE_FOREACH_END(); if (full) { /* free all cached chunks */ @@ -2528,6 +2650,7 @@ ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent) } else { /* free some cached chunks to keep average count */ heap->avg_chunks_count = (heap->avg_chunks_count + (double)heap->peak_chunks_count) / 2.0; + while ((double)heap->cached_chunks_count + 0.9 > heap->avg_chunks_count && heap->cached_chunks) { p = heap->cached_chunks; @@ -2568,6 +2691,16 @@ ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent) heap->last_chunks_delete_boundary = 0; heap->last_chunks_delete_count = 0; +#if ZEND_MM_HEAP_PROTECTION + heap->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(heap, ZEND_MM_ZONE_DEFAULT); +#endif + heap->zones[0].chunks = p; +#if ZEND_MM_HEAP_PROTECTION + heap->zones[1].chunks = NULL; + ZEND_MM_CHECK(p->zone == &heap->zones[0], "zend_mm_heap corrupted"); + ZEND_MM_CHECK(p->zone_free_slot == ZEND_MM_ZONE_FREE_SLOT(heap, ZEND_MM_ZONE_DEFAULT), "zend_mm_heap corrupted"); +#endif + memset(p->free_map, 0, sizeof(p->free_map) + sizeof(p->map)); p->free_map[0] = (1L << ZEND_MM_FIRST_PAGE) - 1; p->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE); @@ -2629,6 +2762,7 @@ ZEND_API size_t ZEND_FASTCALL _zend_mm_block_size(zend_mm_heap *heap, void *ptr typedef struct _zend_alloc_globals { zend_mm_heap *mm_heap; + uint32_t use_userinput_zone; } zend_alloc_globals; #ifdef ZTS @@ -2640,6 +2774,10 @@ static size_t alloc_globals_offset; static zend_alloc_globals alloc_globals; #endif +#if ZEND_MM_HEAP_PROTECTION +# define ZEND_MM_ZONE_USERINPUT 1 +#endif + ZEND_API bool is_zend_mm(void) { #if ZEND_MM_CUSTOM @@ -2688,6 +2826,33 @@ ZEND_API bool is_zend_ptr(const void *ptr) return 0; } +ZEND_API void zend_mm_userinput_begin(void) +{ +#if ZEND_MM_HEAP_PROTECTION + AG(use_userinput_zone)++; + AG(mm_heap)->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_USERINPUT); +#endif +} + +ZEND_API void zend_mm_userinput_end(void) +{ +#if ZEND_MM_HEAP_PROTECTION + AG(use_userinput_zone)--; + if (!AG(use_userinput_zone)) { + AG(mm_heap)->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_DEFAULT); + } +#endif +} + +ZEND_API bool zend_mm_check_in_userinput(void) +{ +#if ZEND_MM_HEAP_PROTECTION + return AG(use_userinput_zone); +#else + return true; +#endif +} + #if !ZEND_DEBUG && defined(HAVE_BUILTIN_CONSTANT_P) #undef _emalloc @@ -2746,7 +2911,7 @@ ZEND_API void* ZEND_FASTCALL _emalloc_huge(size_t size) ZEND_MM_CHECK(chunk->heap == AG(mm_heap), "zend_mm_heap corrupted"); \ ZEND_ASSERT(chunk->map[page_num] & ZEND_MM_IS_SRUN); \ ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(chunk->map[page_num]) == _num); \ - zend_mm_free_small(AG(mm_heap), ptr, _num); \ + zend_mm_free_small(AG(mm_heap), chunk, ptr, _num); \ } \ } #else @@ -2760,7 +2925,7 @@ ZEND_API void* ZEND_FASTCALL _emalloc_huge(size_t size) { \ zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); \ ZEND_MM_CHECK(chunk->heap == AG(mm_heap), "zend_mm_heap corrupted"); \ - zend_mm_free_small(AG(mm_heap), ptr, _num); \ + zend_mm_free_small(AG(mm_heap), chunk, ptr, _num); \ } \ } #endif @@ -2983,6 +3148,12 @@ ZEND_API void zend_memory_reset_peak_usage(void) ZEND_API void shutdown_memory_manager(bool silent, bool full_shutdown) { zend_mm_shutdown(AG(mm_heap), full_shutdown, silent); + + if (!full_shutdown) { + ZEND_ASSERT(AG(use_userinput_zone) == 0 || silent); + AG(use_userinput_zone) = 0; + zend_mm_userinput_begin(); + } } ZEND_API void refresh_memory_manager(void) @@ -3296,6 +3467,8 @@ static void alloc_globals_ctor(zend_alloc_globals *alloc_globals) { char *tmp; + alloc_globals->use_userinput_zone = 0; + #if ZEND_MM_CUSTOM tmp = getenv("USE_ZEND_ALLOC"); if (tmp && !ZEND_ATOL(tmp)) { diff --git a/Zend/zend_alloc.h b/Zend/zend_alloc.h index 264e13848d1b7..c480d62dc765d 100644 --- a/Zend/zend_alloc.h +++ b/Zend/zend_alloc.h @@ -223,6 +223,9 @@ ZEND_API void shutdown_memory_manager(bool silent, bool full_shutdown); ZEND_API void refresh_memory_manager(void); ZEND_API bool is_zend_mm(void); ZEND_API bool is_zend_ptr(const void *ptr); +ZEND_API void zend_mm_userinput_begin(void); +ZEND_API void zend_mm_userinput_end(void); +ZEND_API bool zend_mm_check_in_userinput(void); ZEND_API size_t zend_memory_usage(bool real_usage); ZEND_API size_t zend_memory_peak_usage(bool real_usage); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0a7ab7da1bd5f..d7fed5f687b80 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1972,7 +1972,9 @@ ZEND_API bool zend_is_auto_global_str(const char *name, size_t len) /* {{{ */ { if ((auto_global = zend_hash_str_find_ptr(CG(auto_globals), name, len)) != NULL) { if (auto_global->armed) { + zend_mm_userinput_begin(); auto_global->armed = auto_global->auto_global_callback(auto_global->name); + zend_mm_userinput_end(); } return 1; } @@ -1986,7 +1988,9 @@ ZEND_API bool zend_is_auto_global(zend_string *name) /* {{{ */ if ((auto_global = zend_hash_find_ptr(CG(auto_globals), name)) != NULL) { if (auto_global->armed) { + zend_mm_userinput_begin(); auto_global->armed = auto_global->auto_global_callback(auto_global->name); + zend_mm_userinput_end(); } return 1; } @@ -2013,6 +2017,8 @@ ZEND_API void zend_activate_auto_globals(void) /* {{{ */ { zend_auto_global *auto_global; + ZEND_ASSERT(zend_mm_check_in_userinput()); + ZEND_HASH_MAP_FOREACH_PTR(CG(auto_globals), auto_global) { if (auto_global->jit) { auto_global->armed = 1; diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index f597df36e290c..90b1ca25ce07e 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -22,6 +22,7 @@ #include "main/php.h" #include "main/php_globals.h" #include "zend.h" +#include "zend_alloc.h" #include "zend_extensions.h" #include "zend_compile.h" #include "ZendAccelerator.h" @@ -4872,6 +4873,7 @@ static zend_result accel_finish_startup_preload(bool in_child) orig_error_reporting = EG(error_reporting); EG(error_reporting) = 0; + zend_mm_userinput_begin(); const zend_result rc = php_request_startup(); EG(error_reporting) = orig_error_reporting; diff --git a/ext/spl/tests/SplObjectStorage/gh14639.phpt b/ext/spl/tests/SplObjectStorage/gh14639.phpt index 41260855b2523..6aa667890efd4 100644 --- a/ext/spl/tests/SplObjectStorage/gh14639.phpt +++ b/ext/spl/tests/SplObjectStorage/gh14639.phpt @@ -1,7 +1,7 @@ --TEST-- GH-14639 (Member access within null pointer in ext/spl/spl_observer.c) --INI-- -memory_limit=2M +memory_limit=4M --SKIPIF-- 0) { fputs($fp, str_pad('', min($chunk,$size))); diff --git a/ext/zend_test/tests/observer_error_01.phpt b/ext/zend_test/tests/observer_error_01.phpt index d20a23b00b293..36a8f6e8f7879 100644 --- a/ext/zend_test/tests/observer_error_01.phpt +++ b/ext/zend_test/tests/observer_error_01.phpt @@ -7,7 +7,7 @@ zend_test.observer.enabled=1 zend_test.observer.show_output=1 zend_test.observer.observe_all=1 zend_test.observer.show_return_value=1 -memory_limit=2M +memory_limit=4M --SKIPIF-- -Fatal error: Allowed memory size of 2097152 bytes exhausted%s(tried to allocate %d bytes) in %s on line %d +Fatal error: Allowed memory size of 4194304 bytes exhausted%s(tried to allocate %d bytes) in %s on line %d diff --git a/main/main.c b/main/main.c index eb842474240fd..7740fa36dec58 100644 --- a/main/main.c +++ b/main/main.c @@ -1840,6 +1840,8 @@ zend_result php_request_startup(void) { zend_result retval = SUCCESS; + ZEND_ASSERT(zend_mm_check_in_userinput()); + zend_interned_strings_activate(); #ifdef HAVE_DTRACE @@ -1915,6 +1917,8 @@ zend_result php_request_startup(void) SG(sapi_started) = 1; + zend_mm_userinput_end(); + return retval; } /* }}} */ diff --git a/tests/lang/bug45392.phpt b/tests/lang/bug45392.phpt index cfb09437bd2cc..6df766fdc7715 100644 --- a/tests/lang/bug45392.phpt +++ b/tests/lang/bug45392.phpt @@ -12,10 +12,10 @@ if (getenv("USE_ZEND_ALLOC") === "0") { --FILE-- Date: Wed, 27 Aug 2025 11:45:35 +0200 Subject: [PATCH 2/8] Fix zend_mm_chunk.reserve --- Zend/zend_alloc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 96ef624f17ead..0cd1b2826277a 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -391,7 +391,11 @@ struct _zend_mm_chunk { uint32_t free_tail; /* number of free pages at the end of chunk */ uint32_t num; /* align heap_slot to cache line boundary (assumed to be 64 bytes) */ +#if ZEND_MM_HEAP_PROTECTION char reserve[64 - (sizeof(void*) * 4 + sizeof(uint32_t) * 3)]; +#else + char reserve[64 - (sizeof(void*) * 3 + sizeof(uint32_t) * 3)]; +#endif zend_mm_heap heap_slot; /* used only in main chunk */ #if ZEND_MM_HEAP_PROTECTION zend_mm_zone *zone; @@ -400,6 +404,8 @@ struct _zend_mm_chunk { zend_mm_page_info map[ZEND_MM_PAGES]; /* 2 KB = 512 * 4 */ }; +ZEND_STATIC_ASSERT(ZEND_MM_ALIGNED_OFFSET(XtOffsetOf(zend_mm_chunk, heap_slot), 64) == 0, + "zend_mm_chunk.heap_slot should be aligned to cache line boundary"); ZEND_STATIC_ASSERT(sizeof(zend_mm_chunk) <= ZEND_MM_FIRST_PAGE * ZEND_MM_PAGE_SIZE, ""); struct _zend_mm_page { From 4ac02f7c84241ed74840d5fd2f11e2c60d817620 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 27 Aug 2025 11:56:38 +0200 Subject: [PATCH 3/8] Fix non-ZEND_MM_HEAP_PROTECTION build --- Zend/zend_alloc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 0cd1b2826277a..45cd6c622406b 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2130,6 +2130,7 @@ static void zend_mm_init_key(zend_mm_heap *heap) ZEND_API void zend_mm_refresh_key_child(zend_mm_heap *heap) { +#if ZEND_MM_HEAP_PROTECTION uintptr_t old_key = heap->shadow_key; zend_mm_init_key(heap); @@ -2150,6 +2151,7 @@ ZEND_API void zend_mm_refresh_key_child(zend_mm_heap *heap) slot = next; } } +#endif #if ZEND_DEBUG heap->pid = getpid(); From e6853068c7905a3a210ddeed69a1390ec8205b8f Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 27 Aug 2025 11:56:49 +0200 Subject: [PATCH 4/8] Adjust iz_zend_ptr() --- Zend/zend_alloc.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 45cd6c622406b..37daffb6c3e3d 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2810,8 +2810,12 @@ ZEND_API bool is_zend_ptr(const void *ptr) } #endif - if (AG(mm_heap)->main_chunk) { - zend_mm_chunk *chunk = AG(mm_heap)->main_chunk; + zend_mm_zone *zone; + ZEND_MM_ZONE_FOREACH(AG(mm_heap), zone) { + zend_mm_chunk *chunk = zone->chunks; + if (chunk == NULL) { + continue; + } do { if (ptr >= (void*)chunk @@ -2819,8 +2823,8 @@ ZEND_API bool is_zend_ptr(const void *ptr) return 1; } chunk = chunk->next; - } while (chunk != AG(mm_heap)->main_chunk); - } + } while (chunk != zone->chunks); + } ZEND_MM_ZONE_FOREACH_END(); zend_mm_huge_list *block = AG(mm_heap)->huge_list; while (block) { From f600414b6c99023732e16eb85e8b5666d7c3fe34 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 27 Aug 2025 12:10:22 +0200 Subject: [PATCH 5/8] Fix new tests after rebase --- ext/gd/tests/gh17772.phpt | 2 +- ext/opcache/tests/jit/gh17727.phpt | 4 ++-- .../ob_start_callback_bad_return/handler_false_removed.phpt | 2 +- .../handler_is_stringable_removed.phpt | 2 +- .../handler_non_stringable_removed.phpt | 2 +- .../ob_start_callback_bad_return/handler_true_removed.phpt | 2 +- .../ob_start_callback_bad_return/handler_zero_removed.phpt | 2 +- .../handler_false_removed.phpt | 2 +- .../handler_is_stringable_removed.phpt | 2 +- .../handler_non_stringable_removed.phpt | 2 +- .../handler_true_removed.phpt | 2 +- .../handler_zero_removed.phpt | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ext/gd/tests/gh17772.phpt b/ext/gd/tests/gh17772.phpt index 6252a13341f41..e13466e4539de 100644 --- a/ext/gd/tests/gh17772.phpt +++ b/ext/gd/tests/gh17772.phpt @@ -3,7 +3,7 @@ GH-17772 (imagepalettetotruecolor segfault on image deallocation) --EXTENSIONS-- gd --INI-- -memory_limit=2M +memory_limit=4M --CREDITS-- YuanchengJiang --SKIPIF-- diff --git a/ext/opcache/tests/jit/gh17727.phpt b/ext/opcache/tests/jit/gh17727.phpt index 425315f215a6e..ff676acb229ba 100644 --- a/ext/opcache/tests/jit/gh17727.phpt +++ b/ext/opcache/tests/jit/gh17727.phpt @@ -9,7 +9,7 @@ if (getenv("SKIP_SLOW_TESTS")) die("skip slow test"); --INI-- opcache.jit=1254 fatal_error_backtraces=1 -memory_limit=2M +memory_limit=4M --CREDITS-- arnaud-lb YuanchengJiang @@ -26,6 +26,6 @@ class DestructableObject $_ = new DestructableObject(); ?> --EXPECTF-- -Fatal error: Allowed memory size of 2097152 bytes exhausted %s +Fatal error: Allowed memory size of 4194304 bytes exhausted %s Stack trace: %A diff --git a/tests/output/ob_start_callback_bad_return/handler_false_removed.phpt b/tests/output/ob_start_callback_bad_return/handler_false_removed.phpt index 32702a58fcc14..a5b98c0d1be3f 100644 --- a/tests/output/ob_start_callback_bad_return/handler_false_removed.phpt +++ b/tests/output/ob_start_callback_bad_return/handler_false_removed.phpt @@ -1,7 +1,7 @@ --TEST-- ob_start(): Check behaviour with deprecation when OOM triggers handler removal (handler returns false) --INI-- -memory_limit=2M +memory_limit=4M --FILE-- Date: Wed, 27 Aug 2025 12:47:52 +0200 Subject: [PATCH 6/8] Revise zend_mm_check_in_userinput() --- Zend/zend_alloc.c | 6 ++---- Zend/zend_alloc.h | 2 +- Zend/zend_compile.c | 4 +++- main/main.c | 4 +++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 37daffb6c3e3d..02cbea6d12940 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2856,12 +2856,10 @@ ZEND_API void zend_mm_userinput_end(void) #endif } -ZEND_API bool zend_mm_check_in_userinput(void) +ZEND_API void zend_mm_check_in_userinput(void) { #if ZEND_MM_HEAP_PROTECTION - return AG(use_userinput_zone); -#else - return true; + ZEND_ASSERT(AG(use_userinput_zone)); #endif } diff --git a/Zend/zend_alloc.h b/Zend/zend_alloc.h index c480d62dc765d..82b268e96aa02 100644 --- a/Zend/zend_alloc.h +++ b/Zend/zend_alloc.h @@ -225,7 +225,7 @@ ZEND_API bool is_zend_mm(void); ZEND_API bool is_zend_ptr(const void *ptr); ZEND_API void zend_mm_userinput_begin(void); ZEND_API void zend_mm_userinput_end(void); -ZEND_API bool zend_mm_check_in_userinput(void); +ZEND_API void zend_mm_check_in_userinput(void); ZEND_API size_t zend_memory_usage(bool real_usage); ZEND_API size_t zend_memory_peak_usage(bool real_usage); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d7fed5f687b80..3277d1a6062a6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2017,7 +2017,9 @@ ZEND_API void zend_activate_auto_globals(void) /* {{{ */ { zend_auto_global *auto_global; - ZEND_ASSERT(zend_mm_check_in_userinput()); +#if ZEND_DEBUG + zend_mm_check_in_userinput(); +#endif ZEND_HASH_MAP_FOREACH_PTR(CG(auto_globals), auto_global) { if (auto_global->jit) { diff --git a/main/main.c b/main/main.c index 7740fa36dec58..8581717adba91 100644 --- a/main/main.c +++ b/main/main.c @@ -1840,7 +1840,9 @@ zend_result php_request_startup(void) { zend_result retval = SUCCESS; - ZEND_ASSERT(zend_mm_check_in_userinput()); +#if ZEND_DEBUG + zend_mm_check_in_userinput(); +#endif zend_interned_strings_activate(); From 5e7fb2c3377b94b237c96f1d405109ad22e0955c Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 27 Aug 2025 12:48:42 +0200 Subject: [PATCH 7/8] Allow to opt-out of userinput isolation --- Zend/zend_alloc.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 02cbea6d12940..9c66fad5f1415 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2770,7 +2770,8 @@ ZEND_API size_t ZEND_FASTCALL _zend_mm_block_size(zend_mm_heap *heap, void *ptr typedef struct _zend_alloc_globals { zend_mm_heap *mm_heap; - uint32_t use_userinput_zone; + uint32_t userinput_zone_activated; /* Whether the userinput zone is currently active */ + bool enable_userinput_isolation; /* Whether to switch to the userinput zone before handling user inputs */ } zend_alloc_globals; #ifdef ZTS @@ -2841,17 +2842,21 @@ ZEND_API bool is_zend_ptr(const void *ptr) ZEND_API void zend_mm_userinput_begin(void) { #if ZEND_MM_HEAP_PROTECTION - AG(use_userinput_zone)++; - AG(mm_heap)->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_USERINPUT); + if (AG(enable_userinput_isolation)) { + AG(userinput_zone_activated)++; + AG(mm_heap)->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_USERINPUT); + } #endif } ZEND_API void zend_mm_userinput_end(void) { #if ZEND_MM_HEAP_PROTECTION - AG(use_userinput_zone)--; - if (!AG(use_userinput_zone)) { - AG(mm_heap)->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_DEFAULT); + if (AG(enable_userinput_isolation)) { + AG(userinput_zone_activated)--; + if (!AG(userinput_zone_activated)) { + AG(mm_heap)->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_DEFAULT); + } } #endif } @@ -2859,7 +2864,12 @@ ZEND_API void zend_mm_userinput_end(void) ZEND_API void zend_mm_check_in_userinput(void) { #if ZEND_MM_HEAP_PROTECTION - ZEND_ASSERT(AG(use_userinput_zone)); + if (AG(enable_userinput_isolation)) { + ZEND_ASSERT(AG(userinput_zone_activated)); + ZEND_ASSERT(AG(mm_heap)->zone_free_slot == ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_USERINPUT)); + } else { + ZEND_ASSERT(AG(mm_heap)->zone_free_slot == ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_DEFAULT)); + } #endif } @@ -3160,9 +3170,11 @@ ZEND_API void shutdown_memory_manager(bool silent, bool full_shutdown) zend_mm_shutdown(AG(mm_heap), full_shutdown, silent); if (!full_shutdown) { - ZEND_ASSERT(AG(use_userinput_zone) == 0 || silent); - AG(use_userinput_zone) = 0; - zend_mm_userinput_begin(); + if (AG(enable_userinput_isolation)) { + ZEND_ASSERT(AG(userinput_zone_activated) == 0 || silent); + AG(userinput_zone_activated) = 0; + zend_mm_userinput_begin(); + } } } @@ -3477,7 +3489,9 @@ static void alloc_globals_ctor(zend_alloc_globals *alloc_globals) { char *tmp; - alloc_globals->use_userinput_zone = 0; + tmp = getenv("ZEND_MM_USERINPUT_ISOLATION"); + alloc_globals->enable_userinput_isolation = !(tmp && !ZEND_ATOL(tmp)); + alloc_globals->userinput_zone_activated = 0; #if ZEND_MM_CUSTOM tmp = getenv("USE_ZEND_ALLOC"); From 9a98904fe965528b5e849c6973dca6fe1fd59fb6 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 27 Aug 2025 14:04:30 +0200 Subject: [PATCH 8/8] Adjust zend_mm_refresh_key_child() --- Zend/zend_alloc.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 9c66fad5f1415..4fc9111c77345 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2136,19 +2136,21 @@ ZEND_API void zend_mm_refresh_key_child(zend_mm_heap *heap) zend_mm_init_key(heap); /* Update shadow pointers with new key */ - for (int i = 0; i < ZEND_MM_BINS; i++) { - zend_mm_free_slot *slot = heap->free_slot[i]; - if (!slot) { - continue; - } - zend_mm_free_slot *next; - while ((next = slot->next_free_slot)) { - zend_mm_free_slot *shadow = ZEND_MM_FREE_SLOT_PTR_SHADOW(slot, i); - if (UNEXPECTED(next != zend_mm_decode_free_slot_key(old_key, shadow))) { - zend_mm_panic("zend_mm_heap corrupted"); + for (int i = 0; i < ZEND_MM_ZONES; i++) { + for (int j = 0; j < ZEND_MM_BINS; j++) { + zend_mm_free_slot *slot = ZEND_MM_ZONE_FREE_SLOT(heap, i)[j]; + if (!slot) { + continue; + } + zend_mm_free_slot *next; + while ((next = slot->next_free_slot)) { + zend_mm_free_slot *shadow = ZEND_MM_FREE_SLOT_PTR_SHADOW(slot, j); + if (UNEXPECTED(next != zend_mm_decode_free_slot_key(old_key, shadow))) { + zend_mm_panic("zend_mm_heap corrupted"); + } + zend_mm_set_next_free_slot(heap, j, slot, next); + slot = next; } - zend_mm_set_next_free_slot(heap, i, slot, next); - slot = next; } } #endif