[php-src] PR #24542: Fix unmapping of an unrelated range when the aligned chunk mmap fails

From: Date: Thu, 22 Oct 2026 14:23:24 +0000
Subject: [php-src] PR #24542: Fix unmapping of an unrelated range when the aligned chunk mmap fails
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-39706@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/24542 Author: Marti2203 If the first mmap() in zend_mm_chunk_alloc_int() is not suitably aligned, the mapping is released and a larger one is requested so that it can be trimmed to alignment. Nothing checks whether that second mmap() succeeded. On POSIX, when it fails, the code carries on with ptr == NULL: ```c zend_mm_munmap(ptr, size); ptr = zend_mm_mmap(size + alignment - REAL_PAGE_SIZE); /* NULL on failure */ ... offset = ZEND_MM_ALIGNED_OFFSET(ptr, alignment); /* 0 */ ... if (alignment > REAL_PAGE_SIZE) { zend_mm_munmap((char*)ptr + size, alignment - REAL_PAGE_SIZE); } return ptr; ``` That is a munmap() of the absolute range [size, size + 2 MiB - 4 KiB), followed by an ordinary "Out of memory". In a PIE binary nothing is usually mapped there, so the stray munmap() does no harm. In a non-PIE binary (for example a plain ./configure build with clang 12 on Ubuntu 20.04, which does not default to PIE) that range can be PHP's own text, data or heap, and the process crashes with SIGSEGV instead of reporting "Out of memory". The second mmap() can fail under an address-space limit (RLIMIT_AS, ulimit -v), with strict overcommit, or when the address space is exhausted. The Windows path already has a NULL check for the later retry (added for GH-9650). The first retry has none on either platform. ### Reproducer Non-PIE release build (./configure --disable-all, clang), text segment at 0x600000: ```php <?php $s = str_repeat('A', 0x600000 - 4096); // huge block of exactly 0x600000 bytes echo "allocation succeeded\n"; ``` ``` for v in 25000 25600 26000 27096 27352 28000; do (ulimit -v $v; php -n -d memory_limit=-1 chunk-unmap.php) done ``` | ulimit -v (KiB) | before | after | |---|---|---| | 25000 | Out of memory | Out of memory | | 25600 - 27352 | Segmentation fault | Out of memory | | 28000 | allocation succeeded | allocation succeeded | In the crashing window the first mmap() succeeds and the aligned retry fails. gdb shows the crash in zend_mm_munmap() called from zend_mm_chunk_alloc_int(), and the r-x mapping at 0x600000 is gone from info proc mappings. ### Fix Return NULL when the retry mmap() fails. The check sits before the #ifdef _WIN32 split, so it also covers the first retry on Windows, which until now would have passed NULL on to zend_mm_munmap() and zend_mm_mmap_fixed(). ### Test Zend/tests/zend_mm_chunk_alloc_mmap_failure.phpt sets up this situation from inside the script. It reads the start of the executable mapping from /proc/self/maps and uses it as the allocation size. It then sets RLIMIT_AS (via posix_setrlimit()) to the current VmSize plus that size plus 1 MiB, which leaves room for the first mmap() but not for the aligned retry. Finally it allocates the block. Either a clean "Out of memory" or a successful allocation (if the first mapping happens to be aligned) is accepted. The test needs posix, Linux and 64-bit. It is skipped for PIE builds (where the bug cannot be observed) and with USE_ZEND_ALLOC=0. - Without the fix (non-PIE build): fails with Termsig=11, deterministically in my runs. - With the fix: passes (5/5 runs). - Zend/tests: 4918 passed, 143 skipped, 1 expected fail, 0 failed. The Windows part of the change is not tested; I have no Windows build environment. This issue was found by an LLM-Enhanced Static Analysis Engine and verified manually. 🤖 Generated with [Claude Code](https://claude.com/claude-code)

« previous php.git-pulls (#39706) next »