Commit 8484a9fda7b for php.net

commit 8484a9fda7b7662bc099c3480f85564e3edcae9b
Author: Piotr Hałas <piotr@halas.net.pl>
Date:   Fri Sep 4 14:44:05 2026 +0200

    ext/opcache: keep huge page remap inside the reserved range (#23554)

    create_segments() reserves requested_size bytes with MAP_32BIT, frees
    them, rounds the address up to the 2 MB huge page boundary, and then
    MAP_FIXED-maps requested_size bytes at the new address.

    The address goes up but the size stays the same, so the mapping ends up
    to 2 MB above the memory we reserved, and MAP_FIXED discards what is
    mapped there. If huge pages are available the remap succeeds and
    replaces that memory. If they are not, mmap() fails, but the kernel has
    already removed it and leaves a hole (mm/vma.c, vms_abort_munmap_vmas).

    On a normal host there is usually nothing above the reservation, so this
    is not visible. Under Rosetta 2 MAP_32BIT is not honored, the
    reservation lands directly below libc, and the overshoot unmaps its
    first pages: php-fpm then dies with SIGSEGV shortly after start.

    Reserve one extra huge page, so the aligned range always stays inside
    the reservation. zend_mm_chunk_alloc_int() already does this for 2 MB
    aligned chunks.

diff --git a/NEWS b/NEWS
index 84f5597d978..13b3286c7b6 100644
--- a/NEWS
+++ b/NEWS
@@ -59,6 +59,8 @@ PHP                                                                        NEWS
     (Ilia Alshanetsky)

 - Opcache:
+  . Fixed a crash when the huge page SHM remap discarded mappings outside the
+    reserved address range. (Piotr Hałas)
   . Fixed opcache.protect_memory race under ZTS. (realFlowControl)
   . Fixed bug GH-23288 (Crash on restart when opcache.interned_strings_buffer
     is overridden in an individual FPM pool). (David Carlier)
diff --git a/ext/opcache/shared_alloc_mmap.c b/ext/opcache/shared_alloc_mmap.c
index 18c7532478f..c46b6d8b4ae 100644
--- a/ext/opcache/shared_alloc_mmap.c
+++ b/ext/opcache/shared_alloc_mmap.c
@@ -245,9 +245,9 @@ static int create_segments(size_t requested_size, zend_shared_segment ***shared_
 		/* to got HUGE PAGES in low 32-bit address we have to reserve address
 		   space and then remap it using MAP_HUGETLB */

-		p = mmap(NULL, requested_size, flags, MAP_SHARED|MAP_ANONYMOUS|MAP_32BIT, fd, 0);
+		p = mmap(NULL, requested_size + huge_page_size, flags, MAP_SHARED|MAP_ANONYMOUS|MAP_32BIT, fd, 0);
 		if (p != MAP_FAILED) {
-			munmap(p, requested_size);
+			munmap(p, requested_size + huge_page_size);
 			p = (void*)(ZEND_MM_ALIGNED_SIZE_EX((ptrdiff_t)p, huge_page_size));
 			p = mmap(p, requested_size, flags, MAP_SHARED|MAP_ANONYMOUS|MAP_32BIT|MAP_HUGETLB|MAP_FIXED, -1, 0);
 			if (p != MAP_FAILED) {