From 1b0b852a5ffad4f466e1c6bb2fd713079348296a Mon Sep 17 00:00:00 2001 From: Alexander Danilov <10532067+adapik@users.noreply.github.com> Date: Sat, 26 Sep 2026 20:03:06 +0300 Subject: [PATCH 1/7] ext/openssl: use ZEND_STRL when it's possible (#23926) Drive-by removal of unused `ext/standard/base64.h` import. --- ext/openssl/openssl.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 5ea2a7737434..3fcb34e9b801 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -32,7 +32,6 @@ #include "ext/standard/file.h" #include "ext/standard/info.h" #include "ext/standard/php_fopen_wrappers.h" -#include "ext/standard/base64.h" #ifdef PHP_WIN32 # include "win32/winutil.h" #endif @@ -1733,7 +1732,7 @@ PHP_FUNCTION(openssl_pkcs12_export_to_file) /* parse extra config from args array, promote this to an extra function */ if (args && - (item = zend_hash_str_find(Z_ARRVAL_P(args), "friendly_name", sizeof("friendly_name")-1)) != NULL && + (item = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("friendly_name"))) != NULL && Z_TYPE_P(item) == IS_STRING ) { friendly_name = Z_STRVAL_P(item); @@ -1743,7 +1742,7 @@ PHP_FUNCTION(openssl_pkcs12_export_to_file) friendly_caname */ - if (args && (item = zend_hash_str_find(Z_ARRVAL_P(args), "extracerts", sizeof("extracerts")-1)) != NULL) { + if (args && (item = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("extracerts"))) != NULL) { ca = php_openssl_array_to_X509_sk(item, 5, "extracerts"); if (!ca) { goto cleanup; @@ -1833,13 +1832,13 @@ PHP_FUNCTION(openssl_pkcs12_export) /* parse extra config from args array, promote this to an extra function */ if (args && - (item = zend_hash_str_find(Z_ARRVAL_P(args), "friendly_name", sizeof("friendly_name")-1)) != NULL && + (item = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("friendly_name"))) != NULL && Z_TYPE_P(item) == IS_STRING ) { friendly_name = Z_STRVAL_P(item); } - if (args && (item = zend_hash_str_find(Z_ARRVAL_P(args), "extracerts", sizeof("extracerts")-1)) != NULL) { + if (args && (item = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("extracerts"))) != NULL) { ca = php_openssl_array_to_X509_sk(item, 5, "extracerts"); if (!ca) { goto cleanup; @@ -2450,7 +2449,7 @@ PHP_FUNCTION(openssl_pkey_new) if (args && Z_TYPE_P(args) == IS_ARRAY) { EVP_PKEY *pkey; - if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "rsa", sizeof("rsa")-1)) != NULL && + if ((data = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("rsa"))) != NULL && Z_TYPE_P(data) == IS_ARRAY) { pkey = php_openssl_pkey_init_rsa(data); if (!pkey) { @@ -2458,7 +2457,7 @@ PHP_FUNCTION(openssl_pkey_new) } php_openssl_pkey_object_init(return_value, pkey, /* is_private */ true); return; - } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "dsa", sizeof("dsa") - 1)) != NULL && + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("dsa"))) != NULL && Z_TYPE_P(data) == IS_ARRAY) { bool is_private; pkey = php_openssl_pkey_init_dsa(data, &is_private); @@ -2467,7 +2466,7 @@ PHP_FUNCTION(openssl_pkey_new) } php_openssl_pkey_object_init(return_value, pkey, is_private); return; - } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "dh", sizeof("dh") - 1)) != NULL && + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("dh"))) != NULL && Z_TYPE_P(data) == IS_ARRAY) { bool is_private; pkey = php_openssl_pkey_init_dh(data, &is_private); @@ -2477,7 +2476,7 @@ PHP_FUNCTION(openssl_pkey_new) php_openssl_pkey_object_init(return_value, pkey, is_private); return; #ifdef HAVE_EVP_PKEY_EC - } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "ec", sizeof("ec") - 1)) != NULL && + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("ec"))) != NULL && Z_TYPE_P(data) == IS_ARRAY) { bool is_private; pkey = php_openssl_pkey_init_ec(data, &is_private); @@ -2488,19 +2487,19 @@ PHP_FUNCTION(openssl_pkey_new) return; #endif #if PHP_OPENSSL_API_VERSION >= 0x30000 - } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "x25519", sizeof("x25519") - 1)) != NULL && + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("x25519"))) != NULL && Z_TYPE_P(data) == IS_ARRAY) { php_openssl_pkey_object_curve_25519_448(return_value, "X25519", data); return; - } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "ed25519", sizeof("ed25519") - 1)) != NULL && + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("ed25519"))) != NULL && Z_TYPE_P(data) == IS_ARRAY) { php_openssl_pkey_object_curve_25519_448(return_value, "ED25519", data); return; - } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "x448", sizeof("x448") - 1)) != NULL && + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("x448"))) != NULL && Z_TYPE_P(data) == IS_ARRAY) { php_openssl_pkey_object_curve_25519_448(return_value, "X448", data); return; - } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "ed448", sizeof("ed448") - 1)) != NULL && + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), ZEND_STRL("ed448"))) != NULL && Z_TYPE_P(data) == IS_ARRAY) { php_openssl_pkey_object_curve_25519_448(return_value, "ED448", data); return; From e23dc68f1199bf69524fb84e3e1b0e295c0fb863 Mon Sep 17 00:00:00 2001 From: lacatoire Date: Sat, 26 Sep 2026 18:22:54 +0100 Subject: [PATCH 2/7] ext/pcntl: fix an off by one bound and a parameter the parser made required pcntl_setcpuaffinity() refuses a cpu id equal to the cpu count but named that count as the inclusive upper bound, printing a zend_long with the unsigned format on the way. pcntl_setqos_class() declares $qos_class optional while its parser demanded it, leaving the declared default unreachable. Close GH-23553 --- NEWS | 4 ++ ext/pcntl/pcntl.c | 9 ++-- ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt | 52 ++++++++++++++++++++ ext/pcntl/tests/pcntl_qosclass.phpt | 5 ++ 4 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt diff --git a/NEWS b/NEWS index 8d34aad3fe67..fb25e3a0b809 100644 --- a/NEWS +++ b/NEWS @@ -70,6 +70,10 @@ PHP NEWS an exception is pending. (nicolas-grekas) . Fixed pcntl_signal_dispatch() dropping the signals queued behind a handler that throws. (nicolas-grekas) + . Fixed pcntl_setcpuaffinity() error message advertising the cpu count as + the inclusive upper bound. (lacatoire) + . Fixed pcntl_setqos_class() requiring its optional $qos_class argument. + (lacatoire) - PDO: . Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index b55e1417d114..34a670282636 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -1791,7 +1791,7 @@ PHP_FUNCTION(pcntl_setcpuaffinity) } if (cpu < 0 || cpu >= maxcpus) { - zend_argument_value_error(2, "cpu id must be between 0 and " ZEND_ULONG_FMT " (" ZEND_LONG_FMT ")", maxcpus, cpu); + zend_argument_value_error(2, "cpu id must be between 0 and " ZEND_LONG_FMT " (" ZEND_LONG_FMT ")", maxcpus - 1, cpu); PCNTL_CPU_DESTROY(mask); RETURN_THROWS(); } @@ -1898,13 +1898,14 @@ PHP_FUNCTION(pcntl_getqos_class) PHP_FUNCTION(pcntl_setqos_class) { - zval *qos_obj; + zval *qos_obj = NULL; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL Z_PARAM_OBJECT_OF_CLASS(qos_obj, QosClass_ce) ZEND_PARSE_PARAMETERS_END(); - qos_class_t qos_class = qos_zval_to_lval(qos_obj); + qos_class_t qos_class = qos_obj ? qos_zval_to_lval(qos_obj) : QOS_CLASS_DEFAULT; if (UNEXPECTED(pthread_set_qos_class_self_np((qos_class_t)qos_class, 0) != 0)) { diff --git a/ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt b/ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt new file mode 100644 index 000000000000..6b415a52c242 --- /dev/null +++ b/ext/pcntl/tests/pcntl_cpuaffinity_bound.phpt @@ -0,0 +1,52 @@ +--TEST-- +pcntl_setcpuaffinity(): the upper bound the error advertises is itself a valid cpu id +--EXTENSIONS-- +pcntl +--SKIPIF-- + +--FILE-- +getMessage(), $m)) { + exit("unexpected message: " . $e->getMessage() . PHP_EOL); + } +} +$bound = (int) $m[1]; + +/* Every id is range checked before any syscall runs, so pairing the advertised + bound with an out of range id shows which of the two the check rejects, + without ever changing the process affinity. */ +try { + pcntl_setcpuaffinity($pid, [$bound, PHP_INT_MAX]); +} catch (Throwable $e) { + echo $e::class, "\n"; + var_dump($e->getMessage() === $prefix . $bound . ' (' . PHP_INT_MAX . ')'); +} + +/* and the first id past the bound is rejected, naming itself */ +try { + pcntl_setcpuaffinity($pid, [$bound + 1]); +} catch (Throwable $e) { + echo $e::class, "\n"; + var_dump($e->getMessage() === $prefix . $bound . ' (' . ($bound + 1) . ')'); +} +?> +--EXPECT-- +ValueError +ValueError +bool(true) +ValueError +bool(true) diff --git a/ext/pcntl/tests/pcntl_qosclass.phpt b/ext/pcntl/tests/pcntl_qosclass.phpt index f8ca1a706bd2..947afdb4836b 100644 --- a/ext/pcntl/tests/pcntl_qosclass.phpt +++ b/ext/pcntl/tests/pcntl_qosclass.phpt @@ -13,7 +13,12 @@ pcntl_setqos_class(Pcntl\QosClass::Default); var_dump(Pcntl\QosClass::Default === pcntl_getqos_class()); pcntl_setqos_class(Pcntl\QosClass::Background); var_dump(Pcntl\QosClass::Background == pcntl_getqos_class()); + +/* the parameter is optional, and omitting it applies the declared default */ +pcntl_setqos_class(); +var_dump(Pcntl\QosClass::Default === pcntl_getqos_class()); ?> --EXPECT-- bool(true) bool(true) +bool(true) From 5d57dd6140a874204e6a93719fccf3a201500507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=A6=E7=94=B0=20=E6=86=B2=E5=A4=AA=E9=83=8E?= Date: Sat, 26 Sep 2026 02:15:34 +0000 Subject: [PATCH 3/7] ext/pdo_pgsql: Fix crash when a persistent connection fails The shutdown function tried to reset the session state even when the connection had failed. Regression from GH-20572. Close GH-23922 --- NEWS | 3 +++ ext/pdo/pdo_dbh.c | 2 +- .../tests/persistent_connection_failure.phpt | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 ext/pdo_pgsql/tests/persistent_connection_failure.phpt diff --git a/NEWS b/NEWS index 40bc06f89e22..5dadee43b6ce 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,9 @@ PHP NEWS . Fixed pcntl_signal_dispatch() dropping the signals queued behind a handler that throws. (nicolas-grekas) +- PDO_PGSQL: + . Fixed crash when a persistent connection fails. (KentarouTakeda) + 24 Sep 2026, PHP 8.6.0RC2 - Core: diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index 358ac81eb88f..5aa95cdccaf0 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -1597,7 +1597,7 @@ static void pdo_dbh_free_storage(zend_object *std) dbh->in_txn = false; } - if (dbh->is_persistent && dbh->methods && dbh->methods->persistent_shutdown) { + if (dbh->is_persistent && dbh->driver_data && dbh->methods && dbh->methods->persistent_shutdown) { dbh->methods->persistent_shutdown(dbh); } zend_object_std_dtor(std); diff --git a/ext/pdo_pgsql/tests/persistent_connection_failure.phpt b/ext/pdo_pgsql/tests/persistent_connection_failure.phpt new file mode 100644 index 000000000000..7be690ae05a8 --- /dev/null +++ b/ext/pdo_pgsql/tests/persistent_connection_failure.phpt @@ -0,0 +1,21 @@ +--TEST-- +PDO PgSQL failed persistent connection does not crash on object destruction +--EXTENSIONS-- +pdo_pgsql +--FILE-- + true, + ]); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +echo "Failed connection object destroyed without crash\n"; + +?> +--EXPECTF-- +PDOException: SQLSTATE[08006] [7] %a +Failed connection object destroyed without crash From 179753061cb2268132310748fa8556b90cc4d52f Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Sun, 27 Sep 2026 02:39:02 +0800 Subject: [PATCH 4/7] Windows: Remove useless ext/hash dependencies (#23929) --- ext/opcache/config.w32 | 1 - ext/soap/config.m4 | 1 - ext/soap/config.w32 | 1 - 3 files changed, 3 deletions(-) diff --git a/ext/opcache/config.w32 b/ext/opcache/config.w32 index 1ad346b4da31..cd22b363db0b 100644 --- a/ext/opcache/config.w32 +++ b/ext/opcache/config.w32 @@ -17,7 +17,6 @@ ZEND_EXTENSION('opcache', "\ shared_alloc_win32.c", false); ADD_EXTENSION_DEP('opcache', 'date'); -ADD_EXTENSION_DEP('opcache', 'hash'); ADD_EXTENSION_DEP('opcache', 'pcre'); if (PHP_OPCACHE_JIT == "yes") { diff --git a/ext/soap/config.m4 b/ext/soap/config.m4 index 1b25e5101ede..a3e29d927642 100644 --- a/ext/soap/config.m4 +++ b/ext/soap/config.m4 @@ -21,7 +21,6 @@ if test "$PHP_SOAP" != "no"; then PHP_SUBST([SOAP_SHARED_LIBADD]) ]) PHP_ADD_EXTENSION_DEP(soap, date) - PHP_ADD_EXTENSION_DEP(soap, hash) PHP_ADD_EXTENSION_DEP(soap, libxml) PHP_ADD_EXTENSION_DEP(soap, session, true) fi diff --git a/ext/soap/config.w32 b/ext/soap/config.w32 index 7cecb8e8afed..faa346f3eec8 100644 --- a/ext/soap/config.w32 +++ b/ext/soap/config.w32 @@ -11,7 +11,6 @@ if (PHP_SOAP != "no") { EXTENSION('soap', 'soap.c php_encoding.c php_http.c php_packet_soap.c php_schema.c php_sdl.c php_xml.c', null, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); AC_DEFINE('HAVE_SOAP', 1, "Define to 1 if the PHP extension 'soap' is available."); ADD_EXTENSION_DEP('soap', 'date'); - ADD_EXTENSION_DEP('soap', 'hash'); ADD_EXTENSION_DEP('soap', 'session', true); if (!PHP_SOAP_SHARED) { From f61ebc03488496e7b6af5d2a0e18e3c8c7b09b73 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 12 Sep 2026 11:02:14 +0100 Subject: [PATCH 5/7] ext/intl: intl_error_set_custom_msg() crash without an active call frame Since 6600d0e00fc the message was unconditionally prefixed with get_active_function_or_method_name(), which asserts zend_is_executing() and dereferences a null EG(current_execute_data) in a release build. A UConverter subclass destroyed at request shutdown reached it through ucnv_close(). Fall back to the unprefixed message when there is no frame. Close GH-23673 --- NEWS | 2 ++ ext/intl/intl_error.c | 19 ++++++++++------- .../tests/uconverter_shutdown_subclass.phpt | 21 +++++++++++++++++++ 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 ext/intl/tests/uconverter_shutdown_subclass.phpt diff --git a/NEWS b/NEWS index cc86e4e8be31..c872634f9683 100644 --- a/NEWS +++ b/NEWS @@ -42,6 +42,8 @@ PHP NEWS toUCallback/fromUCallback. (Ilia Alshanetsky) . Fixed Collator attribute and strength methods not rejecting an unconstructed Collator. (Ilia Alshanetsky) + . Fixed crash in intl_error_set_custom_msg() without an active call frame. + (David Carlier) - Lexbor: . Merge patches lexbor/lexbor@8a14bc0 and lexbor/lexbor@f67ce4b, fixing a diff --git a/ext/intl/intl_error.c b/ext/intl/intl_error.c index be6e53fb5439..e5506ab44ac3 100644 --- a/ext/intl/intl_error.c +++ b/ext/intl/intl_error.c @@ -91,13 +91,18 @@ void intl_error_set_custom_msg( intl_error* err, const char* msg) return; } - zend_string *method_or_func = get_active_function_or_method_name(); - zend_string *prefixed_message = zend_string_concat3( - ZSTR_VAL(method_or_func), ZSTR_LEN(method_or_func), - ZEND_STRL("(): "), - msg, strlen(msg) - ); - zend_string_release_ex(method_or_func, false); + zend_string *prefixed_message; + if (zend_is_executing()) { + zend_string *method_or_func = get_active_function_or_method_name(); + prefixed_message = zend_string_concat3( + ZSTR_VAL(method_or_func), ZSTR_LEN(method_or_func), + ZEND_STRL("(): "), + msg, strlen(msg) + ); + zend_string_release_ex(method_or_func, false); + } else { + prefixed_message = zend_string_init(msg, strlen(msg), false); + } if( !err ) { if (INTL_G(error_level)) { diff --git a/ext/intl/tests/uconverter_shutdown_subclass.phpt b/ext/intl/tests/uconverter_shutdown_subclass.phpt new file mode 100644 index 000000000000..09c231a99e9c --- /dev/null +++ b/ext/intl/tests/uconverter_shutdown_subclass.phpt @@ -0,0 +1,21 @@ +--TEST-- +UConverter subclass destroyed at request shutdown does not crash +--EXTENSIONS-- +intl +--FILE-- + +--EXPECT-- +end of script From 497fdf254e441fbfdd2135fa9bcfb370cbf0244d Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 22 Sep 2026 11:30:05 +0100 Subject: [PATCH 6/7] [skip ci] sapi/cli: Wait for the connection in Expect 100-continue abort test The test resets the connection as soon as it is opened, so the reset could reach the server while the connection was still in the listen backlog. The server then never saw the request and the expected error was never logged. Complete a request on a second connection first: the accept queue is FIFO, so a response on the later connection means the earlier one has been accepted, and the reset is guaranteed to hit an established connection. Close GH-23841 --- .../tests/php_cli_server_expect_100_continue_iua.phpt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sapi/cli/tests/php_cli_server_expect_100_continue_iua.phpt b/sapi/cli/tests/php_cli_server_expect_100_continue_iua.phpt index c792630a9305..cf2a40a339a7 100644 --- a/sapi/cli/tests/php_cli_server_expect_100_continue_iua.phpt +++ b/sapi/cli/tests/php_cli_server_expect_100_continue_iua.phpt @@ -11,7 +11,15 @@ if (PHP_OS_FAMILY === "Windows") die("skip SO_LINGER reset behaviour differs on include "php_cli_server.inc"; $server = php_cli_server_start('echo "Hello world";', 'index.php', ['-d', 'ignore_user_abort=1']); -$fp = fsockopen(PHP_CLI_SERVER_HOSTNAME, PHP_CLI_SERVER_PORT); +$fp = php_cli_server_connect(); + +$probe = php_cli_server_connect(); +fwrite($probe, "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"); +while (!feof($probe)) { + fgets($probe); +} +fclose($probe); + socket_set_option(socket_import_stream($fp), SOL_SOCKET, SO_LINGER, ['l_onoff' => 1, 'l_linger' => 0]); fwrite($fp, "POST / HTTP/1.1\r\nExpect: 100-continue\r\nContent-Length: 4\r\n\r\n"); fclose($fp); From 267c6dc694aa9e5a4c76e5a2ee8938e5be2149d3 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 26 Sep 2026 20:40:22 +0100 Subject: [PATCH 7/7] sapi/cli: Run CI for the Expect 100-continue abort test fix 497fdf254e4 was committed with a [skip ci] prefix by mistake, so the fixed php_cli_server_expect_100_continue_iua.phpt never ran in CI. This empty commit makes CI run it.