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) {