Fix unmapping of an unrelated range when the aligned chunk mmap fails (#24542)

When the first mmap() in zend_mm_chunk_alloc_int() is not suitably
aligned, it is released and a larger mapping is requested. If that
second mmap() failed, the POSIX code carried on with ptr == NULL and
called munmap() on [size, size + alignment - page size), an absolute
low address range. In a non-PIE binary this range can contain the
executable itself, so the process crashed instead of reporting
"Out of memory". Return NULL when the retry fails.
This commit is contained in:
Martin Mirchev authored and GitHub committed 2026-10-26 17:30:51 +01:00
1 parent 8934441b85
commit cbda101fe3
3 files changed
+38 -1

No files matched your search

+3 -1
View File
@@ -30,7 +30,9 @@ PHP NEWS
for grouping. (Klaas Skelte van der Werf)
. Fixed bug GH-24265 (Unwind/GracefulExit discarded in
zend_fiber_execute()). (David Carlier)
. Fixed unmapping of an unrelated range when the aligned chunk mmap fails
(Marti2203).
- Date:
. Fixed bug GH-20503 (Assertion failure with ext/date DateInterval property
hash construction). (heathdutton)
@@ -0,0 +1,32 @@
--TEST--
Failed retry mmap() in zend_mm_chunk_alloc_int() must not unmap an unrelated range
--EXTENSIONS--
posix
--SKIPIF--
<?php
if (PHP_OS_FAMILY !== 'Linux') die('skip Linux only');
if (PHP_INT_SIZE != 8) die('skip this test is for 64bit platforms only');
if (getenv('USE_ZEND_ALLOC') === '0') die('skip Zend MM disabled');
$maps = @file_get_contents('/proc/self/maps');
if ($maps === false) die('skip /proc/self/maps not available');
if (!preg_match('/^([0-9a-f]+)-[0-9a-f]+ r-xp /m', $maps, $m) || hexdec($m[1]) > 0x40000000) {
die('skip only for non-PIE builds');
}
?>
--INI--
memory_limit=-1
--FILE--
<?php
// A huge block whose size equals the start of the executable mapping. If the
// first mmap() is not 2 MiB aligned and the retry fails under RLIMIT_AS, the
// missing NULL check used to unmap [size, size + 2 MiB - 4 KiB), i.e. our text.
preg_match('/^([0-9a-f]+)-[0-9a-f]+ r-xp /m', file_get_contents('/proc/self/maps'), $m);
$size = hexdec($m[1]);
preg_match('/VmSize:\s+(\d+)/', file_get_contents('/proc/self/status'), $m);
// Room for the first mmap() of $size bytes, not for the aligned retry.
var_dump(posix_setrlimit(POSIX_RLIMIT_AS, $m[1] * 1024 + $size + 1024 * 1024, POSIX_RLIMIT_INFINITY));
$s = str_repeat('A', $size - 4096);
echo "allocation succeeded\n";
?>
--EXPECTREGEX--
bool\(true\)\s+(.*Out of memory|allocation succeeded)\s*
+3
View File
@@ -905,6 +905,9 @@ static void *zend_mm_chunk_alloc_int(size_t size, size_t alignment)
/* chunk has to be aligned */
zend_mm_munmap(ptr, size);
ptr = zend_mm_mmap(size + alignment - REAL_PAGE_SIZE);
if (ptr == NULL) {
return NULL;
}
#ifdef _WIN32
offset = ZEND_MM_ALIGNED_OFFSET(ptr, alignment);
if (offset != 0) {