From 884070537fb8dbff42677721898173901fe176ee Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Fri, 26 Jun 2026 17:44:29 +0200 Subject: [PATCH 1/5] ext/ldap: Remove unused HAVE_LDAP_EXTENDED_OPERATION (#22455) Current code assumes having ldap_extended_operation_s() also means having ldap_extended_operation(). --- ext/ldap/config.m4 | 1 - ext/ldap/config.w32 | 1 - 2 files changed, 2 deletions(-) diff --git a/ext/ldap/config.m4 b/ext/ldap/config.m4 index 14174bd5dc26..65a3d3b90eed 100644 --- a/ext/ldap/config.m4 +++ b/ext/ldap/config.m4 @@ -134,7 +134,6 @@ if test "$PHP_LDAP" != "no"; then dnl nor ldap_start_tls_s() AC_CHECK_FUNCS(m4_normalize([ ldap_control_find - ldap_extended_operation ldap_extended_operation_s ldap_parse_extended_result ldap_parse_reference diff --git a/ext/ldap/config.w32 b/ext/ldap/config.w32 index c6a7049aa1c6..1fa1fe3f777c 100644 --- a/ext/ldap/config.w32 +++ b/ext/ldap/config.w32 @@ -23,7 +23,6 @@ if (PHP_LDAP != "no") { AC_DEFINE('HAVE_LDAP_PASSWD', 1); AC_DEFINE('HAVE_LDAP_WHOAMI_S', 1); AC_DEFINE('HAVE_LDAP_REFRESH_S', 1); - AC_DEFINE('HAVE_LDAP_EXTENDED_OPERATION', 1); AC_DEFINE('HAVE_3ARG_SETREBINDPROC', 1); } else { WARNING("ldap not enabled; libraries and headers not found"); From ecb2c73cb8632248b6e3948d28f3e4a3e712ddef Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Fri, 26 Jun 2026 17:55:20 +0200 Subject: [PATCH 2/5] ext/pgsql: Remove unused HAVE_DECL_PGRES_TUPLES_CHUNK (#22456) --- ext/pgsql/config.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/pgsql/config.m4 b/ext/pgsql/config.m4 index 1409f879b52c..63e996fe06da 100644 --- a/ext/pgsql/config.m4 +++ b/ext/pgsql/config.m4 @@ -38,7 +38,7 @@ if test "$PHP_PGSQL" != "no"; then old_CFLAGS=$CFLAGS CFLAGS="$CFLAGS $PGSQL_CFLAGS" - AC_CHECK_DECLS([PGRES_TUPLES_CHUNK], + AC_CHECK_DECL([PGRES_TUPLES_CHUNK], PHP_CHECK_LIBRARY([pq], [PQsetChunkedRowsMode], [AC_DEFINE([HAVE_PG_SET_CHUNKED_ROWS_SIZE], [1], [Define to 1 if libpq has the 'PQsetChunkedRowsMode' function (PostgreSQL From a0ddc2aa577de0dc8018d6b068dddae9cb0f1ab2 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Thu, 25 Jun 2026 17:24:38 -0400 Subject: [PATCH 3/5] JIT: Fix run_time_cache offset stored without map_ptr dereference zend_jit_do_fcall() loaded the callee run_time_cache through the closure direct-pointer shortcut whenever the call frame was flagged as a closure call. That flag is set for every ZEND_INIT_DYNAMIC_CALL, but a dynamic call may resolve to a non-closure function whose run_time_cache is a zend_map_ptr offset. The raw offset was then stored into EX(run_time_cache) without resolving it through CG(map_ptr_base), and a later cache lookup dereferenced a bogus address. Restrict the shortcut to statically-known closures. Unknown dynamic calls fall through to the general path, which resolves both offsets and direct pointers. Fixes GH-22443 Closes GH-22459 --- NEWS | 2 ++ ext/opcache/jit/zend_jit_ir.c | 5 +-- ext/opcache/tests/jit/gh22443.phpt | 57 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 ext/opcache/tests/jit/gh22443.phpt diff --git a/NEWS b/NEWS index ef8d40d00f82..4022869c0b06 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,8 @@ PHP NEWS . Fixed bug GH-22158 (Tracing JIT dispatches the observer begin handler through the wrong run_time_cache slot on megamorphic calls). (ptondereau, iliaal) + . Fixed bug GH-22443 (Tracing JIT SIGSEGV on megamorphic dynamic calls from + an undereferenced run_time_cache map_ptr offset). (iliaal) - Intl: . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 5e8a71cd48ce..f8503a131734 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -10221,10 +10221,7 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen && ZEND_MAP_PTR_IS_OFFSET(func->op_array.run_time_cache)) { run_time_cache = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_CG(map_ptr_base)), (uintptr_t)ZEND_MAP_PTR(func->op_array.run_time_cache))); - } else if ((func && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) || - (JIT_G(current_frame) && - JIT_G(current_frame)->call && - TRACE_FRAME_IS_CLOSURE_CALL(JIT_G(current_frame)->call))) { + } else if (func && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) { /* Closures always use direct pointers */ ir_ref local_func_ref = func_ref ? func_ref : ir_LOAD_A(jit_CALL(rx, func)); diff --git a/ext/opcache/tests/jit/gh22443.phpt b/ext/opcache/tests/jit/gh22443.phpt new file mode 100644 index 000000000000..869329b79b5c --- /dev/null +++ b/ext/opcache/tests/jit/gh22443.phpt @@ -0,0 +1,57 @@ +--TEST-- +GH-22443 (SIGSEGV in tracing JIT: run_time_cache offset stored without map_ptr dereference) +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=tracing +opcache.jit_buffer_size=64M +opcache.jit_max_polymorphic_calls=0 +--FILE-- +helper((int) array_sum($a)); } + private function helper(int $x): int { return $x + 1; } +} + +function makeListener($listener): Closure { + return function ($event, $payload) use ($listener) { + return $listener(...array_values($payload)); + }; +} + +function invokeListeners(array $listeners, string $event, array $payload): int { + $r = 0; + foreach ($listeners as $l) { + $r += $l($event, $payload); + } + return $r; +} + +$svc = new Svc(); +$warm = []; +for ($k = 0; $k < 8; $k++) { + $warm[] = makeListener([$svc, 'm' . $k]); +} +$cold = [makeListener([$svc, 'coldMethod'])]; + +$s = 0; +for ($i = 0; $i < 4000000; $i++) { + $s += invokeListeners($warm, 'e', [$i & 7, ($i >> 2) & 7]); +} +for ($j = 0; $j < 5; $j++) { + $s += invokeListeners($cold, 'e', [$j, $j + 1]); +} +echo "done\n"; +?> +--EXPECT-- +done From 64cf7b2dbade0d3001d79be3a49e42d9dbc1aa66 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 21 Jun 2026 08:04:23 -0400 Subject: [PATCH 4/5] Fix int truncation of read length in shmop_read() shmop_read() held the read length in an int while count and shmop->size are zend_long and the bounds checks above validate against the full 64-bit size. On a shared-memory segment larger than INT_MAX a read whose length sets the int sign bit was sign-extended into the size_t length argument of zend_string_init(), requesting a near-SIZE_MAX allocation; other truncated lengths silently returned a wrong-sized string. Hold the length in a zend_long, matching the zend_long writesize already used in shmop_write(). Closes GH-22425 --- ext/shmop/shmop.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/shmop/shmop.c b/ext/shmop/shmop.c index 67f060f3c82c..640f595ea6e8 100644 --- a/ext/shmop/shmop.c +++ b/ext/shmop/shmop.c @@ -224,7 +224,6 @@ PHP_FUNCTION(shmop_read) zend_long start, count; php_shmop *shmop; char *startaddr; - int bytes; zend_string *return_string; if (zend_parse_parameters(ZEND_NUM_ARGS(), "Oll", &shmid, shmop_ce, &start, &count) == FAILURE) { @@ -244,7 +243,7 @@ PHP_FUNCTION(shmop_read) } startaddr = shmop->addr + start; - bytes = count ? count : shmop->size - start; + zend_long bytes = count ? count : shmop->size - start; return_string = zend_string_init(startaddr, bytes, 0); From 5fa74db18d9220b72cc6a04072e317e695f23c90 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 26 Jun 2026 08:15:14 -0400 Subject: [PATCH 5/5] Fix use-after-free in Collator::sort() with a mutating comparator collator_sort() and collator_asort() sort the array in place while the comparator may run a __toString() that appends to the same array through a reference, reallocating its storage under the running sort. Sort a copy and swap it back, matching usort(). Closes GH-22467 --- ext/intl/collator/collator_sort.c | 23 +++++++++--- .../collator_sort_modify_during_compare.phpt | 36 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 ext/intl/tests/collator_sort_modify_during_compare.phpt diff --git a/ext/intl/collator/collator_sort.c b/ext/intl/collator/collator_sort.c index 47e48624e6b7..9d4cb4220203 100644 --- a/ext/intl/collator/collator_sort.c +++ b/ext/intl/collator/collator_sort.c @@ -259,12 +259,13 @@ static void collator_sort_internal( int renumber, INTERNAL_FUNCTION_PARAMETERS ) UCollator* saved_collator; zval* array = NULL; HashTable* hash = NULL; + zend_array* sorted = NULL; zend_long sort_flags = COLLATOR_SORT_REGULAR; COLLATOR_METHOD_INIT_VARS /* Parse parameters. */ - if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Oa/|l", + if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "Oa|l", &object, Collator_ce_ptr, &array, &sort_flags ) == FAILURE ) { RETURN_THROWS(); @@ -283,8 +284,14 @@ static void collator_sort_internal( int renumber, INTERNAL_FUNCTION_PARAMETERS ) hash = Z_ARRVAL_P( array ); + /* Copy array, so the in-place modifications will not be visible to the callback function */ + sorted = zend_array_dup( hash ); + /* Convert strings in the specified array from UTF-8 to UTF-16. */ - collator_convert_hash_from_utf8_to_utf16( hash, COLLATOR_ERROR_CODE_P( co ) ); + collator_convert_hash_from_utf8_to_utf16( sorted, COLLATOR_ERROR_CODE_P( co ) ); + if( U_FAILURE( COLLATOR_ERROR_CODE( co ) ) ) { + zend_array_destroy( sorted ); + } COLLATOR_CHECK_STATUS( co, "Error converting hash from UTF-8 to UTF-16" ); /* Save specified collator in the request-global (?) variable. */ @@ -292,15 +299,23 @@ static void collator_sort_internal( int renumber, INTERNAL_FUNCTION_PARAMETERS ) INTL_G( current_collator ) = co->ucoll; /* Sort specified array. */ - zend_hash_sort(hash, collator_compare_func, renumber); + zend_hash_sort( sorted, collator_compare_func, renumber ); /* Restore saved collator. */ INTL_G( current_collator ) = saved_collator; /* Convert strings in the specified array back to UTF-8. */ - collator_convert_hash_from_utf16_to_utf8( hash, COLLATOR_ERROR_CODE_P( co ) ); + collator_convert_hash_from_utf16_to_utf8( sorted, COLLATOR_ERROR_CODE_P( co ) ); + if( U_FAILURE( COLLATOR_ERROR_CODE( co ) ) ) { + zend_array_destroy( sorted ); + } COLLATOR_CHECK_STATUS( co, "Error converting hash from UTF-16 to UTF-8" ); + zval garbage; + ZVAL_COPY_VALUE( &garbage, array ); + ZVAL_ARR( array, sorted ); + zval_ptr_dtor( &garbage ); + RETURN_TRUE; } /* }}} */ diff --git a/ext/intl/tests/collator_sort_modify_during_compare.phpt b/ext/intl/tests/collator_sort_modify_during_compare.phpt new file mode 100644 index 000000000000..427f5833f1fc --- /dev/null +++ b/ext/intl/tests/collator_sort_modify_during_compare.phpt @@ -0,0 +1,36 @@ +--TEST-- +Collator::sort(): mutating the array from __toString() during comparison must not corrupt the sort +--EXTENSIONS-- +intl +--FILE-- +sort($arr)); +var_dump($arr); +?> +--EXPECT-- +bool(true) +array(4) { + [0]=> + string(1) "a" + [1]=> + string(1) "b" + [2]=> + object(Grow)#2 (0) { + } + [3]=> + string(1) "z" +}