Commit cbda101fe30 for php

commit cbda101fe3008d61e8474da41beff876daa82dcb
Author: Martin Mirchev <mmirchev@comp.nus.edu.sg>
Date:   Tue Oct 27 00:30:51 2026 +0800

    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.

diff --git a/NEWS b/NEWS
index 5ea9ecbafb0..480df545c24 100644
--- a/NEWS
+++ b/NEWS
@@ -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)
diff --git a/Zend/tests/zend_mm_chunk_alloc_mmap_failure.phpt b/Zend/tests/zend_mm_chunk_alloc_mmap_failure.phpt
new file mode 100644
index 00000000000..b137f25620c
--- /dev/null
+++ b/Zend/tests/zend_mm_chunk_alloc_mmap_failure.phpt
@@ -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*
diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c
index 3cbdd1e5f5d..2b0b2d62a19 100644
--- a/Zend/zend_alloc.c
+++ b/Zend/zend_alloc.c
@@ -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) {