From 4446dc9749c84f5f05264c9327e73b132dd766ad Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sat, 19 Sep 2026 18:39:05 +0200 Subject: [PATCH] Bound the LRUN page count in the zend_mm_gc() page scan (Zend/zend_alloc.c) The final loop of zend_mm_gc() walks the pages of each chunk, and dispatches on the page map entry to decide how far to advance: if (info & ZEND_MM_IS_SRUN) { ... i += bin_pages[bin_num]; } else /* if (info & ZEND_MM_IS_LRUN) */ { i += ZEND_MM_LRUN_PAGES(info); } This is the same "reach the large-run case by elimination" pattern that 0c87849da5a promoted to a real check in zend_mm_free_heap(), zend_mm_size() and zend_mm_realloc_heap(), except this one was left behind. Promote the comment to a ZEND_MM_CHECK() that requires both the ZEND_MM_IS_LRUN bit and a non-zero page count before advancing, which guarantees forward progress and only fires on a corrupted heap. It costs a test and a branch. --- Zend/zend_alloc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 4fc8926c0fcc..761344e5d60a 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2311,8 +2311,13 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) 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); + } else { + /* An allocated page is either an SRUN or an LRUN; a zeroed + * or corrupted map entry would otherwise stall the scan, as + * ZEND_MM_LRUN_PAGES(0) is 0 and would not advance i. */ + uint32_t pages_count = ZEND_MM_LRUN_PAGES(info); + ZEND_MM_CHECK((info & ZEND_MM_IS_LRUN) && pages_count != 0, "zend_mm_heap corrupted"); + i += pages_count; } } else { i++;