mirror of
https://github.com/php/php-src.git
synced 2026-10-28 17:56:50 +08:00
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:
1 parent
8934441b85
commit
cbda101fe3
3 files changed
+38
-1
No files matched your search
@@ -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*
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user