From 01735b145e99cd90ddbed4019d9425d1a3c68d18 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/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/gh12073.phpt | 2 +- Zend/tests/new_oom.inc | 2 +- Zend/tests/object_gc_in_shutdown.phpt | 4 +- Zend/zend_alloc.c | 453 ++++++++++++------ Zend/zend_alloc.h | 3 + Zend/zend_compile.c | 6 + ext/opcache/ZendAccelerator.c | 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 +- 17 files changed, 346 insertions(+), 160 deletions(-) diff --git a/Zend/tests/bug70258.phpt b/Zend/tests/bug70258.phpt index 40915a286ef9e..f601e0d9f2898 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/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_SPRAYING_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_SPRAYING_PROTECTION */ + #if UINTPTR_MAX == UINT64_MAX # define BSWAPPTR(u) ZEND_BYTES_SWAP64(u) #else @@ -227,10 +274,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 +327,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_SPRAYING_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 +352,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); @@ -313,6 +369,9 @@ struct _zend_mm_heap { struct _zend_mm_chunk { zend_mm_heap *heap; +#if ZEND_MM_HEAP_SPRAYING_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 */ @@ -320,10 +379,15 @@ struct _zend_mm_chunk { uint32_t num; char reserve[64 - (sizeof(void*) * 3 + sizeof(uint32_t) * 3)]; zend_mm_heap heap_slot; /* used only in main chunk */ +#if ZEND_MM_HEAP_SPRAYING_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]; }; @@ -873,13 +937,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_SPRAYING_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; @@ -914,10 +988,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; @@ -1033,7 +1112,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--; @@ -1087,7 +1166,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; @@ -1105,8 +1184,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; } @@ -1149,8 +1228,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 @@ -1367,7 +1451,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 @@ -1394,6 +1478,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 @@ -1405,17 +1490,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; @@ -1432,8 +1518,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; } /********/ @@ -1529,7 +1615,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); @@ -1719,7 +1805,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; @@ -1734,7 +1820,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); @@ -2030,6 +2116,10 @@ static zend_mm_heap *zend_mm_init(void) } heap = &chunk->heap_slot; chunk->heap = heap; +#if ZEND_MM_HEAP_SPRAYING_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; @@ -2039,6 +2129,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_SPRAYING_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_SPRAYING_PROTECTION + heap->zones[1].chunks = NULL; +#endif heap->chunks_count = 1; heap->peak_chunks_count = 1; heap->cached_chunks_count = 0; @@ -2076,9 +2173,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) { @@ -2090,9 +2188,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"); @@ -2107,13 +2206,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) { @@ -2136,61 +2235,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) { - zend_mm_chunk *next_chunk = chunk->next; + if (chunk->free_pages == ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE) { + 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; } @@ -2227,7 +2331,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; @@ -2254,7 +2358,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; } @@ -2287,6 +2391,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; @@ -2313,73 +2418,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); } @@ -2395,6 +2505,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) { @@ -2444,16 +2555,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 */ @@ -2467,6 +2587,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; @@ -2507,6 +2628,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_SPRAYING_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_SPRAYING_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); @@ -2568,6 +2699,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; + int use_input_zone; } zend_alloc_globals; #ifdef ZTS @@ -2579,6 +2711,10 @@ static size_t alloc_globals_offset; static zend_alloc_globals alloc_globals; #endif +#if ZEND_MM_HEAP_SPRAYING_PROTECTION +# define ZEND_MM_ZONE_INPUT 1 +#endif + ZEND_API bool is_zend_mm(void) { #if ZEND_MM_CUSTOM @@ -2627,6 +2763,33 @@ ZEND_API bool is_zend_ptr(const void *ptr) return 0; } +ZEND_API void zend_mm_input_begin(void) +{ +#if ZEND_MM_HEAP_SPRAYING_PROTECTION + AG(use_input_zone)++; + AG(mm_heap)->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_INPUT); +#endif +} + +ZEND_API void zend_mm_input_end(void) +{ +#if ZEND_MM_HEAP_SPRAYING_PROTECTION + AG(use_input_zone)--; + if (!AG(use_input_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_input(void) +{ +#if ZEND_MM_HEAP_SPRAYING_PROTECTION + return AG(use_input_zone); +#else + return true; +#endif +} + #if !ZEND_DEBUG && defined(HAVE_BUILTIN_CONSTANT_P) #undef _emalloc @@ -2685,7 +2848,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 @@ -2699,7 +2862,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 @@ -2922,6 +3085,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_input_zone) == 0 || silent); + AG(use_input_zone) = 0; + zend_mm_input_begin(); + } } static ZEND_COLD ZEND_NORETURN void zend_out_of_memory(void) @@ -3025,6 +3194,8 @@ static void alloc_globals_ctor(zend_alloc_globals *alloc_globals) { char *tmp; + alloc_globals->use_input_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 541989a2a13e0..b532f3da99cc0 100644 --- a/Zend/zend_alloc.h +++ b/Zend/zend_alloc.h @@ -222,6 +222,9 @@ ZEND_API void start_memory_manager(void); ZEND_API void shutdown_memory_manager(bool silent, bool full_shutdown); ZEND_API bool is_zend_mm(void); ZEND_API bool is_zend_ptr(const void *ptr); +ZEND_API void zend_mm_input_begin(void); +ZEND_API void zend_mm_input_end(void); +ZEND_API bool zend_mm_check_in_input(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 73bc661cd40e3..68b81e00b34eb 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1974,7 +1974,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_input_begin(); auto_global->armed = auto_global->auto_global_callback(auto_global->name); + zend_mm_input_end(); } return 1; } @@ -1988,7 +1990,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_input_begin(); auto_global->armed = auto_global->auto_global_callback(auto_global->name); + zend_mm_input_end(); } return 1; } @@ -2015,6 +2019,8 @@ ZEND_API void zend_activate_auto_globals(void) /* {{{ */ { zend_auto_global *auto_global; + ZEND_ASSERT(zend_mm_check_in_input()); + 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 406c539f44a27..b7c1f57eba36e 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" @@ -4640,6 +4641,7 @@ static zend_result accel_finish_startup_preload(bool in_child) orig_error_reporting = EG(error_reporting); EG(error_reporting) = 0; + zend_mm_input_begin(); const zend_result rc = php_request_startup(); EG(error_reporting) = orig_error_reporting; diff --git a/ext/standard/tests/streams/bug78902.phpt b/ext/standard/tests/streams/bug78902.phpt index c3ea8a52a1639..e8b66bcd04114 100644 --- a/ext/standard/tests/streams/bug78902.phpt +++ b/ext/standard/tests/streams/bug78902.phpt @@ -1,14 +1,14 @@ --TEST-- Bug #78902: Memory leak when using stream_filter_append --INI-- -memory_limit=2M +memory_limit=4M --FILE-- 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 0b38f303c58fc..b913ceb4f0135 100644 --- a/main/main.c +++ b/main/main.c @@ -1802,6 +1802,8 @@ zend_result php_request_startup(void) { zend_result retval = SUCCESS; + ZEND_ASSERT(zend_mm_check_in_input()); + zend_interned_strings_activate(); #ifdef HAVE_DTRACE @@ -1877,6 +1879,8 @@ zend_result php_request_startup(void) SG(sapi_started) = 1; + zend_mm_input_end(); + return retval; } /* }}} */ diff --git a/tests/lang/bug45392.phpt b/tests/lang/bug45392.phpt index f470f2ccddbd7..44bc041df2b0f 100644 --- a/tests/lang/bug45392.phpt +++ b/tests/lang/bug45392.phpt @@ -17,10 +17,10 @@ if (PHP_OS_FAMILY === "Windows" && PHP_INT_SIZE == 8 && $tracing) { --FILE-- Date: Mon, 4 Nov 2024 18:21:16 +0100 Subject: [PATCH 2/8] Use a single knob --- Zend/zend_alloc.c | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index c66d4418ec1cb..0bbb7f0757717 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -146,12 +146,11 @@ static size_t _real_page_size = ZEND_MM_PAGE_SIZE; # define ZEND_MM_ERROR 1 /* report system errors */ #endif #ifndef ZEND_MM_HEAP_PROTECTION -# define ZEND_MM_HEAP_PROTECTION 1 /* protect heap against corruptions */ -#endif -#ifndef ZEND_MM_HEAP_SPRAYING_PROTECTION -# define ZEND_MM_HEAP_SPRAYING_PROTECTION 1 /* protect against remote heap - spraying or heap feng chui via - environment / user input */ +/* Protect heap against: + * - Freelist pointer corruption + * - Heap spraying and heap feng shui via environment / user input + */ +# define ZEND_MM_HEAP_PROTECTION 1 #endif #if ZEND_MM_HEAP_PROTECTION @@ -226,7 +225,7 @@ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */ #define ZEND_MM_FREE_SLOT_LEN (ZEND_MM_ZONE_LEN * ZEND_MM_ZONES) #define ZEND_MM_ZONE_DEFAULT 0 -#if ZEND_MM_HEAP_SPRAYING_PROTECTION +#if ZEND_MM_HEAP_PROTECTION # define ZEND_MM_ZONES 2 @@ -247,7 +246,7 @@ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */ # 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_SPRAYING_PROTECTION */ +#else /* ZEND_MM_HEAP_PROTECTION */ # define ZEND_MM_ZONES 1 @@ -262,7 +261,7 @@ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */ # 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_SPRAYING_PROTECTION */ +#endif /* ZEND_MM_HEAP_PROTECTION */ #if UINTPTR_MAX == UINT64_MAX # define BSWAPPTR(u) ZEND_BYTES_SWAP64(u) @@ -327,7 +326,7 @@ struct _zend_mm_heap { size_t peak; /* peak memory usage */ #endif uintptr_t shadow_key; /* free slot shadow ptr xor key */ -#if ZEND_MM_HEAP_SPRAYING_PROTECTION +#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 */ @@ -369,7 +368,7 @@ struct _zend_mm_heap { struct _zend_mm_chunk { zend_mm_heap *heap; -#if ZEND_MM_HEAP_SPRAYING_PROTECTION +#if ZEND_MM_HEAP_PROTECTION zend_mm_free_slot **zone_free_slot; #endif zend_mm_chunk *next; @@ -379,7 +378,7 @@ struct _zend_mm_chunk { uint32_t num; char reserve[64 - (sizeof(void*) * 3 + sizeof(uint32_t) * 3)]; zend_mm_heap heap_slot; /* used only in main chunk */ -#if ZEND_MM_HEAP_SPRAYING_PROTECTION +#if ZEND_MM_HEAP_PROTECTION zend_mm_zone *zone; #endif zend_mm_page_map free_map; /* 512 bits or 64 bytes */ @@ -950,7 +949,7 @@ static zend_always_inline void zend_mm_chunk_init(zend_mm_heap *heap, zend_mm_zo chunk->prev->next = chunk; chunk->next->prev = chunk; } -#if ZEND_MM_HEAP_SPRAYING_PROTECTION +#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 @@ -2116,7 +2115,7 @@ static zend_mm_heap *zend_mm_init(void) } heap = &chunk->heap_slot; chunk->heap = heap; -#if ZEND_MM_HEAP_SPRAYING_PROTECTION +#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 @@ -2129,11 +2128,11 @@ 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_SPRAYING_PROTECTION +#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_SPRAYING_PROTECTION +#if ZEND_MM_HEAP_PROTECTION heap->zones[1].chunks = NULL; #endif heap->chunks_count = 1; @@ -2628,11 +2627,11 @@ 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_SPRAYING_PROTECTION +#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_SPRAYING_PROTECTION +#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"); @@ -2711,7 +2710,7 @@ static size_t alloc_globals_offset; static zend_alloc_globals alloc_globals; #endif -#if ZEND_MM_HEAP_SPRAYING_PROTECTION +#if ZEND_MM_HEAP_PROTECTION # define ZEND_MM_ZONE_INPUT 1 #endif @@ -2765,7 +2764,7 @@ ZEND_API bool is_zend_ptr(const void *ptr) ZEND_API void zend_mm_input_begin(void) { -#if ZEND_MM_HEAP_SPRAYING_PROTECTION +#if ZEND_MM_HEAP_PROTECTION AG(use_input_zone)++; AG(mm_heap)->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_INPUT); #endif @@ -2773,7 +2772,7 @@ ZEND_API void zend_mm_input_begin(void) ZEND_API void zend_mm_input_end(void) { -#if ZEND_MM_HEAP_SPRAYING_PROTECTION +#if ZEND_MM_HEAP_PROTECTION AG(use_input_zone)--; if (!AG(use_input_zone)) { AG(mm_heap)->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_DEFAULT); @@ -2783,7 +2782,7 @@ ZEND_API void zend_mm_input_end(void) ZEND_API bool zend_mm_check_in_input(void) { -#if ZEND_MM_HEAP_SPRAYING_PROTECTION +#if ZEND_MM_HEAP_PROTECTION return AG(use_input_zone); #else return true; From b9f761d9e2ddc33af99cf6426ff59e0eb25a3c37 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 4 Nov 2024 18:22:20 +0100 Subject: [PATCH 3/8] Review --- Zend/zend_alloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 0bbb7f0757717..135394abbae96 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2698,7 +2698,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; - int use_input_zone; + uint32_t use_input_zone; } zend_alloc_globals; #ifdef ZTS From 35a17c9ede18860c34fa44779274ee075cd8a2de Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 4 Nov 2024 18:28:38 +0100 Subject: [PATCH 4/8] Naming: input -> userinput --- Zend/zend_alloc.c | 28 ++++++++++++++-------------- Zend/zend_alloc.h | 6 +++--- Zend/zend_compile.c | 10 +++++----- ext/opcache/ZendAccelerator.c | 2 +- main/main.c | 4 ++-- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 135394abbae96..7d4d484c59b6f 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2698,7 +2698,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_input_zone; + uint32_t use_userinput_zone; } zend_alloc_globals; #ifdef ZTS @@ -2711,7 +2711,7 @@ static zend_alloc_globals alloc_globals; #endif #if ZEND_MM_HEAP_PROTECTION -# define ZEND_MM_ZONE_INPUT 1 +# define ZEND_MM_ZONE_USERINPUT 1 #endif ZEND_API bool is_zend_mm(void) @@ -2762,28 +2762,28 @@ ZEND_API bool is_zend_ptr(const void *ptr) return 0; } -ZEND_API void zend_mm_input_begin(void) +ZEND_API void zend_mm_userinput_begin(void) { #if ZEND_MM_HEAP_PROTECTION - AG(use_input_zone)++; - AG(mm_heap)->zone_free_slot = ZEND_MM_ZONE_FREE_SLOT(AG(mm_heap), ZEND_MM_ZONE_INPUT); + 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_input_end(void) +ZEND_API void zend_mm_userinput_end(void) { #if ZEND_MM_HEAP_PROTECTION - AG(use_input_zone)--; - if (!AG(use_input_zone)) { + 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_input(void) +ZEND_API bool zend_mm_check_in_userinput(void) { #if ZEND_MM_HEAP_PROTECTION - return AG(use_input_zone); + return AG(use_userinput_zone); #else return true; #endif @@ -3086,9 +3086,9 @@ 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_input_zone) == 0 || silent); - AG(use_input_zone) = 0; - zend_mm_input_begin(); + ZEND_ASSERT(AG(use_userinput_zone) == 0 || silent); + AG(use_userinput_zone) = 0; + zend_mm_userinput_begin(); } } @@ -3193,7 +3193,7 @@ static void alloc_globals_ctor(zend_alloc_globals *alloc_globals) { char *tmp; - alloc_globals->use_input_zone = 0; + alloc_globals->use_userinput_zone = 0; #if ZEND_MM_CUSTOM tmp = getenv("USE_ZEND_ALLOC"); diff --git a/Zend/zend_alloc.h b/Zend/zend_alloc.h index b532f3da99cc0..7f7d0e95e621e 100644 --- a/Zend/zend_alloc.h +++ b/Zend/zend_alloc.h @@ -222,9 +222,9 @@ ZEND_API void start_memory_manager(void); ZEND_API void shutdown_memory_manager(bool silent, bool full_shutdown); ZEND_API bool is_zend_mm(void); ZEND_API bool is_zend_ptr(const void *ptr); -ZEND_API void zend_mm_input_begin(void); -ZEND_API void zend_mm_input_end(void); -ZEND_API bool zend_mm_check_in_input(void); +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 68b81e00b34eb..3d2395d40ebb6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1974,9 +1974,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_input_begin(); + zend_mm_userinput_begin(); auto_global->armed = auto_global->auto_global_callback(auto_global->name); - zend_mm_input_end(); + zend_mm_userinput_end(); } return 1; } @@ -1990,9 +1990,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_input_begin(); + zend_mm_userinput_begin(); auto_global->armed = auto_global->auto_global_callback(auto_global->name); - zend_mm_input_end(); + zend_mm_userinput_end(); } return 1; } @@ -2019,7 +2019,7 @@ ZEND_API void zend_activate_auto_globals(void) /* {{{ */ { zend_auto_global *auto_global; - ZEND_ASSERT(zend_mm_check_in_input()); + ZEND_ASSERT(zend_mm_check_in_userinput()); ZEND_HASH_MAP_FOREACH_PTR(CG(auto_globals), auto_global) { if (auto_global->jit) { diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index b7c1f57eba36e..85c6dbf1ee3be 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4641,7 +4641,7 @@ static zend_result accel_finish_startup_preload(bool in_child) orig_error_reporting = EG(error_reporting); EG(error_reporting) = 0; - zend_mm_input_begin(); + zend_mm_userinput_begin(); const zend_result rc = php_request_startup(); EG(error_reporting) = orig_error_reporting; diff --git a/main/main.c b/main/main.c index b913ceb4f0135..cda35ccbe2bce 100644 --- a/main/main.c +++ b/main/main.c @@ -1802,7 +1802,7 @@ zend_result php_request_startup(void) { zend_result retval = SUCCESS; - ZEND_ASSERT(zend_mm_check_in_input()); + ZEND_ASSERT(zend_mm_check_in_userinput()); zend_interned_strings_activate(); @@ -1879,7 +1879,7 @@ zend_result php_request_startup(void) SG(sapi_started) = 1; - zend_mm_input_end(); + zend_mm_userinput_end(); return retval; } From b602da9522bee25ecb745d98b83751cab876865a Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 4 Nov 2024 18:43:31 +0100 Subject: [PATCH 5/8] WS --- Zend/zend_alloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 7d4d484c59b6f..680cff34d5b25 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -233,7 +233,7 @@ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */ zend_mm_heap *__heap = (_heap); \ zend_mm_zone *__zone = &__heap->zones[0]; \ zend_mm_zone *__end = &__heap->zones[ZEND_MM_ZONES]; \ - for (; __zone!= __end; __zone++) { \ + for (; __zone != __end; __zone++) { \ _zone = __zone; # define ZEND_MM_ZONE_FOREACH_END() \ From e4e07ea42f7f3f408416b8d47f10f6844150da30 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 4 Nov 2024 18:47:09 +0100 Subject: [PATCH 6/8] Fix test --- ext/spl/tests/gh14639.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/spl/tests/gh14639.phpt b/ext/spl/tests/gh14639.phpt index 520ee20839c28..b1a87851255ef 100644 --- a/ext/spl/tests/gh14639.phpt +++ b/ext/spl/tests/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-- Date: Thu, 7 Nov 2024 15:00:37 +0100 Subject: [PATCH 7/8] Commit --- Zend/zend_alloc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 680cff34d5b25..eca06753d7473 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -227,6 +227,8 @@ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */ #if ZEND_MM_HEAP_PROTECTION +/* How may zones to use. Each zone has a separate freelist and chunks, so that + * allocation from a zone does not affect the layout of other zones. */ # define ZEND_MM_ZONES 2 # define ZEND_MM_ZONE_FOREACH(_heap, _zone) do { \ From ff21aab0e115cedeab7d20ffa466f5edd561e62b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 11 Nov 2024 13:18:27 +0100 Subject: [PATCH 8/8] Fix heap_slot alignment --- Zend/zend_alloc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index eca06753d7473..4b05c7a9e60e6 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -378,7 +378,8 @@ struct _zend_mm_chunk { 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;