From a8086be81c088db439dda157d330dfd69e38bcc7 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:04:09 +0200 Subject: [PATCH 01/56] Fix GH-18986: OpenSSL backend: incorrect RAND_{load,write}_file() return value check As noted by the LibreSSL maintainer, these functions return -1 on error. This is further confirmed by my static analyzer that inferred the same thing for OpenSSL. Closes GH-19013. --- NEWS | 2 ++ ext/openssl/openssl.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index c395d0dcd3b5..42de159acbaa 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,8 @@ PHP NEWS - OpenSSL: . Fixed bug #80770 (It is not possible to get client peer certificate with stream_socket_server). (Jakub Zelenka) + . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() + return value check). (nielsdos, botovq) - PCNTL: . Fixed bug GH-18958 (Fatal error during shutdown after pcntl_rfork() or diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 718f946ad176..e44bc90bbd7f 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -1095,7 +1095,7 @@ static int php_openssl_load_rand_file(const char * file, int *egdsocket, int *se return SUCCESS; #endif } - if (file == NULL || !RAND_load_file(file, -1)) { + if (file == NULL || RAND_load_file(file, -1) < 0) { if (RAND_status() == 0) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "Unable to load random state; not enough random data!"); @@ -1122,7 +1122,7 @@ static int php_openssl_write_rand_file(const char * file, int egdsocket, int see file = RAND_file_name(buffer, sizeof(buffer)); } PHP_OPENSSL_RAND_ADD_TIME(); - if (file == NULL || !RAND_write_file(file)) { + if (file == NULL || RAND_write_file(file) < 0) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "Unable to write random state"); return FAILURE; From b495ce03716bbfa469c5eafb244205153abfb883 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 15 Jul 2025 19:09:08 +0200 Subject: [PATCH 02/56] Fix memleak on failure in collator_get_sort_key() Closes GH-19138. --- NEWS | 3 +++ ext/intl/collator/collator_sort.c | 1 + 2 files changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 42de159acbaa..0b66c5447a1f 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,9 @@ PHP NEWS (nielsdos) . Remove incorrect string release. (nielsdos) +- Intl: + . Fix memleak on failure in collator_get_sort_key(). (nielsdos) + - LDAP: . Fixed GH-18902 ldap_exop/ldap_exop_sync assert triggered on empty request OID. (David Carlier) diff --git a/ext/intl/collator/collator_sort.c b/ext/intl/collator/collator_sort.c index 0634e68fc7a3..b610a8aa6c78 100644 --- a/ext/intl/collator/collator_sort.c +++ b/ext/intl/collator/collator_sort.c @@ -556,6 +556,7 @@ PHP_FUNCTION( collator_get_sort_key ) key_len = ucol_getSortKey(co->ucoll, ustr, ustr_len, (uint8_t*)ZSTR_VAL(key_str), key_len); efree( ustr ); if(!key_len) { + zend_string_efree(key_str); RETURN_FALSE; } ZSTR_LEN(key_str) = key_len - 1; From cd8722304caebe91f86964643c27fc763bda92da Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 1 Jul 2025 00:07:22 +0200 Subject: [PATCH 03/56] Fix error return check of EVP_CIPHER_CTX_ctrl() OpenSSL can return -1 on error [1, 2], and OpenBSD's docs confirm this [3]. Change all checks to <= 0. [1] https://github.com/openssl/openssl/blob/b3161bd9a9329be3d6bf6b29a06835e2721898bb/crypto/evp/evp_enc.c#L1530-L1531 [2] https://github.com/openssl/openssl/blob/b3161bd9a9329be3d6bf6b29a06835e2721898bb/crypto/evp/evp_enc.c#L1611 [3] https://man.openbsd.org/EVP_CIPHER_CTX_ctrl.3 Closes GH-18987. --- NEWS | 1 + ext/openssl/openssl.c | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 0b66c5447a1f..81e23e6c6ff6 100644 --- a/NEWS +++ b/NEWS @@ -42,6 +42,7 @@ PHP NEWS stream_socket_server). (Jakub Zelenka) . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() return value check). (nielsdos, botovq) + . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) - PCNTL: . Fixed bug GH-18958 (Fatal error during shutdown after pcntl_rfork() or diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index e44bc90bbd7f..a0418e74ba7d 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -7475,7 +7475,7 @@ static int php_openssl_validate_iv(const char **piv, size_t *piv_len, size_t iv_ char *iv_new; if (mode->is_aead) { - if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) != 1) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) <= 0) { php_error_docref(NULL, E_WARNING, "Setting of IV length for AEAD mode failed"); return FAILURE; } @@ -7547,7 +7547,7 @@ static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type, return FAILURE; } if (mode->set_tag_length_always || (enc && mode->set_tag_length_when_encrypting)) { - if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL)) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL) <= 0) { php_error_docref(NULL, E_WARNING, "Setting tag length for AEAD cipher failed"); return FAILURE; } @@ -7555,7 +7555,7 @@ static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type, if (!enc && tag && tag_len > 0) { if (!mode->is_aead) { php_error_docref(NULL, E_WARNING, "The tag cannot be used because the cipher algorithm does not support AEAD"); - } else if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag)) { + } else if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag) <= 0) { php_error_docref(NULL, E_WARNING, "Setting tag for AEAD cipher decryption failed"); return FAILURE; } @@ -7693,7 +7693,7 @@ PHP_OPENSSL_API zend_string* php_openssl_encrypt( if (mode.is_aead && tag) { zend_string *tag_str = zend_string_alloc(tag_len, 0); - if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) == 1) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) > 0) { ZSTR_VAL(tag_str)[tag_len] = '\0'; ZSTR_LEN(tag_str) = tag_len; ZEND_TRY_ASSIGN_REF_NEW_STR(tag, tag_str); From faf833bffc9ec448c69b48f6cafab4ae759281b9 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Wed, 16 Jul 2025 14:09:24 +0200 Subject: [PATCH 04/56] PHP 8.3 is now for PHP-8.3.25-dev --- NEWS | 13 +++++++++---- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 6 +++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index 81e23e6c6ff6..4fa5dda65c3d 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,14 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.3.24 +?? ??? ????, PHP 8.3.25 + +OpenSSL: + . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() + return value check). (nielsdos, botovq) + . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) + + +31 Jul 2025, PHP 8.3.24 - Calendar: . Fixed jewishtojd overflow on year argument. (David Carlier) @@ -40,9 +48,6 @@ PHP NEWS - OpenSSL: . Fixed bug #80770 (It is not possible to get client peer certificate with stream_socket_server). (Jakub Zelenka) - . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() - return value check). (nielsdos, botovq) - . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) - PCNTL: . Fixed bug GH-18958 (Fatal error during shutdown after pcntl_rfork() or diff --git a/Zend/zend.h b/Zend/zend.h index 0d2c52e2505e..df90c452654a 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.3.24-dev" +#define ZEND_VERSION "4.3.25-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index e96c12486fa2..822184e80996 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.3.24-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.3.25-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/main/php_version.h b/main/php_version.h index cf74940556dd..80c0dc560653 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 3 -#define PHP_RELEASE_VERSION 24 +#define PHP_RELEASE_VERSION 25 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.3.24-dev" -#define PHP_VERSION_ID 80324 +#define PHP_VERSION "8.3.25-dev" +#define PHP_VERSION_ID 80325 From eade5c17ead650b0735295214bbec84872465e87 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 27 May 2025 19:03:56 +0200 Subject: [PATCH 05/56] Fix GH-18529: additional inheriting of TLS int options This is for LDAP_OPT_X_TLS_PROTOCOL_MIN and LDAP_OPT_X_TLS_PROTOCOL_MAX It also adds a test that uses LDAPCONF with TLS max version lower than the minimum TLS server version so it should always fail. However it does not fial for the second case without this change which confirms that the change works as expected. Closes GH-18676 --- .github/scripts/setup-slapd.sh | 3 ++ NEWS | 6 ++- ext/ldap/ldap.c | 32 ++++++++++++--- .../tests/ldap_start_tls_rc_max_version.conf | 1 + .../tests/ldap_start_tls_rc_max_version.phpt | 41 +++++++++++++++++++ ext/ldap/tests/skipifbindfailure.inc | 33 +++++++++++++++ 6 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 ext/ldap/tests/ldap_start_tls_rc_max_version.conf create mode 100644 ext/ldap/tests/ldap_start_tls_rc_max_version.phpt diff --git a/.github/scripts/setup-slapd.sh b/.github/scripts/setup-slapd.sh index fcaa67d0a5f7..f6b976783c77 100755 --- a/.github/scripts/setup-slapd.sh +++ b/.github/scripts/setup-slapd.sh @@ -72,6 +72,9 @@ olcTLSCertificateKeyFile: /etc/ldap/ssl/server.key add: olcTLSVerifyClient olcTLSVerifyClient: never - +add: olcTLSProtocolMin +olcTLSProtocolMin: 3.3 +- add: olcAuthzRegexp olcAuthzRegexp: uid=usera,cn=digest-md5,cn=auth cn=usera,dc=my-domain,dc=com - diff --git a/NEWS b/NEWS index 4fa5dda65c3d..06427de7f965 100644 --- a/NEWS +++ b/NEWS @@ -2,7 +2,11 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.25 -OpenSSL: +- LDAP: + . Fixed bug GH-18529 (additional inheriting of TLS int options). + (Jakub Zelenka) + +- OpenSSL: . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() return value check). (nielsdos, botovq) . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index 769e6caa277b..0b0a0c21df4a 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -3732,7 +3732,8 @@ PHP_FUNCTION(ldap_rename_ext) */ static int _php_ldap_tls_newctx(LDAP *ld) { - int val = 0, i, opts[] = { + int val = 0, i; + int str_opts[] = { #if (LDAP_API_VERSION > 2000) LDAP_OPT_X_TLS_CACERTDIR, LDAP_OPT_X_TLS_CACERTFILE, @@ -3752,21 +3753,42 @@ static int _php_ldap_tls_newctx(LDAP *ld) #endif 0}; - for (i=0 ; opts[i] ; i++) { + for (i=0 ; str_opts[i] ; i++) { char *path = NULL; - ldap_get_option(ld, opts[i], &path); + ldap_get_option(ld, str_opts[i], &path); if (path) { /* already set locally */ ldap_memfree(path); } else { - ldap_get_option(NULL, opts[i], &path); + ldap_get_option(NULL, str_opts[i], &path); if (path) { /* set globally, inherit */ - ldap_set_option(ld, opts[i], path); + ldap_set_option(ld, str_opts[i], path); ldap_memfree(path); } } } +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN + int int_opts[] = { + LDAP_OPT_X_TLS_PROTOCOL_MIN, +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MAX + LDAP_OPT_X_TLS_PROTOCOL_MAX, +#endif + 0 + }; + for (i=0 ; int_opts[i] ; i++) { + int value = 0; + + ldap_get_option(ld, int_opts[i], &value); + if (value <= 0) { /* if value is not set already */ + ldap_get_option(NULL, int_opts[i], &value); + if (value > 0) { /* set globally, inherit */ + ldap_set_option(ld, int_opts[i], &value); + } + } + } +#endif + return ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &val); } diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.conf b/ext/ldap/tests/ldap_start_tls_rc_max_version.conf new file mode 100644 index 000000000000..0cd03f8b8e19 --- /dev/null +++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.conf @@ -0,0 +1 @@ +TLS_PROTOCOL_MAX 3.2 \ No newline at end of file diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt new file mode 100644 index 000000000000..359785f8b5a3 --- /dev/null +++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt @@ -0,0 +1,41 @@ +--TEST-- +ldap_start_tls() - Basic ldap_start_tls test +--EXTENSIONS-- +ldap +--ENV-- +LDAPCONF={PWD}/ldap_start_tls_rc_max_version.conf +--SKIPIF-- + "OpenLDAP", + "min_version" => 20600, +]; +require_once __DIR__ .'/skipifbindfailure.inc'; +?> +--FILE-- + +--EXPECT-- +bool(false) +bool(false) +bool(false) diff --git a/ext/ldap/tests/skipifbindfailure.inc b/ext/ldap/tests/skipifbindfailure.inc index 1a0d0c6d1998..81c7998cfbb5 100644 --- a/ext/ldap/tests/skipifbindfailure.inc +++ b/ext/ldap/tests/skipifbindfailure.inc @@ -10,4 +10,37 @@ if ($skip_on_bind_failure) { ldap_unbind($link); } + +if (isset($require_vendor)) { + ob_start(); + phpinfo(INFO_MODULES); + $phpinfo = ob_get_clean(); + + // Extract the LDAP section specifically + if (preg_match('/^ldap\s*$(.*?)^[a-z_]+\s*$/ims', $phpinfo, $ldap_section_match)) { + $ldap_section = $ldap_section_match[1]; + + // Extract vendor info from the LDAP section only + if (preg_match('/Vendor Name\s*=>\s*(.+)/i', $ldap_section, $name_match) && + preg_match('/Vendor Version\s*=>\s*(\d+)/i', $ldap_section, $version_match)) { + + $vendor_name = trim($name_match[1]); + $vendor_version = (int)$version_match[1]; + + // Check vendor name if specified + if (isset($require_vendor['name']) && $vendor_name !== $require_vendor['name']) { + die("skip Requires {$require_vendor['name']} (detected: $vendor_name)"); + } + + // Check minimum version if specified + if (isset($require_vendor['min_version']) && $vendor_version < $require_vendor['min_version']) { + die("skip Requires minimum version {$require_vendor['min_version']} (detected: $vendor_version)"); + } + } else { + die("skip Cannot determine LDAP vendor information"); + } + } else { + die("skip LDAP extension information not found"); + } +} ?> From a5df26691d1b1f71b964b869510473a5d5413999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 15 Jul 2025 13:36:56 +0200 Subject: [PATCH 06/56] ext/xml: Suppress libxml deprecation for `_xmlParserCtxt.inState` (#19131) The FreeBSD build fails due to the deprecation and -Werror: 2025-07-15T00:37:20.8390774Z /home/runner/work/php-src/php-src/ext/xml/compat.c:358:38: error: 'instate' is deprecated [-Werror,-Wdeprecated-declarations] 2025-07-15T00:37:20.8392577Z 358 | if (ret == NULL || parser->parser->instate == XML_PARSER_CONTENT) { 2025-07-15T00:37:20.8393184Z | ^ 2025-07-15T00:37:20.8394006Z /usr/local/include/libxml2/libxml/parser.h:309:33: note: 'instate' has been explicitly marked deprecated here 2025-07-15T00:37:20.8394903Z 309 | xmlParserInputState instate XML_DEPRECATED_MEMBER; 2025-07-15T00:37:20.8395413Z | ^ 2025-07-15T00:37:20.8396166Z /usr/local/include/libxml2/libxml/xmlexports.h:74:50: note: expanded from macro 'XML_DEPRECATED_MEMBER' 2025-07-15T00:37:20.8397058Z 74 | #define XML_DEPRECATED_MEMBER __attribute__((deprecated)) 2025-07-15T00:37:20.8397581Z | ^ 2025-07-15T00:37:20.8425542Z 1 error generated. --- ext/xml/compat.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/xml/compat.c b/ext/xml/compat.c index 7b463ebb5112..6bd69bec1961 100644 --- a/ext/xml/compat.c +++ b/ext/xml/compat.c @@ -375,7 +375,9 @@ _get_entity(void *user, const xmlChar *name) if (ret == NULL) ret = xmlGetDocEntity(parser->parser->myDoc, name); + ZEND_DIAGNOSTIC_IGNORED_START("-Wdeprecated-declarations") if (ret == NULL || (parser->parser->instate != XML_PARSER_ENTITY_VALUE && parser->parser->instate != XML_PARSER_ATTRIBUTE_VALUE)) { + ZEND_DIAGNOSTIC_IGNORED_END if (ret == NULL || ret->etype == XML_INTERNAL_GENERAL_ENTITY || ret->etype == XML_INTERNAL_PARAMETER_ENTITY || ret->etype == XML_INTERNAL_PREDEFINED_ENTITY) { /* Predefined entities will expand unless no cdata handler is present */ if (parser->h_default && ! (ret && ret->etype == XML_INTERNAL_PREDEFINED_ENTITY && parser->h_cdata)) { From 8516ae86d7de1987e19e0ff34a9eae855d518b0e Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 17 Jul 2025 13:44:21 +0200 Subject: [PATCH 07/56] Skip LDAP TLS max version test for now --- ext/ldap/tests/ldap_start_tls_rc_max_version.phpt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt index 359785f8b5a3..e983b97c4b4e 100644 --- a/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt +++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt @@ -6,6 +6,10 @@ ldap LDAPCONF={PWD}/ldap_start_tls_rc_max_version.conf --SKIPIF-- "OpenLDAP", "min_version" => 20600, From bdca73cc142ae682cdfea3b21b89354fad346ffe Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 17 Jul 2025 20:22:10 +0200 Subject: [PATCH 08/56] ext/hash: Remove incorrect zval_ptr_dtor --- ext/hash/hash.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/hash/hash.c b/ext/hash/hash.c index 19d72ed7699b..58b68fc63b9b 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -850,8 +850,6 @@ PHP_FUNCTION(hash_copy) RETVAL_OBJ(Z_OBJ_HANDLER_P(zhash, clone_obj)(Z_OBJ_P(zhash))); if (php_hashcontext_from_object(Z_OBJ_P(return_value))->context == NULL) { - zval_ptr_dtor(return_value); - zend_throw_error(NULL, "Cannot copy hash"); RETURN_THROWS(); } From d11099ae325b0ade036c039f3e6962120c0a1709 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 17 Jul 2025 20:22:16 +0200 Subject: [PATCH 09/56] ext/socket: Remove incorrect zval_ptr_dtor --- ext/sockets/sockets.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index c252dc6e07a4..a0d5e52458f3 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2511,7 +2511,6 @@ PHP_FUNCTION(socket_addrinfo_bind) } default: close(php_sock->bsd_socket); - zval_ptr_dtor(return_value); zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); RETURN_THROWS(); } @@ -2575,7 +2574,6 @@ PHP_FUNCTION(socket_addrinfo_connect) default: zend_argument_value_error(1, "socket type must be one of AF_UNIX, AF_INET, or AF_INET6"); close(php_sock->bsd_socket); - zval_ptr_dtor(return_value); RETURN_THROWS(); } From 86aaded7e57dd2b2f5a4dd4e6a6d9511e6aec4e7 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 17 Jul 2025 21:51:46 +0200 Subject: [PATCH 10/56] NEWS for GH-19162 Closes GH-19162. --- NEWS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS b/NEWS index 06427de7f965..cbd68f4ce748 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.25 +- Hash: + . Fix crash on clone failure. (nielsdos) + - LDAP: . Fixed bug GH-18529 (additional inheriting of TLS int options). (Jakub Zelenka) @@ -11,6 +14,8 @@ PHP NEWS return value check). (nielsdos, botovq) . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) +- Sockets: + . Fix some potential crashes on incorrect argument value. (nielsdos) 31 Jul 2025, PHP 8.3.24 From be09985c870a72d93e53295897679f49c0f11a58 Mon Sep 17 00:00:00 2001 From: Petr Sumbera Date: Fri, 18 Jul 2025 17:29:02 +0200 Subject: [PATCH 11/56] Fix GH-19169: ZEND_STATIC_ASSERT for -std=c++17 needs to define ZEND_STATIC_ASSERT to appropriate C++ static_assert instead of the C version. --- NEWS | 4 ++++ Zend/zend_portability.h | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index cbd68f4ce748..cee68cfdd626 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.25 +- Core: + . Fixed GH-19169 build issue with C++17 and ZEND_STATIC_ASSERT macro. + (psumbera) + - Hash: . Fix crash on clone failure. (nielsdos) diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index 670687973148..2308d6cdb305 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -761,7 +761,9 @@ extern "C++" { /** @deprecated */ #define ZEND_CGG_DIAGNOSTIC_IGNORED_END ZEND_DIAGNOSTIC_IGNORED_END -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */ +#if defined(__cplusplus) +# define ZEND_STATIC_ASSERT(c, m) static_assert((c), m) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */ # define ZEND_STATIC_ASSERT(c, m) _Static_assert((c), m) #else # define ZEND_STATIC_ASSERT(c, m) From 312869381aaa993ae0429afd65535e3372b8dd99 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 19 Jul 2025 23:25:29 +0200 Subject: [PATCH 12/56] Fix GH-19098: libxml<2.13 segmentation fault caused by php_libxml_node_free This implements a workaround for reconciliation not being performed for document-less nodes in libxml<2.13. Closes GH-19186. --- NEWS | 4 +++ ext/libxml/libxml.c | 27 +++++++++++++++----- ext/xmlreader/tests/gh19098.phpt | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 ext/xmlreader/tests/gh19098.phpt diff --git a/NEWS b/NEWS index cee68cfdd626..6fe91bee5433 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,10 @@ PHP NEWS . Fixed bug GH-18529 (additional inheriting of TLS int options). (Jakub Zelenka) +- LibXML: + . Fixed bug GH-19098 (libxml<2.13 segmentation fault caused by + php_libxml_node_free). (nielsdos) + - OpenSSL: . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() return value check). (nielsdos, botovq) diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index 5c903d2c9a22..eecbca4ed890 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -331,13 +331,26 @@ PHP_LIBXML_API void php_libxml_node_free_list(xmlNodePtr node) if (curnode->type == XML_ELEMENT_NODE) { /* This ensures that namespace references in this subtree are defined within this subtree, * otherwise a use-after-free would be possible when the original namespace holder gets freed. */ -#if 0 - xmlDOMWrapCtxt dummy_ctxt = {0}; - xmlDOMWrapReconcileNamespaces(&dummy_ctxt, curnode, /* options */ 0); -#else - /* See php_dom.c */ - xmlReconciliateNs(curnode->doc, curnode); -#endif + if (LIBXML_VERSION < 21300 && UNEXPECTED(curnode->doc == NULL)) { + /* xmlReconciliateNs() in these versions just uses the document for xmlNewReconciledNs(), + * which can create an oldNs xml namespace declaration via xmlSearchNs() -> xmlTreeEnsureXMLDecl(). */ + xmlDoc dummy; + memset(&dummy, 0, sizeof(dummy)); + dummy.type = XML_DOCUMENT_NODE; + curnode->doc = &dummy; + xmlReconciliateNs(curnode->doc, curnode); + curnode->doc = NULL; + + /* Append oldNs to current node's nsDef, which can be at most one node. */ + if (dummy.oldNs) { + ZEND_ASSERT(dummy.oldNs->next == NULL); + xmlNsPtr old = curnode->nsDef; + curnode->nsDef = dummy.oldNs; + dummy.oldNs->next = old; + } + } else { + xmlReconciliateNs(curnode->doc, curnode); + } } /* Skip freeing */ curnode = next; diff --git a/ext/xmlreader/tests/gh19098.phpt b/ext/xmlreader/tests/gh19098.phpt new file mode 100644 index 000000000000..13a6eda328f2 --- /dev/null +++ b/ext/xmlreader/tests/gh19098.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-19098 (libxml<2.13 segmentation fault caused by php_libxml_node_free) +--EXTENSIONS-- +xmlreader +dom +--FILE-- + + + + +'); + +$success = $xml_reader->next("sparql"); + +$success = $xml_reader->read(); +$success = $xml_reader->next("results"); + +while ($xml_reader->read()) { + if ($xml_reader->next("result")) { + $result_as_dom_node = $xml_reader->expand(); + $child = $result_as_dom_node->firstChild; + unset($result_as_dom_node); + var_dump($child->namespaceURI); + foreach ($child->attributes as $attr) { + var_dump($attr->namespaceURI); + } + $doc = new DOMDocument; + $doc->adoptNode($child); + echo $doc->saveXML($child), "\n"; + unset($child); + break; + } +} + +?> +--EXPECT-- +string(38) "http://www.w3.org/2005/sparql-results#" +string(36) "http://www.w3.org/XML/1998/namespace" +string(10) "urn:custom" +NULL + From 6cc4ae1f1dcf5128b8aa610fbfea8bcba270ceb1 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 19 Jul 2025 12:25:21 +0200 Subject: [PATCH 13/56] Fix GH-18640: heap-use-after-free ext/soap/php_encoding.c:299:32 in soap_check_zval_ref For attributes, relying on the ref_map doesn't make sense the first place as you can't really refer to attributes from attributes. The code therefore assumes that the node is unique, which is broken. Closes GH-19181. --- NEWS | 4 +++ ext/soap/php_encoding.c | 14 +++++++++++ ext/soap/tests/bugs/gh18640.phpt | 42 ++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 ext/soap/tests/bugs/gh18640.phpt diff --git a/NEWS b/NEWS index 6fe91bee5433..6d020762eeee 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,10 @@ PHP NEWS return value check). (nielsdos, botovq) . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) +- SOAP: + . Fixed bug GH-18640 (heap-use-after-free ext/soap/php_encoding.c:299:32 + in soap_check_zval_ref). (nielsdos) + - Sockets: . Fix some potential crashes on incorrect argument value. (nielsdos) diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index 27b2c4e652b9..0b4b553d8754 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -1936,6 +1936,11 @@ static xmlNodePtr to_xml_object(encodeTypePtr type, zval *data, int style, xmlNo sdlAttributePtr attr; zval *zattr, rv; + /* Attributes can't refer to other attributes as there's nothing to attach the href to. */ + HashTable **ref_map = &SOAP_GLOBAL(ref_map); + HashTable *old_ref_map = *ref_map; + *ref_map = NULL; + ZEND_HASH_FOREACH_PTR(sdlType->attributes, attr) { if (attr->name) { zattr = get_zval_property(data, attr->name, &rv); @@ -1965,6 +1970,8 @@ static xmlNodePtr to_xml_object(encodeTypePtr type, zval *data, int style, xmlNo } } } ZEND_HASH_FOREACH_END(); + + *ref_map = old_ref_map; } } if (style == SOAP_ENCODED) { @@ -3034,6 +3041,12 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP ret = xmlNewNode(NULL, BAD_CAST("BOGUS")); xmlAddChild(parent, ret); FIND_ZVAL_NULL(data, ret, style); + + /* Literals are unique and can't refer to other references via attributes. */ + HashTable **ref_map = &SOAP_GLOBAL(ref_map); + HashTable *old_ref_map = *ref_map; + *ref_map = NULL; + if (Z_TYPE_P(data) == IS_ARRAY) { zval *tmp; smart_str list = {0}; @@ -3108,6 +3121,7 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP zval_ptr_dtor_str(&tmp); } } + *ref_map = old_ref_map; return ret; } diff --git a/ext/soap/tests/bugs/gh18640.phpt b/ext/soap/tests/bugs/gh18640.phpt new file mode 100644 index 000000000000..493659eca30c --- /dev/null +++ b/ext/soap/tests/bugs/gh18640.phpt @@ -0,0 +1,42 @@ +--TEST--- +GH-18640 (heap-use-after-free ext/soap/php_encoding.c:299:32 in soap_check_zval_ref) +--EXTENSIONS-- +soap +--CREDITS-- +YuanchengJiang +--FILE-- + 1, 'classmap' => ['logOnEvent' => 'LogOnEvent', 'events' => 'IVREvents']]); +$timestamp = new LogOnEvent(); // Bogus! +$logOffEvents[] = new LogOffEvent($timestamp); +$logOffEvents[] = new LogOffEvent($timestamp); +$ivrEvents = new IVREvents($logOffEvents); +$result = $soapClient->PostEvents($ivrEvents); + +class LogOffEvent { + function __construct(public $timestamp) { + $this->timestamp = $timestamp; + } +} + +class LogOnEvent { +} + +class IVREvents { + function __construct(public $logOffEvent) { + } +} +?> +--EXPECT-- +string(359) " + +" From 605ee054915beff79811c2b2868888426ce16ff2 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:19:48 +0200 Subject: [PATCH 14/56] Fix test conflict between chmod_variation2 and file_variation5 Both used "somelink". See https://github.com/php/php-src/actions/runs/16427526464/job/46421461376 Closes GH-19208. --- ext/standard/tests/file/chmod_variation2.phpt | 2 +- ext/standard/tests/file/file_variation5.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/standard/tests/file/chmod_variation2.phpt b/ext/standard/tests/file/chmod_variation2.phpt index e96af25ec469..c2b6c2e810a3 100644 --- a/ext/standard/tests/file/chmod_variation2.phpt +++ b/ext/standard/tests/file/chmod_variation2.phpt @@ -34,7 +34,7 @@ clearstatcache(); printf("%o\n", fileperms($filepath) & PERMISSIONS_MASK); echo "\nchmod() on a linked file\n"; -$linkname = "somelink"; +$linkname = "somelink2"; var_dump(symlink($filepath, $linkname)); var_dump(chmod($filepath, 0777)); var_dump(chmod($linkname, 0755)); diff --git a/ext/standard/tests/file/file_variation5.phpt b/ext/standard/tests/file/file_variation5.phpt index 7f012b7d431e..65e68ab0927d 100644 --- a/ext/standard/tests/file/file_variation5.phpt +++ b/ext/standard/tests/file/file_variation5.phpt @@ -27,7 +27,7 @@ echo "\nfile() on a path containing .. with invalid directories\n"; var_dump(file("./$test_dirname/bad_dir/../../$filename")); echo "\nfile() on a linked file\n"; -$linkname = "somelink"; +$linkname = "somelink5"; var_dump(symlink($filepath, $linkname)); var_dump(file($linkname)); var_dump(unlink($linkname)); From 13c781f04df4c3ee4e57ff96df67e5868ffdadb8 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 21 Jul 2025 22:15:23 +0200 Subject: [PATCH 15/56] Add missing cc clobber Closes GH-19205. --- Zend/zend_multiply.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Zend/zend_multiply.h b/Zend/zend_multiply.h index a99e858bd779..b3f0086009f4 100644 --- a/Zend/zend_multiply.h +++ b/Zend/zend_multiply.h @@ -176,13 +176,15 @@ static zend_always_inline size_t zend_safe_address(size_t nmemb, size_t size, si __asm__ ("mull %3\n\tadcl $0,%1" : "=&a"(res), "=&d" (m_overflow) : "%0"(res), - "rm"(size)); + "rm"(size) + : "cc"); } else { __asm__ ("mull %3\n\taddl %4,%0\n\tadcl $0,%1" : "=&a"(res), "=&d" (m_overflow) : "%0"(res), "rm"(size), - "rm"(offset)); + "rm"(offset) + : "cc"); } if (UNEXPECTED(m_overflow)) { @@ -211,7 +213,8 @@ static zend_always_inline size_t zend_safe_address(size_t nmemb, size_t size, si "adc $0,%1" : "=&a"(res), "=&d" (m_overflow) : "%0"(res), - "rm"(size)); + "rm"(size) + : "cc"); } else { __asm__ ("mul" LP_SUFF " %3\n\t" "add %4,%0\n\t" @@ -219,7 +222,8 @@ static zend_always_inline size_t zend_safe_address(size_t nmemb, size_t size, si : "=&a"(res), "=&d" (m_overflow) : "%0"(res), "rm"(size), - "rm"(offset)); + "rm"(offset) + : "cc"); } #undef LP_SUFF if (UNEXPECTED(m_overflow)) { From 5d1636e40bb6c9e5641024e532aa97851a7d9837 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 22 Jul 2025 12:38:06 +0200 Subject: [PATCH 16/56] Leak in failed unserialize() with opcache With opcache, zend_string_init_interned() will allocate non-interned strings at runtime because shm is locked. Hence, we need to make sure to actually free this string. Fixes OSS-Fuzz #433303828 Closes GH-19211 --- NEWS | 4 ++++ .../tests/serialize/oss_fuzz_433303828.phpt | 13 +++++++++++++ ext/standard/var_unserializer.re | 2 ++ 3 files changed, 19 insertions(+) create mode 100644 ext/standard/tests/serialize/oss_fuzz_433303828.phpt diff --git a/NEWS b/NEWS index 6d020762eeee..5fdee5f12103 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,10 @@ PHP NEWS - Sockets: . Fix some potential crashes on incorrect argument value. (nielsdos) +- Standard: + . Fixed OSS Fuzz #417078295 (Leak in failed unserialize() with opcache). + (ilutov) + 31 Jul 2025, PHP 8.3.24 - Calendar: diff --git a/ext/standard/tests/serialize/oss_fuzz_433303828.phpt b/ext/standard/tests/serialize/oss_fuzz_433303828.phpt new file mode 100644 index 000000000000..fb90b51d4dad --- /dev/null +++ b/ext/standard/tests/serialize/oss_fuzz_433303828.phpt @@ -0,0 +1,13 @@ +--TEST-- +OSS-Fuzz #433303828 +--FILE-- + +--EXPECTF-- +Warning: unserialize(): Error at offset 9 of 10 bytes in %s on line %d + +Warning: unserialize(): Error at offset 10 of 11 bytes in %s on line %d diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index a050fb5f74a7..1d23269ef03d 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -1310,10 +1310,12 @@ object ":" uiv ":" ["] { YYCURSOR = *p; if (*(YYCURSOR) != ':') { + zend_string_release_ex(class_name, 0); return 0; } if (*(YYCURSOR+1) != '{') { *p = YYCURSOR+1; + zend_string_release_ex(class_name, 0); return 0; } From a48d620c262bb96b75990a4a9aca5ad3698a1ec8 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 22 Jul 2025 15:45:48 +0200 Subject: [PATCH 17/56] [skip ci] Fix wrong oss-fuzz ID Oops, darn copy & paste. --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 5fdee5f12103..c7d7ef1c2b58 100644 --- a/NEWS +++ b/NEWS @@ -30,7 +30,7 @@ PHP NEWS . Fix some potential crashes on incorrect argument value. (nielsdos) - Standard: - . Fixed OSS Fuzz #417078295 (Leak in failed unserialize() with opcache). + . Fixed OSS Fuzz #433303828 (Leak in failed unserialize() with opcache). (ilutov) 31 Jul 2025, PHP 8.3.24 From be8819259417d63b389eb9ea6c00abd6aeb8e1c6 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:30:40 +0200 Subject: [PATCH 18/56] Run FreebSD CI under 13.5 13.3 gives a 404 now. Also pulls in a 8.4 fix to include xxhash from the bundled location. Closes GH-19213. --- .github/actions/freebsd/action.yml | 2 +- ext/hash/php_hash_xxhash.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml index fd37e92bbe5f..17446311bb86 100644 --- a/.github/actions/freebsd/action.yml +++ b/.github/actions/freebsd/action.yml @@ -9,7 +9,7 @@ runs: - name: FreeBSD uses: vmactions/freebsd-vm@v1 with: - release: '13.3' + release: '13.5' usesh: true copyback: false # Temporarily disable sqlite, as FreeBSD ships it with disabled double quotes. We'll need to fix our tests. diff --git a/ext/hash/php_hash_xxhash.h b/ext/hash/php_hash_xxhash.h index a1e8840ce272..ace70deedb00 100644 --- a/ext/hash/php_hash_xxhash.h +++ b/ext/hash/php_hash_xxhash.h @@ -18,7 +18,7 @@ #define PHP_HASH_XXHASH_H #define XXH_INLINE_ALL 1 -#include "xxhash.h" +#include "xxhash/xxhash.h" typedef struct { XXH32_state_t s; From 23ec35bf4a89b6879f1a90c097516cffbeb7d16d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 16 Jul 2025 23:07:40 +0200 Subject: [PATCH 19/56] Coerce numeric string keys from iterators when argument unpacking Fixes GH-18581 Closes GH-19151 --- NEWS | 2 ++ Zend/tests/gh18581.phpt | 32 ++++++++++++++++++++++++++++++++ Zend/zend_vm_def.h | 5 +++++ Zend/zend_vm_execute.h | 5 +++++ 4 files changed, 44 insertions(+) create mode 100644 Zend/tests/gh18581.phpt diff --git a/NEWS b/NEWS index c7d7ef1c2b58..89f24c4bfc45 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ PHP NEWS - Core: . Fixed GH-19169 build issue with C++17 and ZEND_STATIC_ASSERT macro. (psumbera) + . Fixed bug GH-18581 (Coerce numeric string keys from iterators when argument + unpacking). (ilutov) - Hash: . Fix crash on clone failure. (nielsdos) diff --git a/Zend/tests/gh18581.phpt b/Zend/tests/gh18581.phpt new file mode 100644 index 000000000000..cc5c0fff02e9 --- /dev/null +++ b/Zend/tests/gh18581.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-18581: Coerce numeric string keys from iterators when argument unpacking +--FILE-- + 'first'; + yield '101' => 'second'; + yield '102' => 'third'; + yield 'named' => 'fourth'; +} + +function test($x = null, $y = null, ...$z) { + var_dump($x, $y, $z); + var_dump($z[0]); + var_dump($z['named']); +} + +test(...g()); + +?> +--EXPECT-- +string(5) "first" +string(6) "second" +array(2) { + [0]=> + string(5) "third" + ["named"]=> + string(6) "fourth" +} +string(5) "third" +string(6) "fourth" diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0eacdfe145d4..7c9f151134fe 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5236,6 +5236,11 @@ ZEND_VM_C_LABEL(send_again): } name = Z_STR_P(&key); + + zend_ulong tmp; + if (ZEND_HANDLE_NUMERIC(name, tmp)) { + name = NULL; + } } } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 4ea6b302c8cf..9daa00d97c41 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2355,6 +2355,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_UNPACK_SPEC_HANDLER(ZEND_ } name = Z_STR_P(&key); + + zend_ulong tmp; + if (ZEND_HANDLE_NUMERIC(name, tmp)) { + name = NULL; + } } } From ad2143f3b06f629c8e6a5d3a4aa409935182de59 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:28:14 +0200 Subject: [PATCH 20/56] Fix arginfo/zpp violation if zend_hrtime is not available Part of GH-19210. Closes GH-19218. --- ext/standard/hrtime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/hrtime.c b/ext/standard/hrtime.c index 6af8bfc96506..831e903e1cc3 100644 --- a/ext/standard/hrtime.c +++ b/ext/standard/hrtime.c @@ -46,7 +46,6 @@ delivered timestamp is monotonic and cannot be adjusted. */ PHP_FUNCTION(hrtime) { -#if ZEND_HRTIME_AVAILABLE bool get_as_num = 0; zend_hrtime_t t = zend_hrtime(); @@ -55,6 +54,7 @@ PHP_FUNCTION(hrtime) Z_PARAM_BOOL(get_as_num) ZEND_PARSE_PARAMETERS_END(); +#if ZEND_HRTIME_AVAILABLE if (UNEXPECTED(get_as_num)) { PHP_RETURN_HRTIME(t); } else { From beeeee29783604b80be465d738dc4eee635161c1 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:32:16 +0200 Subject: [PATCH 21/56] Handle broken hrtime in ftp Part of GH-19210. Closes GH-19219. --- ext/ftp/ftp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index acdc1522e58f..3f9a78a81f0f 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -1471,7 +1471,8 @@ static int my_poll(php_socket_t fd, int events, int timeout) { if (n == -1 && php_socket_errno() == EINTR) { zend_hrtime_t delta_ns = zend_hrtime() - start_ns; - if (delta_ns > timeout_hr) { + /* delta_ns == 0 is only possible with a platform that does not support a high-res timer. */ + if (delta_ns > timeout_hr || UNEXPECTED(delta_ns == 0)) { #ifndef PHP_WIN32 errno = ETIMEDOUT; #endif From f94c11fff8475acc7b8a39eb4433bd594f73ac3b Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 25 Jul 2025 12:04:40 +0200 Subject: [PATCH 22/56] NEWS for hrtime in FTP and standard --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 89f24c4bfc45..2b21969dffbc 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,9 @@ PHP NEWS . Fixed bug GH-18581 (Coerce numeric string keys from iterators when argument unpacking). (ilutov) +- FTP: + . Fix theoretical issues with hrtime() not being available. (nielsdos) + - Hash: . Fix crash on clone failure. (nielsdos) @@ -34,6 +37,7 @@ PHP NEWS - Standard: . Fixed OSS Fuzz #433303828 (Leak in failed unserialize() with opcache). (ilutov) + . Fix theoretical issues with hrtime() not being available. (nielsdos) 31 Jul 2025, PHP 8.3.24 From b63372058552fd7cae24a8684d8b2b8276b3a2fc Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sat, 26 Jul 2025 10:03:34 +0200 Subject: [PATCH 23/56] Add unique entry point for extra tests We are adding extra (non-phpt) test suites in [1] and [2]. In order to avoid touching CI files too often (which are maintained in 8.1 and merged in upper branches), we add a single entry point to call the extra tests. The entry point can be updated in branches without synchronizing all the way from 8.1. CI files still need to be touched to install dependencies of these tests, but this should be manageable as these do not change often and are the same in every branch. Closes GH-19242. [1] https://github.com/php/php-src/pull/16987 [2] https://github.com/php/php-src/pull/18939 --- .github/actions/apt-x32/action.yml | 5 +- .github/actions/apt-x64/action.yml | 5 +- .github/actions/extra-tests/action.yml | 7 ++ .github/actions/freebsd/action.yml | 13 ++- .github/workflows/nightly.yml | 11 +++ run-extra-tests.php | 123 +++++++++++++++++++++++++ 6 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 .github/actions/extra-tests/action.yml create mode 100755 run-extra-tests.php diff --git a/.github/actions/apt-x32/action.yml b/.github/actions/apt-x32/action.yml index 879300f99274..0147b15cfcf5 100644 --- a/.github/actions/apt-x32/action.yml +++ b/.github/actions/apt-x32/action.yml @@ -6,6 +6,8 @@ runs: run: | set -x + OPCACHE_TLS_TESTS_DEPS="gcc clang lld" + export DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 apt-get update -y | true @@ -50,4 +52,5 @@ runs: re2c \ unzip \ wget \ - zlib1g-dev:i386 + zlib1g-dev:i386 \ + $OPCACHE_TLS_TESTS_DEPS diff --git a/.github/actions/apt-x64/action.yml b/.github/actions/apt-x64/action.yml index 4e1a03dd58cc..15697ed9f2fa 100644 --- a/.github/actions/apt-x64/action.yml +++ b/.github/actions/apt-x64/action.yml @@ -6,6 +6,8 @@ runs: run: | set -x + OPCACHE_TLS_TESTS_DEPS="gcc clang lld" + sudo apt-get update sudo apt-get install \ bison \ @@ -58,4 +60,5 @@ runs: libqdbm-dev \ libjpeg-dev \ libpng-dev \ - libfreetype6-dev + libfreetype6-dev \ + $OPCACHE_TLS_TESTS_DEPS diff --git a/.github/actions/extra-tests/action.yml b/.github/actions/extra-tests/action.yml new file mode 100644 index 000000000000..053749606400 --- /dev/null +++ b/.github/actions/extra-tests/action.yml @@ -0,0 +1,7 @@ +name: Extra tests +runs: + using: composite + steps: + - shell: sh + run: | + sapi/cli/php run-extra-tests.php diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml index 17446311bb86..ce133925e252 100644 --- a/.github/actions/freebsd/action.yml +++ b/.github/actions/freebsd/action.yml @@ -3,6 +3,9 @@ inputs: configurationParameters: default: '' required: false + runExtraTests: + default: false + required: false runs: using: composite steps: @@ -17,6 +20,8 @@ runs: prepare: | cd $GITHUB_WORKSPACE + OPCACHE_TLS_TESTS_DEPS="gcc" + kldload accf_http pkg install -y \ autoconf \ @@ -41,9 +46,11 @@ runs: webp \ libavif \ `#sqlite3` \ - curl + curl \ + $OPCACHE_TLS_TESTS_DEPS ./buildconf -f + CC=clang CXX=clang++ \ ./configure \ --prefix=/usr/local \ --enable-debug \ @@ -106,3 +113,7 @@ runs: --show-slow 1000 \ --set-timeout 120 \ -d zend_extension=opcache.so + + if test "${{ inputs.runExtraTests }}" = "true"; then + sapi/cli/php run-extra-tests.php + fi diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5817c647a871..38ccb05e0992 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -85,6 +85,8 @@ jobs: with: runTestsParameters: >- --asan -x + - name: Extra tests + uses: ./.github/actions/extra-tests ALPINE: if: inputs.run_alpine name: ALPINE_X64_ASAN_UBSAN_DEBUG_ZTS @@ -134,6 +136,8 @@ jobs: --asan -x -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Notify Slack if: failure() uses: ./.github/actions/notify-slack @@ -266,6 +270,8 @@ jobs: ${{ matrix.run_tests_parameters }} -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files - name: Notify Slack @@ -355,6 +361,8 @@ jobs: ${{ matrix.run_tests_parameters }} -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Notify Slack if: failure() uses: ./.github/actions/notify-slack @@ -414,6 +422,8 @@ jobs: runTestsParameters: >- -d zend_extension=opcache.so -d opcache.enable_cli=1 + - name: Extra tests + uses: ./.github/actions/extra-tests - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files - name: Notify Slack @@ -1076,3 +1086,4 @@ jobs: with: configurationParameters: >- --${{ matrix.zts && 'enable' || 'disable' }}-zts + runExtraTests: true diff --git a/run-extra-tests.php b/run-extra-tests.php new file mode 100755 index 000000000000..a299addf4044 --- /dev/null +++ b/run-extra-tests.php @@ -0,0 +1,123 @@ +#!/usr/bin/env php +os}\n"; + echo "CPU Arch: {$environment->cpuArch}\n"; + echo "ZTS: " . ($environment->zts ? 'Yes' : 'No') . "\n"; + echo "DEBUG: " . ($environment->debug ? 'Yes' : 'No') . "\n"; + echo "=====================================================================\n"; + + echo "No tests in this branch yet.\n"; + + echo "All OK\n"; +} + +function output_group_start(Environment $environment, string $name): void +{ + if ($environment->githubAction) { + printf("::group::%s\n", $name); + } else { + printf("%s\n", $name); + } +} + +function output_group_end(Environment $environment): void +{ + if ($environment->githubAction) { + printf("::endgroup::\n"); + } +} + +/** + * Returns getenv('TEST_PHP_OS') if defined, otherwise returns one of + * 'Windows NT', 'Linux', 'FreeBSD', 'Darwin', ... + */ +function detect_os(): string +{ + $os = (string) getenv('TEST_PHP_OS'); + if ($os !== '') { + return $os; + } + + return php_uname('s'); +} + +/** + * Returns getenv('TEST_PHP_CPU_ARCH') if defined, otherwise returns one of + * 'x86', 'x86_64', 'aarch64', ... + */ +function detect_cpu_arch(): string +{ + $cpu = (string) getenv('TEST_PHP_CPU_ARCH'); + if ($cpu !== '') { + return $cpu; + } + + $cpu = php_uname('m'); + if (strtolower($cpu) === 'amd64') { + $cpu = 'x86_64'; + } else if (in_array($cpu, ['i386', 'i686'])) { + $cpu = 'x86'; + } else if ($cpu === 'arm64') { + $cpu = 'aarch64'; + } + + return $cpu; +} + +main($argc, $argv); From 5dd965117a1d88cbd99dac9bf097d82c83819f69 Mon Sep 17 00:00:00 2001 From: dixyes Date: Sat, 26 Jul 2025 00:41:59 +0800 Subject: [PATCH 24/56] Free opened_path when opened_path_len >= MAXPATHLEN Closes GH-19240. --- NEWS | 3 +++ main/php_open_temporary_file.c | 1 + 2 files changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 2b21969dffbc..94a14290a8bd 100644 --- a/NEWS +++ b/NEWS @@ -39,6 +39,9 @@ PHP NEWS (ilutov) . Fix theoretical issues with hrtime() not being available. (nielsdos) +- Windows: + . Free opened_path when opened_path_len >= MAXPATHLEN. (dixyes) + 31 Jul 2025, PHP 8.3.24 - Calendar: diff --git a/main/php_open_temporary_file.c b/main/php_open_temporary_file.c index dcea78358486..608f3ecf73ba 100644 --- a/main/php_open_temporary_file.c +++ b/main/php_open_temporary_file.c @@ -157,6 +157,7 @@ static int php_do_open_temporary_file(const char *path, const char *pfx, zend_st free(cwdw); free(pfxw); efree(new_state.cwd); + free(opened_path); return -1; } assert(strlen(opened_path) == opened_path_len); From e16df981bffe4b373c5fffd802f4182900feeab6 Mon Sep 17 00:00:00 2001 From: dixyes Date: Sat, 26 Jul 2025 00:34:40 +0800 Subject: [PATCH 25/56] ext/pdo_pgsql: Fix _pdo_pgsql_trim_message bad access close GH-19239 --- NEWS | 4 ++++ ext/pdo_pgsql/pgsql_driver.c | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 94a14290a8bd..9f714c37942a 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,10 @@ PHP NEWS return value check). (nielsdos, botovq) . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) +- PDO Pgsql: + . Fixed dangling pointer access on _pdo_pgsql_trim_message helper. + (dixyes) + - SOAP: . Fixed bug GH-18640 (heap-use-after-free ext/soap/php_encoding.c:299:32 in soap_check_zval_ref). (nielsdos) diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 1cccfd2ab07a..e9a6efe14fae 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -38,8 +38,14 @@ static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh); static char * _pdo_pgsql_trim_message(const char *message, int persistent) { - size_t i = strlen(message)-1; + size_t i = strlen(message); char *tmp; + if (UNEXPECTED(i == 0)) { + tmp = pemalloc(1, persistent); + tmp[0] = '\0'; + return tmp; + } + --i; if (i>1 && (message[i-1] == '\r' || message[i-1] == '\n') && message[i] == '.') { --i; From e1c4a0ae512c3059ff396b7450dfd4bac44ea289 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 27 Jul 2025 12:20:21 +0100 Subject: [PATCH 26/56] Fixed GH-19261: msgfmt_parse_message leaks on message format failure. close GH-19262 --- NEWS | 4 ++++ ext/intl/msgformat/msgformat_parse.c | 3 ++- ext/intl/tests/gh19261.phpt | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 ext/intl/tests/gh19261.phpt diff --git a/NEWS b/NEWS index 9f714c37942a..b4cc8d8429fa 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,10 @@ PHP NEWS - Hash: . Fix crash on clone failure. (nielsdos) +- Intl: + . Fixed GH-19261: msgfmt_parse_message leaks on message creation failure. + (David Carlier) + - LDAP: . Fixed bug GH-18529 (additional inheriting of TLS int options). (Jakub Zelenka) diff --git a/ext/intl/msgformat/msgformat_parse.c b/ext/intl/msgformat/msgformat_parse.c index baa6dfcce74b..31441c92373d 100644 --- a/ext/intl/msgformat/msgformat_parse.c +++ b/ext/intl/msgformat/msgformat_parse.c @@ -127,10 +127,11 @@ PHP_FUNCTION( msgfmt_parse_message ) if(spattern && spattern_len) { efree(spattern); } - INTL_METHOD_CHECK_STATUS(mfo, "Creating message formatter failed"); + INTL_METHOD_CHECK_STATUS_OR_GOTO(mfo, "Creating message formatter failed", clean); msgfmt_do_parse(mfo, source, src_len, return_value); +clean: /* drop the temporary formatter */ msgformat_data_free(&mfo->mf_data); } diff --git a/ext/intl/tests/gh19261.phpt b/ext/intl/tests/gh19261.phpt new file mode 100644 index 000000000000..3f281919a956 --- /dev/null +++ b/ext/intl/tests/gh19261.phpt @@ -0,0 +1,28 @@ +--TEST-- +MessageFormatter::parseMessage() with invalid locale +--EXTENSIONS-- +intl +--CREDITS-- +girgias@php.net +--FILE-- + +--EXPECT-- +bool(false) +string(59) "Creating message formatter failed: U_ILLEGAL_ARGUMENT_ERROR" +bool(false) +string(59) "Creating message formatter failed: U_ILLEGAL_ARGUMENT_ERROR" From 17df11e3f7b96f8fcce9ccf48a7b676256a03255 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Mon, 5 Aug 2024 15:54:50 +0200 Subject: [PATCH 27/56] Fix bug #51558: shared readline build fails (#15242) The 'rl_pending_input' is a variable in Readline library and checking it with PHP_CHECK_LIBRARY wouldn't find it on some systems. Library check works on most systems but not on the mentioned AIX in the bug as it exports variables and functions differently whereas the linker couldn't resolve the variable as a function. This should fix the build on systems where this caused issues, such as AIX. The is not self-contained header and needs to also have included before to have FILE type available. This fixes the issue on unpatched default readline installations, such as macOS. Checking this variable ensures that the found library is the correct library and also that it is of minimum version needed by current PHP code (https://bugs.php.net/48608). The library check: ```c | char rl_pending_input (); | int main (void) { | return rl_pending_input (); | } ``` The declaration check: ```c | #include | #include | int main (void) { | #ifndef rl_pending_input | #ifdef __cplusplus | (void) rl_pending_input; | #else | (void) rl_pending_input; | #endif | #endif | ; | return 0; | } ``` Closes https://bugs.php.net/51558 Closes GH-19259. --- NEWS | 4 ++++ ext/readline/config.m4 | 27 ++++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index b4cc8d8429fa..4bf10555b459 100644 --- a/NEWS +++ b/NEWS @@ -35,6 +35,10 @@ PHP NEWS . Fixed dangling pointer access on _pdo_pgsql_trim_message helper. (dixyes) +- Readline: + . Fixed bug GH-19250 and bug #51360 (Invalid conftest for rl_pending_input). + (petk, nielsdos) + - SOAP: . Fixed bug GH-18640 (heap-use-after-free ext/soap/php_encoding.c:299:32 in soap_check_zval_ref). (nielsdos) diff --git a/ext/readline/config.m4 b/ext/readline/config.m4 index 209f92b6e0a7..873b116fcffb 100644 --- a/ext/readline/config.m4 +++ b/ext/readline/config.m4 @@ -47,13 +47,6 @@ if test "$PHP_READLINE" && test "$PHP_READLINE" != "no"; then -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS ]) - PHP_CHECK_LIBRARY(readline, rl_pending_input, - [], [ - AC_MSG_ERROR([invalid readline installation detected. Try --with-libedit instead.]) - ], [ - -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS - ]) - PHP_CHECK_LIBRARY(readline, rl_callback_read_char, [ AC_DEFINE(HAVE_RL_CALLBACK_READ_CHAR, 1, [ ]) @@ -75,6 +68,26 @@ if test "$PHP_READLINE" && test "$PHP_READLINE" != "no"; then -L$READLINE_DIR/$PHP_LIBDIR $PHP_READLINE_LIBS ]) + CFLAGS_SAVE=$CFLAGS + LDFLAGS_SAVE=$LDFLAGS + LIBS_SAVE=$LIBS + CFLAGS="$CFLAGS $INCLUDES" + LDFLAGS="$LDFLAGS -L$READLINE_DIR/$PHP_LIBDIR" + LIBS="$LIBS -lreadline" + + dnl Sanity and minimum version check if readline library has variable + dnl rl_pending_input. + AC_CHECK_DECL([rl_pending_input],, [AC_MSG_FAILURE([ + Invalid readline installation detected. Try --with-libedit instead. + ])], [ + #include + #include + ]) + + CFLAGS=$CFLAGS_SAVE + LDFLAGS=$LDFLAGS_SAVE + LIBS=$LIBS_SAVE + AC_DEFINE(HAVE_HISTORY_LIST, 1, [ ]) AC_DEFINE(HAVE_LIBREADLINE, 1, [ ]) From 83b8d2c290d24221e95c8fa1dc57a1b85f21fb70 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 12 Jul 2025 18:30:08 +0100 Subject: [PATCH 28/56] ext/gd: Fix comparison with result of php_stream_can_cast() Closes GH-19107 --- NEWS | 3 +++ ext/gd/gd.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 4bf10555b459..704398f5b6c3 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,9 @@ PHP NEWS - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) +- GD: + . Fix incorrect comparison with result of php_stream_can_cast(). (Girgias) + - Hash: . Fix crash on clone failure. (nielsdos) diff --git a/ext/gd/gd.c b/ext/gd/gd.c index 6b727a211189..cce2a5ca42fc 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -1566,7 +1566,7 @@ static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, pefree(pstr, 1); zend_string_release_ex(buff, 0); } - else if (php_stream_can_cast(stream, PHP_STREAM_AS_STDIO)) { + else if (php_stream_can_cast(stream, PHP_STREAM_AS_STDIO) == SUCCESS) { /* try and force the stream to be FILE* */ if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO | PHP_STREAM_CAST_TRY_HARD, (void **) &fp, REPORT_ERRORS)) { goto out_err; From 6b0a80903bece4c383708b16cabc4659a0b6774a Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 28 Jul 2025 14:07:05 +0100 Subject: [PATCH 29/56] ext/intl: Fix return value on failure for resourcebundle count handler Closes GH-19277 --- NEWS | 1 + ext/intl/resourcebundle/resourcebundle_class.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 704398f5b6c3..a850699051ce 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,7 @@ PHP NEWS - Intl: . Fixed GH-19261: msgfmt_parse_message leaks on message creation failure. (David Carlier) + . Fix return value on failure for resourcebundle count handler. (Girgias) - LDAP: . Fixed bug GH-18529 (additional inheriting of TLS int options). diff --git a/ext/intl/resourcebundle/resourcebundle_class.c b/ext/intl/resourcebundle/resourcebundle_class.c index 9e86b41e761a..8a2ff19d60ef 100644 --- a/ext/intl/resourcebundle/resourcebundle_class.c +++ b/ext/intl/resourcebundle/resourcebundle_class.c @@ -257,7 +257,7 @@ static zend_result resourcebundle_array_count(zend_object *object, zend_long *co if (rb->me == NULL) { intl_errors_set(&rb->error, U_ILLEGAL_ARGUMENT_ERROR, "Found unconstructed ResourceBundle", 0); - return 0; + return FAILURE; } *count = ures_getSize( rb->me ); From 3aaa8d3526c53f3e3a9901853910401bd3eb3aa9 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Tue, 22 Jul 2025 10:39:56 +0200 Subject: [PATCH 30/56] Reset global pointers to prevent use-after-free Closes GH-19212. --- NEWS | 4 +++- ext/opcache/jit/zend_jit.c | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index d52050e3a8e3..a05b577d101b 100644 --- a/NEWS +++ b/NEWS @@ -2,7 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.1.34 - +- Opcache: + . Reset global pointers to prevent use-after-free in zend_jit_status(). + (Florian Engelhardt) 03 Jul 2025, PHP 8.1.33 diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 4d2baddb9009..f0225b0c7a88 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -5087,6 +5087,14 @@ ZEND_EXT_API void zend_jit_shutdown(void) #else zend_jit_trace_free_caches(&jit_globals); #endif + + /* Reset global pointers to prevent use-after-free in `zend_jit_status()` + * after gracefully restarting Apache with mod_php, see: + * https://github.com/php/php-src/pull/19212 */ + dasm_ptr = NULL; + dasm_buf = NULL; + dasm_end = NULL; + dasm_size = 0; } static void zend_jit_reset_counters(void) From 80022c035ba153ebf23b17da5fcb30993df7c76b Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 29 Jul 2025 22:34:47 +0200 Subject: [PATCH 31/56] Fix failed assertion with throwing __toString in binary const expr Solve this with the same pattern as ZEND_AST_GREATER[_EQUAL]. Fixes OSS-Fuzz #434346548 Closes GH-19291 --- NEWS | 2 ++ Zend/tests/oss_fuzz_434346548.phpt | 22 ++++++++++++++++++++++ Zend/zend_ast.c | 3 ++- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/oss_fuzz_434346548.phpt diff --git a/NEWS b/NEWS index f9cac590c867..4cd237ffeedd 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ PHP NEWS (psumbera) . Fixed bug GH-18581 (Coerce numeric string keys from iterators when argument unpacking). (ilutov) + . Fixed OSS-Fuzz #434346548 (Failed assertion with throwing __toString in + binary const expr). (ilutov) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/oss_fuzz_434346548.phpt b/Zend/tests/oss_fuzz_434346548.phpt new file mode 100644 index 000000000000..139e36758bc4 --- /dev/null +++ b/Zend/tests/oss_fuzz_434346548.phpt @@ -0,0 +1,22 @@ +--TEST-- +OSS-Fuzz #434346548: Failed assertion with throwing __toString in binary const expr +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +Foo::__toString(): Return value must be of type string, none returned diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index f8c4ca17a9b9..1ecb85f1d2a2 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -548,9 +548,10 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( ret = FAILURE; } else { binary_op_type op = get_binary_op(ast->attr); - ret = op(result, &op1, &op2); + op(result, &op1, &op2); zval_ptr_dtor_nogc(&op1); zval_ptr_dtor_nogc(&op2); + ret = EG(exception) ? FAILURE : SUCCESS; } break; case ZEND_AST_GREATER: From bc4b6ce7a87be88ff9d85a32b5fcb4b9477ae788 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 30 Jul 2025 14:57:52 +0200 Subject: [PATCH 32/56] Prevent operands from being released during comparison Fixes GH-19305 Closes GH-19309 --- NEWS | 2 ++ Zend/tests/gh19305-001.phpt | 27 +++++++++++++++++++++++++++ Zend/tests/gh19305-002.phpt | 27 +++++++++++++++++++++++++++ Zend/tests/gh19305-003.phpt | 28 ++++++++++++++++++++++++++++ Zend/zend_object_handlers.c | 33 ++++++++++++++++++++++++++------- Zend/zend_operators.c | 14 +++++++++++++- Zend/zend_types.h | 12 ++++++++++++ 7 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/gh19305-001.phpt create mode 100644 Zend/tests/gh19305-002.phpt create mode 100644 Zend/tests/gh19305-003.phpt diff --git a/NEWS b/NEWS index 4cd237ffeedd..fb6a541bbf66 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,8 @@ PHP NEWS unpacking). (ilutov) . Fixed OSS-Fuzz #434346548 (Failed assertion with throwing __toString in binary const expr). (ilutov) + . Fixed bug GH-19305 (Operands may be being released during comparison). + (Arnaud) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/gh19305-001.phpt b/Zend/tests/gh19305-001.phpt new file mode 100644 index 000000000000..4c9ee37473e3 --- /dev/null +++ b/Zend/tests/gh19305-001.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-19305 001: Operands may be released during comparison +--FILE-- + 'test', + 'bar' => 2, +]; +$b = (object)[ + 'foo' => new class { + public function __toString() { + global $a, $b; + $a = $b = null; + return ''; + } + }, + 'bar' => 2, +]; + +// Comparison of $a->foo and $b->foo calls __toString(), which releases +// both $a and $b. +var_dump($a > $b); + +?> +--EXPECT-- +bool(true) diff --git a/Zend/tests/gh19305-002.phpt b/Zend/tests/gh19305-002.phpt new file mode 100644 index 000000000000..790156191317 --- /dev/null +++ b/Zend/tests/gh19305-002.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-19305 002: Operands may be released during comparison +--FILE-- + 'test', + 'bar' => 2, +]; +$b = [ + 'foo' => new class { + public function __toString() { + global $a, $b; + $a = $b = null; + return ''; + } + }, + 'bar' => 2, +]; + +// Comparison of $a['foo'] and $b['foo'] calls __toString(), which releases +// both $a and $b. +var_dump($a > $b); + +?> +--EXPECT-- +bool(true) diff --git a/Zend/tests/gh19305-003.phpt b/Zend/tests/gh19305-003.phpt new file mode 100644 index 000000000000..7b071156ef8e --- /dev/null +++ b/Zend/tests/gh19305-003.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-19305 003: Operands may be released during comparison +--SKIPIF-- + +--FILE-- +newLazyProxy(function () { return new C; }); + +// Comparison calls initializers, which releases $o +var_dump($o > +$r->newLazyGhost(function () { + global $o; + $o = null; +})); + +?> +--EXPECT-- +bool(false) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 180364b248d7..2eacb614f97c 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1792,6 +1792,10 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ } Z_PROTECT_RECURSION_P(o1); + GC_ADDREF(zobj1); + GC_ADDREF(zobj2); + int ret; + for (i = 0; i < zobj1->ce->default_properties_count; i++) { zval *p1, *p2; @@ -1806,35 +1810,50 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ if (Z_TYPE_P(p1) != IS_UNDEF) { if (Z_TYPE_P(p2) != IS_UNDEF) { - int ret; - ret = zend_compare(p1, p2); if (ret != 0) { Z_UNPROTECT_RECURSION_P(o1); - return ret; + goto done; } } else { Z_UNPROTECT_RECURSION_P(o1); - return 1; + ret = 1; + goto done; } } else { if (Z_TYPE_P(p2) != IS_UNDEF) { Z_UNPROTECT_RECURSION_P(o1); - return 1; + ret = 1; + goto done; } } } Z_UNPROTECT_RECURSION_P(o1); - return 0; + ret = 0; + +done: + OBJ_RELEASE(zobj1); + OBJ_RELEASE(zobj2); + + return ret; } else { + GC_ADDREF(zobj1); + GC_ADDREF(zobj2); + if (!zobj1->properties) { rebuild_object_properties(zobj1); } if (!zobj2->properties) { rebuild_object_properties(zobj2); } - return zend_compare_symbol_tables(zobj1->properties, zobj2->properties); + + int ret = zend_compare_symbol_tables(zobj1->properties, zobj2->properties); + + OBJ_RELEASE(zobj1); + OBJ_RELEASE(zobj2); + + return ret; } } /* }}} */ diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 38c87dfe98db..8646fc25be26 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -3384,7 +3384,19 @@ static int hash_zval_compare_function(zval *z1, zval *z2) /* {{{ */ ZEND_API int ZEND_FASTCALL zend_compare_symbol_tables(HashTable *ht1, HashTable *ht2) /* {{{ */ { - return ht1 == ht2 ? 0 : zend_hash_compare(ht1, ht2, (compare_func_t) hash_zval_compare_function, 0); + if (ht1 == ht2) { + return 0; + } + + GC_TRY_ADDREF(ht1); + GC_TRY_ADDREF(ht2); + + int ret = zend_hash_compare(ht1, ht2, (compare_func_t) hash_zval_compare_function, 0); + + GC_TRY_DTOR_NO_REF(ht1); + GC_TRY_DTOR_NO_REF(ht2); + + return ret; } /* }}} */ diff --git a/Zend/zend_types.h b/Zend/zend_types.h index c4a07f58874a..63f6ddbc9724 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -731,6 +731,18 @@ static zend_always_inline uint8_t zval_get_type(const zval* pz) { } \ } while (0) +#define GC_TRY_DTOR_NO_REF(p) \ + do { \ + zend_refcounted_h *_p = &(p)->gc; \ + if (!(_p->u.type_info & GC_IMMUTABLE)) { \ + if (zend_gc_delref(_p) == 0) { \ + rc_dtor_func((zend_refcounted *)_p); \ + } else { \ + gc_check_possible_root_no_ref((zend_refcounted *)_p); \ + } \ + } \ + } while (0) + #define GC_TYPE_MASK 0x0000000f #define GC_FLAGS_MASK 0x000003f0 #define GC_INFO_MASK 0xfffffc00 From 5bd5f352e5aeeda3154deffee21caa227e0ce48b Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:44:31 +0200 Subject: [PATCH 33/56] Fix GH-19303: Unpacking empty packed array into uninitialized array causes assertion failure Having an empty result array is not a problem, because zend_hash_extend() will initialize it. Except it does not when the number of elements to add equals 0, which leaves the array uninitialized and therefore does not set the packed flag, causing the assertion failure. Technically, removing the assert would also work and save a check. On the other hand, this check could also prevent some real work to be done and should be relatively cheap as we already have to compute the sum anyway. Closes GH-19318. --- NEWS | 2 ++ Zend/tests/array_unpack/gh19303.phpt | 11 +++++++++++ Zend/zend_vm_def.h | 27 ++++++++++++++++----------- Zend/zend_vm_execute.h | 27 ++++++++++++++++----------- 4 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 Zend/tests/array_unpack/gh19303.phpt diff --git a/NEWS b/NEWS index fb6a541bbf66..ced51075fab1 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,8 @@ PHP NEWS binary const expr). (ilutov) . Fixed bug GH-19305 (Operands may be being released during comparison). (Arnaud) + . Fixed bug GH-19303 (Unpacking empty packed array into uninitialized array + causes assertion failure). (nielsdos) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/array_unpack/gh19303.phpt b/Zend/tests/array_unpack/gh19303.phpt new file mode 100644 index 000000000000..af594c3740c2 --- /dev/null +++ b/Zend/tests/array_unpack/gh19303.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-19303 (Unpacking empty packed array into uninitialized array causes assertion failure) +--FILE-- + +--EXPECT-- +array(0) { +} diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 7c9f151134fe..f1d4f7448ce1 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6162,17 +6162,22 @@ ZEND_VM_C_LABEL(add_unpack_again): zval *val; if (HT_IS_PACKED(ht) && (zend_hash_num_elements(result_ht) == 0 || HT_IS_PACKED(result_ht))) { - zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); - ZEND_HASH_FILL_PACKED(result_ht) { - ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { - if (UNEXPECTED(Z_ISREF_P(val)) && - UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { - val = Z_REFVAL_P(val); - } - Z_TRY_ADDREF_P(val); - ZEND_HASH_FILL_ADD(val); - } ZEND_HASH_FOREACH_END(); - } ZEND_HASH_FILL_END(); + /* zend_hash_extend() skips initialization when the number of elements is 0, + * but the code below expects that result_ht is initialized as packed. + * We can just skip the work in that case. */ + if (result_ht->nNumUsed + zend_hash_num_elements(ht) > 0) { + zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); + ZEND_HASH_FILL_PACKED(result_ht) { + ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { + if (UNEXPECTED(Z_ISREF_P(val)) && + UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { + val = Z_REFVAL_P(val); + } + Z_TRY_ADDREF_P(val); + ZEND_HASH_FILL_ADD(val); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } } else { zend_string *key; diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 9daa00d97c41..5f7f4997ce8d 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2657,17 +2657,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_ARRAY_UNPACK_SPEC_HANDLER( zval *val; if (HT_IS_PACKED(ht) && (zend_hash_num_elements(result_ht) == 0 || HT_IS_PACKED(result_ht))) { - zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); - ZEND_HASH_FILL_PACKED(result_ht) { - ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { - if (UNEXPECTED(Z_ISREF_P(val)) && - UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { - val = Z_REFVAL_P(val); - } - Z_TRY_ADDREF_P(val); - ZEND_HASH_FILL_ADD(val); - } ZEND_HASH_FOREACH_END(); - } ZEND_HASH_FILL_END(); + /* zend_hash_extend() skips initialization when the number of elements is 0, + * but the code below expects that result_ht is initialized as packed. + * We can just skip the work in that case. */ + if (result_ht->nNumUsed + zend_hash_num_elements(ht) > 0) { + zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); + ZEND_HASH_FILL_PACKED(result_ht) { + ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { + if (UNEXPECTED(Z_ISREF_P(val)) && + UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { + val = Z_REFVAL_P(val); + } + Z_TRY_ADDREF_P(val); + ZEND_HASH_FILL_ADD(val); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } } else { zend_string *key; From 0406a55c923bc61e3a09ed273ae9b834d01cb951 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 30 Jul 2025 16:56:55 +0200 Subject: [PATCH 34/56] Prevent resumption of generator suspended in yield from Normally we prevent generators from being resumed while they are already running, but we failed to do so for generators delegating to non-Generators. As a result such generator can be resumed, terminated, which causes unexpected results (crashes) later. In gh19306.phpt in particular, the generator delegate It::getIterator() suspends while being called by generator g(). We then resume g(), which throws while trying to resume It::getIterator(). This causes g() and It::getIterator() to be released. We then UAF when resuming the Fiber in It::getIterator(). Fix this by ensuring that generators are marked as running while they fetch the next value from the delegate. Fixes GH-19306 Closes GH-19315 --- NEWS | 2 ++ Zend/tests/gh19306.phpt | 40 ++++++++++++++++++++++++++++++++++++++++ Zend/zend_generators.c | 5 +++-- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/gh19306.phpt diff --git a/NEWS b/NEWS index ced51075fab1..9f1f48a9ee95 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,8 @@ PHP NEWS (Arnaud) . Fixed bug GH-19303 (Unpacking empty packed array into uninitialized array causes assertion failure). (nielsdos) + . Fixed bug GH-19306 (Generator can be resumed while fetching next value from + delegated Generator). (Arnaud) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/gh19306.phpt b/Zend/tests/gh19306.phpt new file mode 100644 index 000000000000..e19735d94c84 --- /dev/null +++ b/Zend/tests/gh19306.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-19306: Generator suspended in yield from may be resumed +--FILE-- +next(); + echo "Fiber return\n"; +}); +$fiber->start(); +echo "Fiber suspended\n"; +try { + $a->next(); +} catch (Throwable $t) { + echo $t->getMessage(), "\n"; +} +echo "Destroying fiber\n"; +$fiber = null; +echo "Shutdown\n"; +?> +--EXPECT-- +Fiber start +Fiber suspended +Cannot resume an already running generator +Destroying fiber +Shutdown diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index f82dd4c1daf1..3ba582fef6e7 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -787,6 +787,8 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ orig_generator->execute_fake.prev_execute_data = original_execute_data; } + generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; + /* Ensure this is run after executor_data swap to have a proper stack trace */ if (UNEXPECTED(!Z_ISUNDEF(generator->values))) { if (EXPECTED(zend_generator_get_next_delegated_value(generator) == SUCCESS)) { @@ -795,7 +797,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ EG(jit_trace_num) = original_jit_trace_num; orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER); - generator->flags &= ~ZEND_GENERATOR_IN_FIBER; + generator->flags &= ~(ZEND_GENERATOR_CURRENTLY_RUNNING | ZEND_GENERATOR_IN_FIBER); return; } if (UNEXPECTED(EG(exception))) { @@ -817,7 +819,6 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ } /* Resume execution */ - generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; if (!ZEND_OBSERVER_ENABLED) { zend_execute_ex(generator->execute_data); } else { From 6fa8a25a405495c868ef643f41a7b837762b21b3 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 31 Jul 2025 09:18:23 +0200 Subject: [PATCH 35/56] Prevent throwing in running generator Generator::throw() on a running generator is not allowed. It throws "Cannot resume an already running generator" when trying to resume the generator to handle the provided exception. However, when calling Generator::throw() on a generator with a non-Generator delegate, we release the delegate regardless. If a Fiber was suspended in the delegate, this causes use after frees when the Fiber is resumed. Fix this by throwing "Cannot resume an already running generator" earlier. Fixes GH-19326 Closes GH-19327 --- NEWS | 2 ++ Zend/tests/gh19326.phpt | 36 ++++++++++++++++++++++++++++++++++++ Zend/zend_generators.c | 14 ++++++++++++-- 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/gh19326.phpt diff --git a/NEWS b/NEWS index 9f1f48a9ee95..0b261679e788 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,8 @@ PHP NEWS causes assertion failure). (nielsdos) . Fixed bug GH-19306 (Generator can be resumed while fetching next value from delegated Generator). (Arnaud) + . Fixed bug GH-19326 (Calling Generator::throw() on a running generator with + a non-Generator delegate crashes). (Arnaud) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/gh19326.phpt b/Zend/tests/gh19326.phpt new file mode 100644 index 000000000000..335fdd382ea6 --- /dev/null +++ b/Zend/tests/gh19326.phpt @@ -0,0 +1,36 @@ +--TEST-- +GH-19326: Calling Generator::throw() on a running generator with a non-Generator delegate crashes +--FILE-- +rewind(); + +$fiber = new Fiber(function () use ($b) { + $b->next(); +}); + +$fiber->start(); + +try { + $b->throw(new Exception('test')); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$fiber->resume(); + +?> +--EXPECT-- +Cannot resume an already running generator diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index 3ba582fef6e7..eeab16b9a135 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -492,8 +492,14 @@ ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_ return ptr; } -static void zend_generator_throw_exception(zend_generator *generator, zval *exception) +static zend_result zend_generator_throw_exception(zend_generator *generator, zval *exception) { + if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) { + zval_ptr_dtor(exception); + zend_throw_error(NULL, "Cannot resume an already running generator"); + return FAILURE; + } + zend_execute_data *original_execute_data = EG(current_execute_data); /* Throw the exception in the context of the generator. Decrementing the opline @@ -519,6 +525,8 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce } EG(current_execute_data) = original_execute_data; + + return SUCCESS; } static void zend_generator_add_child(zend_generator *generator, zend_generator *child) @@ -1026,7 +1034,9 @@ ZEND_METHOD(Generator, throw) if (generator->execute_data) { zend_generator *root = zend_generator_get_current(generator); - zend_generator_throw_exception(root, exception); + if (zend_generator_throw_exception(root, exception) == FAILURE) { + return; + } zend_generator_resume(generator); From a96b05e63f92c835976a51499e73fea5b9591fdf Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 30 Jul 2025 19:04:15 +0200 Subject: [PATCH 36/56] Fix GH-19300: Nested array_multisort invocation with error breaks There are 2 issues: 1. When a MULTISORT_ABORT happens, it frees func, but func may point to ARRAYG(multisort_func), which would be a problem with nested invocations as it can destroy that of the "parent" invocation. To solve this, delay assigning to the globals. 2. The old globals were not restored which means that nested invocations with different flags will cause a wrong sorting function to be used. Closes GH-19319. --- NEWS | 2 ++ ext/standard/array.c | 12 +++++--- ext/standard/tests/array/gh19300_1.phpt | 40 +++++++++++++++++++++++++ ext/standard/tests/array/gh19300_2.phpt | 40 +++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 ext/standard/tests/array/gh19300_1.phpt create mode 100644 ext/standard/tests/array/gh19300_2.phpt diff --git a/NEWS b/NEWS index 0b261679e788..e89bc61148d1 100644 --- a/NEWS +++ b/NEWS @@ -68,6 +68,8 @@ PHP NEWS . Fixed OSS Fuzz #433303828 (Leak in failed unserialize() with opcache). (ilutov) . Fix theoretical issues with hrtime() not being available. (nielsdos) + . Fixed bug GH-19300 (Nested array_multisort invocation with error breaks). + (nielsdos) - Windows: . Free opened_path when opened_path_len >= MAXPATHLEN. (dixyes) diff --git a/ext/standard/array.c b/ext/standard/array.c index 4d3c03bce526..a2a0459ec3b2 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -5911,7 +5911,7 @@ PHP_FUNCTION(array_multisort) for (i = 0; i < MULTISORT_LAST; i++) { parse_state[i] = 0; } - func = ARRAYG(multisort_func) = ecalloc(argc, sizeof(bucket_compare_func_t)); + func = ecalloc(argc, sizeof(bucket_compare_func_t)); /* Here we go through the input arguments and parse them. Each one can * be either an array or a sort flag which follows an array. If not @@ -5927,7 +5927,7 @@ PHP_FUNCTION(array_multisort) /* We see the next array, so we update the sort flags of * the previous array and reset the sort flags. */ if (i > 0) { - ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); + func[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); sort_order = PHP_SORT_ASC; sort_type = PHP_SORT_REGULAR; } @@ -5979,8 +5979,6 @@ PHP_FUNCTION(array_multisort) MULTISORT_ABORT; } } - /* Take care of the last array sort flags. */ - ARRAYG(multisort_func)[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); /* Make sure the arrays are of the same size. */ array_size = zend_hash_num_elements(Z_ARRVAL_P(arrays[0])); @@ -5998,6 +5996,11 @@ PHP_FUNCTION(array_multisort) RETURN_TRUE; } + /* Take care of the last array sort flags. */ + func[num_arrays - 1] = php_get_data_compare_func_unstable(sort_type, sort_order != PHP_SORT_ASC); + bucket_compare_func_t *old_multisort_func = ARRAYG(multisort_func); + ARRAYG(multisort_func) = func; + /* Create the indirection array. This array is of size MxN, where * M is the number of entries in each input array and N is the number * of the input arrays + 1. The last column is UNDEF to indicate the end @@ -6074,6 +6077,7 @@ PHP_FUNCTION(array_multisort) efree(indirect); efree(func); efree(arrays); + ARRAYG(multisort_func) = old_multisort_func; } /* }}} */ diff --git a/ext/standard/tests/array/gh19300_1.phpt b/ext/standard/tests/array/gh19300_1.phpt new file mode 100644 index 000000000000..18b639bf425b --- /dev/null +++ b/ext/standard/tests/array/gh19300_1.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-19300 (Nested array_multisort invocation with error breaks) - correct invocation variation +--FILE-- +data; + } +} + +$inputs = [ + new MyStringable('3'), + new MyStringable('1'), + new MyStringable('2'), +]; + +var_dump(array_multisort($inputs, SORT_STRING)); +var_dump($inputs); +?> +--EXPECT-- +bool(true) +array(3) { + [0]=> + object(MyStringable)#2 (1) { + ["data":"MyStringable":private]=> + string(1) "1" + } + [1]=> + object(MyStringable)#3 (1) { + ["data":"MyStringable":private]=> + string(1) "2" + } + [2]=> + object(MyStringable)#1 (1) { + ["data":"MyStringable":private]=> + string(1) "3" + } +} diff --git a/ext/standard/tests/array/gh19300_2.phpt b/ext/standard/tests/array/gh19300_2.phpt new file mode 100644 index 000000000000..41ae7e82bb79 --- /dev/null +++ b/ext/standard/tests/array/gh19300_2.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-19300 (Nested array_multisort invocation with error breaks) - error variation +--FILE-- +getMessage(), "\n"; + } +} +set_error_handler('error_handle'); + +$inputs = [ + new stdClass, + new stdClass, + new stdClass, +]; + +var_dump(array_multisort($inputs, SORT_NUMERIC)); +var_dump($inputs); +?> +--EXPECT-- +array_multisort(): Argument #1 ($array) must be an array or a sort flag +array_multisort(): Argument #1 ($array) must be an array or a sort flag +array_multisort(): Argument #1 ($array) must be an array or a sort flag +array_multisort(): Argument #1 ($array) must be an array or a sort flag +bool(true) +array(3) { + [0]=> + object(stdClass)#1 (0) { + } + [1]=> + object(stdClass)#2 (0) { + } + [2]=> + object(stdClass)#3 (0) { + } +} From d0fad34230eaeeac1ade4a9311aaf23ae2969fd6 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Fri, 18 Jul 2025 15:28:29 +0200 Subject: [PATCH 37/56] Fix circumvented type check with return by ref + finally Fixes GH-18736 Closes GH-19172 --- NEWS | 2 ++ Zend/tests/gh18736.phpt | 24 ++++++++++++++++++++++++ Zend/zend_compile.c | 12 ++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 Zend/tests/gh18736.phpt diff --git a/NEWS b/NEWS index e89bc61148d1..727b25f0521f 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,8 @@ PHP NEWS delegated Generator). (Arnaud) . Fixed bug GH-19326 (Calling Generator::throw() on a running generator with a non-Generator delegate crashes). (Arnaud) + . Fixed bug GH-18736 (Circumvented type check with return by ref + finally). + (ilutov) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/gh18736.phpt b/Zend/tests/gh18736.phpt new file mode 100644 index 000000000000..f397ee39a310 --- /dev/null +++ b/Zend/tests/gh18736.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-18736: Circumvented type check with return by ref + finally +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +test(): Return value must be of type int, string returned diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 41113e2f0055..6c8577cef588 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5201,8 +5201,20 @@ static void zend_compile_return(zend_ast *ast) /* {{{ */ expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1, 0); } + uint32_t opnum_before_finally = get_next_op_number(); + zend_handle_loops_and_finally((expr_node.op_type & (IS_TMP_VAR | IS_VAR)) ? &expr_node : NULL); + /* Content of reference might have changed in finally, repeat type check. */ + if (by_ref + /* Check if any opcodes were emitted since the last return type check. */ + && opnum_before_finally != get_next_op_number() + && !is_generator + && (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { + zend_emit_return_type_check( + expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1, 0); + } + opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN, &expr_node, NULL); From 9d29283392191f660ad767b246d0f4e064b5d174 Mon Sep 17 00:00:00 2001 From: Arndt Kaiser Date: Fri, 1 Aug 2025 19:51:09 +0200 Subject: [PATCH 38/56] Fix filtering of INI directives to respect leading whitespaces Directives are now correctly filtered out if the line in the php.ini file begins with whitespace characters. Closes GH-19348 --- build/Makefile.global | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Makefile.global b/build/Makefile.global index ec19efcbc589..49d4f8bcb3cf 100644 --- a/build/Makefile.global +++ b/build/Makefile.global @@ -90,7 +90,7 @@ PHP_TEST_SHARED_EXTENSIONS = ` \ . $$i; $(top_srcdir)/build/shtool echo -n -- " -d zend_extension=$(top_builddir)/modules/$$dlname"; \ done; \ fi` -PHP_DEPRECATED_DIRECTIVES_REGEX = '^(magic_quotes_(gpc|runtime|sybase)?|(zend_)?extension(_debug)?(_ts)?)[\t\ ]*=' +PHP_DEPRECATED_DIRECTIVES_REGEX = '^[\t\ ]*(magic_quotes_(gpc|runtime|sybase)?|(zend_)?extension(_debug)?(_ts)?)[\t\ ]*=' test: all @if test ! -z "$(PHP_EXECUTABLE)" && test -x "$(PHP_EXECUTABLE)"; then \ From e9e432a35d8327d0bac84216d1b8afddae3c535f Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 4 Aug 2025 16:16:49 +0200 Subject: [PATCH 39/56] Upgrade to macOS 14 and backport necessary changes --- .github/actions/brew/action.yml | 26 ++++++------- .github/actions/configure-macos/action.yml | 37 ++++++++++--------- .../actions/verify-generated-files/action.yml | 2 +- .github/workflows/push.yml | 4 +- 4 files changed, 33 insertions(+), 36 deletions(-) diff --git a/.github/actions/brew/action.yml b/.github/actions/brew/action.yml index 5cd8d33cfa6b..fa2acd58d2e5 100644 --- a/.github/actions/brew/action.yml +++ b/.github/actions/brew/action.yml @@ -5,31 +5,27 @@ runs: - shell: bash run: | set -x + + # Patch brew to overwrite always + formula_installer="$(brew --repo)"/Library/Homebrew/formula_installer.rb + code=" keg.link\(verbose: verbose\?" + sudo sed -Ei '' "s/$code.*/$code, overwrite: true\)/" "$formula_installer" + + # Some packages exist on x86 but not arm, or vice versa. + # Install them with reinstall to avoid warnings. + brew reinstall autoconf webp tidy-html5 libzip libsodium icu4c brew install \ - pkg-config \ - autoconf \ bison \ re2c brew install \ - openssl@1.1 \ - curl \ + aspell \ krb5 \ bzip2 \ enchant \ libffi \ - libpng \ - webp \ - freetype \ intltool \ libiconv \ - zlib \ t1lib \ - gd \ - libzip \ - gmp \ - tidy-html5 \ libxml2 \ libjpeg \ - libxslt \ - postgresql - brew link gettext --force + libxslt diff --git a/.github/actions/configure-macos/action.yml b/.github/actions/configure-macos/action.yml index 87627d4f8484..071e31708c21 100644 --- a/.github/actions/configure-macos/action.yml +++ b/.github/actions/configure-macos/action.yml @@ -10,15 +10,15 @@ runs: run: | set -x BREW_OPT="$(brew --prefix)"/opt - export PATH="/usr/local/opt/bison/bin:$PATH" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/openssl@1.1/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/curl/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/krb5/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libffi/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxml2/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxslt/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/zlib/lib/pkgconfig" - export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/icu4c@74/lib/pkgconfig" + export PATH="$BREW_OPT/bison/bin:$PATH" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/openssl@1.1/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/curl/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/krb5/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libffi/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libxml2/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libxslt/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/zlib/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/icu4c/lib/pkgconfig" sed -i -e 's/Requires.private:.*//g' "$BREW_OPT/curl/lib/pkgconfig/libcurl.pc" ./buildconf --force ./configure \ @@ -28,8 +28,8 @@ runs: --enable-fpm \ --with-pdo-mysql=mysqlnd \ --with-mysqli=mysqlnd \ - --with-pgsql=/usr/local/opt/libpq \ - --with-pdo-pgsql=/usr/local/opt/libpq \ + --with-pgsql="$BREW_OPT"/libpq \ + --with-pdo-pgsql="$BREW_OPT"/libpq \ --with-pdo-sqlite \ --without-pear \ --enable-gd \ @@ -42,30 +42,31 @@ runs: --enable-soap \ --enable-xmlreader \ --with-xsl \ - --with-tidy=/usr/local/opt/tidy-html5 \ + --with-tidy="$BREW_OPT"/tidy-html5 \ --with-libxml \ --enable-sysvsem \ --enable-sysvshm \ --enable-shmop \ --enable-pcntl \ - --with-readline=/usr/local/opt/readline \ + --with-readline="$BREW_OPT"/readline \ --enable-mbstring \ --with-curl \ - --with-gettext=/usr/local/opt/gettext \ + --with-gettext="$BREW_OPT"/gettext \ --enable-sockets \ - --with-bz2=/usr/local/opt/bzip2 \ + --with-bz2="$BREW_OPT"/bzip2 \ --with-openssl \ - --with-gmp=/usr/local/opt/gmp \ - --with-iconv=/usr/local/opt/libiconv \ + --with-gmp="$BREW_OPT"/gmp \ + --with-iconv="$BREW_OPT"/libiconv \ --enable-bcmath \ --enable-calendar \ --enable-ftp \ - --with-pspell=/usr/local/opt/aspell \ + --with-pspell="$BREW_OPT"/aspell \ --with-kerberos \ --enable-sysvmsg \ --with-ffi \ --enable-zend-test \ --enable-dl-test=shared \ + --enable-intl \ --with-mhash \ --with-sodium \ --enable-dba \ diff --git a/.github/actions/verify-generated-files/action.yml b/.github/actions/verify-generated-files/action.yml index 4bd838f63172..4d26e5f25027 100644 --- a/.github/actions/verify-generated-files/action.yml +++ b/.github/actions/verify-generated-files/action.yml @@ -5,7 +5,7 @@ runs: - shell: bash run: | set -x - [[ "$OSTYPE" == "darwin"* ]] && export PATH="/usr/local/opt/bison/bin:$PATH" + [[ "$OSTYPE" == "darwin"* ]] && export PATH="$(brew --prefix)/opt/bison/bin:$PATH" scripts/dev/genfiles Zend/zend_vm_gen.php build/gen_stub.php -f diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 5353ef7d0ea4..5c1057dd9164 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -113,7 +113,7 @@ jobs: uses: ./.github/actions/verify-generated-files MACOS_DEBUG_NTS: if: github.repository == 'php/php-src' || github.event_name == 'pull_request' - runs-on: macos-13 + runs-on: macos-14 steps: - name: git checkout uses: actions/checkout@v4 @@ -130,7 +130,7 @@ jobs: configurationParameters: --enable-debug --disable-zts - name: make run: |- - export PATH="/usr/local/opt/bison/bin:$PATH" + export PATH="$(brew --prefix)/opt/bison/bin:$PATH" make -j$(sysctl -n hw.logicalcpu) >/dev/null - name: make install run: sudo make install From f077c9d233f4fe6096a83cd87a36db96b64012b4 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 4 Aug 2025 23:10:41 +0200 Subject: [PATCH 40/56] [skip ci] Add zend_jit_arm64.c to gitignore This is not generated in CI since moving to macOS 14, which is arm based. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b8eb03412fa6..49acc9f2e178 100644 --- a/.gitignore +++ b/.gitignore @@ -169,6 +169,7 @@ php # Miscellaneous extensions files /ext/opcache/jit/zend_jit_x86.c +/ext/opcache/jit/zend_jit_arm64.c /ext/opcache/minilua # Generated by `cd ext/name && phpize && ./configure` From 1c65cc3998a69f8b33a09e16823a64c4954a6214 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 5 Aug 2025 14:50:18 +0200 Subject: [PATCH 41/56] [skip ci] Group component output in community job --- .github/workflows/nightly.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 38ccb05e0992..ade549e16efe 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -565,14 +565,16 @@ jobs: repositories="amp cache dns file http parallel parser pipeline process serialization socket sync websocket-client websocket-server" X=0 for repository in $repositories; do - printf "Testing amp/%s\n" "$repository" + echo "::group::$repository" git clone "https://github.com/amphp/$repository.git" "amphp-$repository" --depth 1 cd "amphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ vendor/bin/phpunit || EXIT_CODE=$? + echo "::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; + echo "Failed" fi cd .. done @@ -596,14 +598,16 @@ jobs: repositories="async cache child-process datagram dns event-loop promise promise-stream promise-timer stream" X=0 for repository in $repositories; do - printf "Testing reactphp/%s\n" "$repository" + echo "::group::$repository" git clone "https://github.com/reactphp/$repository.git" "reactphp-$repository" --depth 1 cd "reactphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ vendor/bin/phpunit || EXIT_CODE=$? + echo "::endgroup::" if [ $[EXIT_CODE:-0} -gt 128 ]; then X=1; + echo "Failed" fi cd .. done @@ -634,9 +638,12 @@ jobs: export SYMFONY_DEPRECATIONS_HELPER=max[total]=999 X=0 for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do + echo "::group::$component" php ./phpunit $component --exclude-group tty,benchmark,intl-data,transient --exclude-group skip || EXIT_CODE=$? + echo "::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; + echo "Failed" fi done exit $X From 8199cad99f9c8c9d18b829680001d5185016690a Mon Sep 17 00:00:00 2001 From: Shivam Mathur Date: Wed, 6 Aug 2025 05:32:36 +0530 Subject: [PATCH 42/56] Fix master branch check in find-target-branch.bat (#19385) --- .github/scripts/windows/find-target-branch.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/windows/find-target-branch.bat b/.github/scripts/windows/find-target-branch.bat index 43b285214971..a0b47f248894 100644 --- a/.github/scripts/windows/find-target-branch.bat +++ b/.github/scripts/windows/find-target-branch.bat @@ -3,6 +3,6 @@ for /f "usebackq tokens=3" %%i in (`findstr PHP_MAJOR_VERSION main\php_version.h`) do set BRANCH=%%i for /f "usebackq tokens=3" %%i in (`findstr PHP_MINOR_VERSION main\php_version.h`) do set BRANCH=%BRANCH%.%%i -if /i "%BRANCH%" equ "8.3" ( +if /i "%BRANCH%" equ "8.5" ( set BRANCH=master ) From c286cb8a1e84dbafb85003c456f6b47a77b7818d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 6 Aug 2025 14:00:44 +0200 Subject: [PATCH 43/56] [skip ci] Add fuzzer binaries to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4e95d9a9da33..cd9ba81306fd 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,7 @@ config.h.in /sapi/cgi/php-cgi /sapi/fpm/php-fpm /sapi/phpdbg/phpdbg +/sapi/fuzzer/php-fuzz-* /scripts/php-config /scripts/phpize php From a63e97667e84cd6919bdb2c722f5bd0179852565 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 6 Aug 2025 14:18:43 +0200 Subject: [PATCH 44/56] COMMUNTIY build grouping adjustments Unconditionally execute assignment of EXIT_CODE. Otherwise, the variable bleeds into the next iteration. Also add newline before ::endgroup::. ASAN does not add a trailing newline. --- .github/workflows/nightly.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ade549e16efe..b4266921f84a 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -570,8 +570,9 @@ jobs: cd "amphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ - vendor/bin/phpunit || EXIT_CODE=$? - echo "::endgroup::" + vendor/bin/phpunit + EXIT_CODE=$? + echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; echo "Failed" @@ -603,8 +604,9 @@ jobs: cd "reactphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ - vendor/bin/phpunit || EXIT_CODE=$? - echo "::endgroup::" + vendor/bin/phpunit + EXIT_CODE=$? + echo -e "\n::endgroup::" if [ $[EXIT_CODE:-0} -gt 128 ]; then X=1; echo "Failed" @@ -639,8 +641,9 @@ jobs: X=0 for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do echo "::group::$component" - php ./phpunit $component --exclude-group tty,benchmark,intl-data,transient --exclude-group skip || EXIT_CODE=$? - echo "::endgroup::" + php ./phpunit $component --exclude-group tty,benchmark,intl-data,transient --exclude-group skip + EXIT_CODE=$? + echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; echo "Failed" From 5be04e25fd6dd55cf6f4adcf45bc1daa95f5c7aa Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 6 Aug 2025 14:27:36 +0200 Subject: [PATCH 45/56] [skip ci] Skip segfaulting OOM test in GH actions on Win This only fails on the PHP-8.3 branch, most likely to be related to the environment as discussed with Niels. --- ext/spl/tests/gh14639.phpt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/spl/tests/gh14639.phpt b/ext/spl/tests/gh14639.phpt index 1b6f621d27bd..db85b2b044ef 100644 --- a/ext/spl/tests/gh14639.phpt +++ b/ext/spl/tests/gh14639.phpt @@ -7,6 +7,9 @@ memory_limit=2M if (getenv("USE_ZEND_ALLOC") === "0") { die("skip Zend MM disabled"); } +if (getenv("GITHUB_ACTIONS") && substr(PHP_OS, 0, 3) == "WIN") { + die("skip Segfaults in GitHub actions on Windows"); +} ?> --FILE-- Date: Wed, 6 Aug 2025 15:07:45 +0200 Subject: [PATCH 46/56] [skip ci] Fix Symfony COMMUNITY build for new phpunit version --exclude-group no longer accepts a comma-separated list. Doc block comments are no longer supported, switch to attributes. --- .github/workflows/nightly.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b4266921f84a..b2846da496e9 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -634,14 +634,14 @@ jobs: php /usr/bin/composer install --no-progress --ignore-platform-req=php+ php ./phpunit install # Test causes a heap-buffer-overflow but I cannot reproduce it locally... - php -r '$c = file_get_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php"); $c = str_replace("public function testSanitizeDeepNestedString()", "/** @group skip */\n public function testSanitizeDeepNestedString()", $c); file_put_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php", $c);' + php -r '$c = file_get_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php"); $c = str_replace("public function testSanitizeDeepNestedString()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testSanitizeDeepNestedString()", $c); file_put_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php", $c);' # Buggy FFI test in Symfony, see https://github.com/symfony/symfony/issues/47668 - php -r '$c = file_get_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php"); $c = str_replace("public function testCastNonTrailingCharPointer()", "/** @group skip */\n public function testCastNonTrailingCharPointer()", $c); file_put_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php", $c);' + php -r '$c = file_get_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php"); $c = str_replace("public function testCastNonTrailingCharPointer()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testCastNonTrailingCharPointer()", $c); file_put_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php", $c);' export SYMFONY_DEPRECATIONS_HELPER=max[total]=999 X=0 for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do echo "::group::$component" - php ./phpunit $component --exclude-group tty,benchmark,intl-data,transient --exclude-group skip + php ./phpunit $component --exclude-group tty --exclude-group benchmark --exclude-group intl-data --exclude-group transient --exclude-group skip EXIT_CODE=$? echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then From 44618752f900dcd7a4f9128fe28d01f4d372972d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 6 Aug 2025 22:28:56 +0200 Subject: [PATCH 47/56] [skip ci] Bump retries in benchmark diff when looking for benchmarked commits --- benchmark/generate_diff.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/generate_diff.php b/benchmark/generate_diff.php index 94c020df4b99..466a6ae0e1ed 100644 --- a/benchmark/generate_diff.php +++ b/benchmark/generate_diff.php @@ -62,7 +62,7 @@ function formatDiff(?int $baseInstructions, int $headInstructions): string { } function find_benchmarked_commit_hash(string $repo, string $commitHash): ?string { - $repeat = 10; + $repeat = 100; while (true) { if ($repeat-- <= 0) { From bd2766ce795df712923a818017fa2fa1169a7a23 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 3 Feb 2024 14:54:23 +0000 Subject: [PATCH 48/56] zend call stack fixing stack limit for macOs arm64. 8MB sounded a prudent size for older 10.9 macOs release, however with newer mac with arm64, it triggers a stack overflow. Cherry picks b320aabc5e875ef101510eb39ea3b40e351dc6a5 (GH-13319) from PHP-8.4. Closes GH-19390. --- NEWS | 1 + Zend/tests/stack_limit/stack_limit_010.phpt | 5 ++++- Zend/zend_call_stack.c | 9 +++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 727b25f0521f..ad06a4eef1b7 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ PHP NEWS a non-Generator delegate crashes). (Arnaud) . Fixed bug GH-18736 (Circumvented type check with return by ref + finally). (ilutov) + . Fixed zend call stack size for macOs/arm64. (David Carlier) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/stack_limit/stack_limit_010.phpt b/Zend/tests/stack_limit/stack_limit_010.phpt index 312c2cf55169..9e37555e6dec 100644 --- a/Zend/tests/stack_limit/stack_limit_010.phpt +++ b/Zend/tests/stack_limit/stack_limit_010.phpt @@ -15,7 +15,10 @@ $stack = zend_test_zend_call_stack_get(); var_dump($stack); $expectedMaxSize = match(php_uname('s')) { - 'Darwin' => 8*1024*1024, + 'Darwin' => match(php_uname('m')) { + 'x86_64' => 8*1024*1024, + 'arm64' => 8372224, + }, 'FreeBSD' => match(php_uname('m')) { 'amd64' => 512*1024*1024 - 4096, 'i386' => 64*1024*1024 - 4096, diff --git a/Zend/zend_call_stack.c b/Zend/zend_call_stack.c index bbaa8810af9e..8e0ce4c32488 100644 --- a/Zend/zend_call_stack.c +++ b/Zend/zend_call_stack.c @@ -417,7 +417,9 @@ static bool zend_call_stack_get_macos(zend_call_stack *stack) void *base = pthread_get_stackaddr_np(pthread_self()); size_t max_size; - if (pthread_main_np()) { +#if !defined(__aarch64__) + if (pthread_main_np()) + { /* pthread_get_stacksize_np() returns a too low value for the main * thread in OSX 10.9, 10.10: * https://mail.openjdk.org/pipermail/hotspot-dev/2013-October/011353.html @@ -427,7 +429,10 @@ static bool zend_call_stack_get_macos(zend_call_stack *stack) /* Stack size is 8MiB by default for main threads * https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html */ max_size = 8 * 1024 * 1024; - } else { + } + else +#endif + { max_size = pthread_get_stacksize_np(pthread_self()); } From 2cd9233e42a68267d544bd15373521b7d531da6b Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 7 Aug 2025 13:54:55 +0200 Subject: [PATCH 49/56] Fix ERROR_CODE handling in COMMUNITY build We need || because a non-0 return from the application aborts the execution of the job. --- .github/workflows/nightly.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b2846da496e9..513ade4a7100 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -570,8 +570,8 @@ jobs: cd "amphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ - vendor/bin/phpunit - EXIT_CODE=$? + EXIT_CODE=0 + vendor/bin/phpunit || EXIT_CODE=$? echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; @@ -604,8 +604,8 @@ jobs: cd "reactphp-$repository" git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-req=php+ - vendor/bin/phpunit - EXIT_CODE=$? + EXIT_CODE=0 + vendor/bin/phpunit || EXIT_CODE=$? echo -e "\n::endgroup::" if [ $[EXIT_CODE:-0} -gt 128 ]; then X=1; @@ -641,8 +641,8 @@ jobs: X=0 for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do echo "::group::$component" - php ./phpunit $component --exclude-group tty --exclude-group benchmark --exclude-group intl-data --exclude-group transient --exclude-group skip - EXIT_CODE=$? + EXIT_CODE=0 + php ./phpunit $component --exclude-group tty --exclude-group benchmark --exclude-group intl-data --exclude-group transient --exclude-group skip || EXIT_CODE=$? echo -e "\n::endgroup::" if [ ${EXIT_CODE:-0} -gt 128 ]; then X=1; From 5cf45ba5aba8d71f2c942254fe321906dfce4041 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:38:15 +0200 Subject: [PATCH 50/56] Fix GH-19371: integer overflow in calendar.c Closes GH-19380. --- NEWS | 3 + ext/calendar/calendar.c | 25 ++++++++ .../tests/cal_days_in_month_error1.phpt | 2 +- ext/calendar/tests/gh19371.phpt | 61 +++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 ext/calendar/tests/gh19371.phpt diff --git a/NEWS b/NEWS index ad06a4eef1b7..33ce00bc0caf 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,9 @@ PHP NEWS (ilutov) . Fixed zend call stack size for macOs/arm64. (David Carlier) +- Calendar: + . Fixed bug GH-19371 (integer overflow in calendar.c). (nielsdos) + - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/ext/calendar/calendar.c b/ext/calendar/calendar.c index 6da7e69529e2..b387b20c09b1 100644 --- a/ext/calendar/calendar.c +++ b/ext/calendar/calendar.c @@ -194,6 +194,16 @@ PHP_FUNCTION(cal_days_in_month) RETURN_THROWS(); } + if (UNEXPECTED(month <= 0 || month > INT32_MAX - 1)) { + zend_argument_value_error(2, "must be between 1 and %d", INT32_MAX - 1); + RETURN_THROWS(); + } + + if (UNEXPECTED(year > INT32_MAX - 1)) { + zend_argument_value_error(3, "must be less than %d", INT32_MAX - 1); + RETURN_THROWS(); + } + calendar = &cal_conversion_table[cal]; sdn_start = calendar->to_jd(year, month, 1); @@ -239,6 +249,21 @@ PHP_FUNCTION(cal_to_jd) RETURN_THROWS(); } + if (UNEXPECTED(month <= 0 || month > INT32_MAX - 1)) { + zend_argument_value_error(2, "must be between 1 and %d", INT32_MAX - 1); + RETURN_THROWS(); + } + + if (UNEXPECTED(ZEND_LONG_EXCEEDS_INT(day))) { + zend_argument_value_error(3, "must be between %d and %d", INT32_MIN, INT32_MAX); + RETURN_THROWS(); + } + + if (UNEXPECTED(year > INT32_MAX - 1)) { + zend_argument_value_error(4, "must be less than %d", INT32_MAX - 1); + RETURN_THROWS(); + } + RETURN_LONG(cal_conversion_table[cal].to_jd(year, month, day)); } /* }}} */ diff --git a/ext/calendar/tests/cal_days_in_month_error1.phpt b/ext/calendar/tests/cal_days_in_month_error1.phpt index f334888479f2..e110c13cc2a7 100644 --- a/ext/calendar/tests/cal_days_in_month_error1.phpt +++ b/ext/calendar/tests/cal_days_in_month_error1.phpt @@ -12,7 +12,7 @@ try { echo "{$ex->getMessage()}\n"; } try{ - cal_days_in_month(CAL_GREGORIAN,0, 2009); + cal_days_in_month(CAL_GREGORIAN,20, 2009); } catch (ValueError $ex) { echo "{$ex->getMessage()}\n"; } diff --git a/ext/calendar/tests/gh19371.phpt b/ext/calendar/tests/gh19371.phpt new file mode 100644 index 000000000000..1d807a983886 --- /dev/null +++ b/ext/calendar/tests/gh19371.phpt @@ -0,0 +1,61 @@ +--TEST-- +GH-19371 (integer overflow in calendar.c) +--SKIPIF-- + +--EXTENSIONS-- +calendar +--FILE-- +getMessage(), "\n"; +} +try { + echo cal_days_in_month(CAL_GREGORIAN, PHP_INT_MIN, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_days_in_month(CAL_GREGORIAN, PHP_INT_MAX, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + echo cal_to_jd(CAL_GREGORIAN, PHP_INT_MIN, 1, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, PHP_INT_MAX, 1, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, PHP_INT_MIN, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, PHP_INT_MAX, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, 1, PHP_INT_MAX); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +cal_days_in_month(): Argument #3 ($year) must be less than 2147483646 +cal_days_in_month(): Argument #2 ($month) must be between 1 and 2147483646 +cal_days_in_month(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #3 ($day) must be between -2147483648 and 2147483647 +cal_to_jd(): Argument #3 ($day) must be between -2147483648 and 2147483647 +cal_to_jd(): Argument #4 ($year) must be less than 2147483646 From cc93bbb765a15123db0320ff50113de0300bcb11 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 7 Aug 2025 19:27:58 +0200 Subject: [PATCH 51/56] Fix GH-19397: mb_list_encodings() can cause crashes on shutdown The request shutdown does not necessarily hold the last reference, if there is still a CV that refers to the array. Closes GH-19405. --- NEWS | 4 ++++ ext/mbstring/mbstring.c | 4 ++-- ext/mbstring/tests/gh19397.phpt | 11 +++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 ext/mbstring/tests/gh19397.phpt diff --git a/NEWS b/NEWS index 33ce00bc0caf..fd2aad2d4e88 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,10 @@ PHP NEWS . Fixed bug GH-19098 (libxml<2.13 segmentation fault caused by php_libxml_node_free). (nielsdos) +- MbString: + . Fixed bug GH-19397 (mb_list_encodings() can cause crashes on shutdown). + (nielsdos) + - Opcache: . Reset global pointers to prevent use-after-free in zend_jit_status(). (Florian Engelhardt) diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index dec565707fa7..1d5c27a2a381 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -1165,8 +1165,8 @@ PHP_RSHUTDOWN_FUNCTION(mbstring) MBSTRG(outconv_state) = 0; if (MBSTRG(all_encodings_list)) { - GC_DELREF(MBSTRG(all_encodings_list)); - zend_array_destroy(MBSTRG(all_encodings_list)); + /* must be *array* release to remove from GC root buffer and free the hashtable itself */ + zend_array_release(MBSTRG(all_encodings_list)); MBSTRG(all_encodings_list) = NULL; } diff --git a/ext/mbstring/tests/gh19397.phpt b/ext/mbstring/tests/gh19397.phpt new file mode 100644 index 000000000000..e6e07b161c08 --- /dev/null +++ b/ext/mbstring/tests/gh19397.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-19397 (mb_list_encodings() can cause crashes on shutdown) +--EXTENSIONS-- +mbstring +--FILE-- + 0); +?> +--EXPECT-- +bool(true) From 9e2aa658a8a0e6d7cf04df6683c468a08108290d Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sat, 9 Aug 2025 13:32:56 +0200 Subject: [PATCH 52/56] Fix GH-19428: openssl_pkey_derive segfaults for DH derive with low key_length This happens only for OpenSSL 1.1.1 because key_length is ignored for DH. It means that the provided string is overwritten with longer buffer. --- NEWS | 2 ++ ext/openssl/openssl.c | 18 +++++++++++++- ext/openssl/tests/gh19428.phpt | 44 ++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 ext/openssl/tests/gh19428.phpt diff --git a/NEWS b/NEWS index fd2aad2d4e88..d10f72712e4f 100644 --- a/NEWS +++ b/NEWS @@ -58,6 +58,8 @@ PHP NEWS . Fixed bug GH-18986 (OpenSSL backend: incorrect RAND_{load,write}_file() return value check). (nielsdos, botovq) . Fix error return check of EVP_CIPHER_CTX_ctrl(). (nielsdos) + . Fixed bug GH-19428 (openssl_pkey_derive segfaults for DH derive with low + key_length param). (Jakub Zelenka) - PDO Pgsql: . Fixed dangling pointer access on _pdo_pgsql_trim_message helper. diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index a0418e74ba7d..256d15215849 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -5200,12 +5200,20 @@ PHP_FUNCTION(openssl_pkey_get_details) } /* }}} */ -static zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, size_t key_size) { +static zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, size_t requested_key_size) { + size_t key_size = requested_key_size; EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key, NULL); if (!ctx) { return NULL; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* OpenSSL 1.1 does not respect key_size for DH, so force size discovery so it can be compared later. */ + if (EVP_PKEY_base_id(key) == EVP_PKEY_DH && key_size != 0) { + key_size = 0; + } +#endif + if (EVP_PKEY_derive_init(ctx) <= 0 || EVP_PKEY_derive_set_peer(ctx, peer_key) <= 0 || (key_size == 0 && EVP_PKEY_derive(ctx, NULL, &key_size) <= 0)) { @@ -5214,6 +5222,14 @@ static zend_string *php_openssl_pkey_derive(EVP_PKEY *key, EVP_PKEY *peer_key, s return NULL; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* Now compare the computed size for DH to mirror OpenSSL 3.0+ behavior. */ + if (EVP_PKEY_base_id(key) == EVP_PKEY_DH && requested_key_size > 0 && requested_key_size < key_size) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } +#endif + zend_string *result = zend_string_alloc(key_size, 0); if (EVP_PKEY_derive(ctx, (unsigned char *)ZSTR_VAL(result), &key_size) <= 0) { php_openssl_store_errors(); diff --git a/ext/openssl/tests/gh19428.phpt b/ext/openssl/tests/gh19428.phpt new file mode 100644 index 000000000000..5d290f32e62a --- /dev/null +++ b/ext/openssl/tests/gh19428.phpt @@ -0,0 +1,44 @@ +--TEST-- +GH-19428: openssl_pkey_derive() DH with low key_length +--EXTENSIONS-- +openssl +--FILE-- + +--EXPECT-- +bool(false) From b57578f3b13c80761073088166a17e6ac3c38dd4 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 10 Aug 2025 13:25:22 +0200 Subject: [PATCH 53/56] Fix GH-19383: php-8.3.24: Missing type in function definition: fpm_event_kqueue_clean This is just a clean backport. --- sapi/fpm/fpm/events/kqueue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sapi/fpm/fpm/events/kqueue.c b/sapi/fpm/fpm/events/kqueue.c index 8fc4a6f049b0..b81650d7741d 100644 --- a/sapi/fpm/fpm/events/kqueue.c +++ b/sapi/fpm/fpm/events/kqueue.c @@ -96,7 +96,7 @@ static int fpm_event_kqueue_init(int max) /* {{{ */ /* * release kqueue stuff */ -static int fpm_event_kqueue_clean() /* {{{ */ +static int fpm_event_kqueue_clean(void) /* {{{ */ { if (kevents) { free(kevents); From 9b86533ce42c8a4aed9e2561010ddf79d063fd27 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 9 Jul 2025 21:18:18 +0200 Subject: [PATCH 54/56] Fix GH-19065: Long match statement can segfault compiler during recursive SSA renaming On some systems, like Alpine, the thread stack size is small by default. The last step of SSA construction involves variable renaming that is recursive, and also makes copies of their version of the renamed variables on the stack. This combination causes a stack overflow during compilation on Alpine. Triggerable for example with very long match statements. A stop-gap solution would be to use heap allocated arrays for the renamed variable list, but that would only delay the error as increasing the number of match arms increases the depth of the dominator tree, and will eventually run into the same issue. This patch transforms the algorithm into an iterative one. There are two states stored in a worklist stack: positive numbers indicate that the block still needs to undergo variable renaming. Negative numbers indicate that the block and its dominated children are already renamed. Because 0 is also a valid block number, we bias the block numbers by adding 1. To restore to the right variant when backtracking the "recursive" step, we index into an array pointing to the different variable renaming variants. Closes GH-19083. --- NEWS | 2 + Zend/Optimizer/zend_ssa.c | 100 ++++++++++++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index d10f72712e4f..ffe2a2136e27 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,8 @@ PHP NEWS . Fixed bug GH-18736 (Circumvented type check with return by ref + finally). (ilutov) . Fixed zend call stack size for macOs/arm64. (David Carlier) + . Fixed bug GH-19065 (Long match statement can segfault compiler during + recursive SSA renaming). (nielsdos, Arnaud) - Calendar: . Fixed bug GH-19371 (integer overflow in calendar.c). (nielsdos) diff --git a/Zend/Optimizer/zend_ssa.c b/Zend/Optimizer/zend_ssa.c index 590df8155e39..cb8acc3775d1 100644 --- a/Zend/Optimizer/zend_ssa.c +++ b/Zend/Optimizer/zend_ssa.c @@ -22,6 +22,7 @@ #include "zend_ssa.h" #include "zend_dump.h" #include "zend_inference.h" +#include "zend_worklist.h" #include "Optimizer/zend_optimizer_internal.h" static bool dominates(const zend_basic_block *blocks, int a, int b) { @@ -787,7 +788,7 @@ ZEND_API int zend_ssa_rename_op(const zend_op_array *op_array, const zend_op *op } /* }}} */ -static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */ +static void zend_ssa_rename_in_block(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */ { zend_basic_block *blocks = ssa->cfg.blocks; zend_ssa_block *ssa_blocks = ssa->blocks; @@ -795,15 +796,6 @@ static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build int ssa_vars_count = ssa->vars_count; int i, j; zend_op *opline, *end; - int *tmp = NULL; - ALLOCA_FLAG(use_heap = 0); - - // FIXME: Can we optimize this copying out in some cases? - if (blocks[n].next_child >= 0) { - tmp = do_alloca(sizeof(int) * (op_array->last_var + op_array->T), use_heap); - memcpy(tmp, var, sizeof(int) * (op_array->last_var + op_array->T)); - var = tmp; - } if (ssa_blocks[n].phis) { zend_ssa_phi *phi = ssa_blocks[n].phis; @@ -887,22 +879,90 @@ static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build } ssa->vars_count = ssa_vars_count; +} +/* }}} */ - j = blocks[n].children; - while (j >= 0) { - // FIXME: Tail call optimization? - if (zend_ssa_rename(op_array, build_flags, ssa, var, j) == FAILURE) - return FAILURE; - j = blocks[j].next_child; - } +static zend_result zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) +{ + /* The worklist contains block numbers, encoded as positive or negative value. + * Positive values indicate that the variable rename still needs to happen for the block. + * Negative values indicate the variable rename was done and all children were handled too. + * In that case, we will clean up. + * Because block 0 is valid, we bias the block numbers by adding 1 such that we can distinguish + * positive and negative values in all cases. */ + zend_worklist_stack work; + ALLOCA_FLAG(work_use_heap); + ZEND_WORKLIST_STACK_ALLOCA(&work, ssa->cfg.blocks_count, work_use_heap); + zend_worklist_stack_push(&work, n + 1); + + /* This is used to backtrack the right version of the renamed variables to use. */ + ALLOCA_FLAG(save_vars_use_heap); + unsigned int save_vars_top = 0; + int **save_vars = do_alloca(sizeof(int *) * (ssa->cfg.blocks_count + 1), save_vars_use_heap); + save_vars[0] = var; + + while (work.len) { + n = zend_worklist_stack_pop(&work); + + /* Enter state: perform SSA variable rename */ + if (n > 0) { + n--; + + // FIXME: Can we optimize this copying out in some cases? + int *new_var; + if (ssa->cfg.blocks[n].next_child >= 0) { + new_var = emalloc(sizeof(int) * (op_array->last_var + op_array->T)); + memcpy(new_var, save_vars[save_vars_top], sizeof(int) * (op_array->last_var + op_array->T)); + save_vars[++save_vars_top] = new_var; + } else { + new_var = save_vars[save_vars_top]; + } - if (tmp) { - free_alloca(tmp, use_heap); + zend_ssa_rename_in_block(op_array, build_flags, ssa, new_var, n); + + int j = ssa->cfg.blocks[n].children; + if (j >= 0) { + /* Push backtrack state */ + zend_worklist_stack_push(&work, -(n + 1)); + + /* Push children in enter state */ + unsigned int child_count = 0; + int len_prior = work.len; + do { + zend_worklist_stack_push(&work, j + 1); + j = ssa->cfg.blocks[j].next_child; + child_count++; + } while (j >= 0); + + /* Reverse block order to maintain SSA variable number order given in previous PHP versions, + * but the data structure doesn't allow reverse dominator tree traversal. */ + for (unsigned int i = 0; i < child_count / 2; i++) { + int tmp = work.buf[len_prior + i]; + work.buf[len_prior + i] = work.buf[work.len - 1 - i]; + work.buf[work.len - 1 - i] = tmp; + } + } else { + /* Leafs jump directly to backtracking */ + goto backtrack; + } + } + /* Leave state: backtrack */ + else { + n = -n; + n--; +backtrack:; + if (ssa->cfg.blocks[n].next_child >= 0) { + efree(save_vars[save_vars_top]); + save_vars_top--; + } + } } + free_alloca(save_vars, save_vars_use_heap); + ZEND_WORKLIST_STACK_FREE_ALLOCA(&work, work_use_heap); + return SUCCESS; } -/* }}} */ ZEND_API zend_result zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa) /* {{{ */ { From abb7f2879eb46b2e39f146594521d8fb3bb85376 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 12 Aug 2025 14:17:33 +0200 Subject: [PATCH 55/56] [skip ci] Add timeout for FreeBSD Unfortunately, these jobs routinely fail to boot correctly. In this case, they'll stall and block CI for 6 hours until they finally fail. Add a limit to make them fail earlier. --- .github/workflows/nightly.yml | 1 + .github/workflows/push.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 513ade4a7100..ffa43fe744a2 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1086,6 +1086,7 @@ jobs: - zts: ${{ !inputs.run_freebsd_zts && true || '*never*' }} name: "FREEBSD_${{ matrix.zts && 'ZTS' || 'NTS' }}" runs-on: ubuntu-latest + timeout-minutes: 50 steps: - name: git checkout uses: actions/checkout@v4 diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 5c1057dd9164..29fc841dd4e7 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -174,6 +174,7 @@ jobs: if: github.repository == 'php/php-src' || github.event_name == 'pull_request' name: FREEBSD runs-on: ubuntu-latest + timeout-minutes: 50 steps: - name: git checkout uses: actions/checkout@v4 From f729a6471999b093c6b55674d00e6aea9f266e61 Mon Sep 17 00:00:00 2001 From: Eric Mann Date: Tue, 26 Aug 2025 08:05:30 -0700 Subject: [PATCH 56/56] Update versions for PHP 8.3.25 --- NEWS | 2 +- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index ffe2a2136e27..bead37f03833 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.3.25 +28 Aug 2025, PHP 8.3.25 - Core: . Fixed GH-19169 build issue with C++17 and ZEND_STATIC_ASSERT macro. diff --git a/Zend/zend.h b/Zend/zend.h index df90c452654a..40f580bfb52d 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.3.25-dev" +#define ZEND_VERSION "4.3.25" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 822184e80996..f494346fc434 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.3.25-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.3.25],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/main/php_version.h b/main/php_version.h index 80c0dc560653..75ef8a97b275 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -3,6 +3,6 @@ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 3 #define PHP_RELEASE_VERSION 25 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.3.25-dev" +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.3.25" #define PHP_VERSION_ID 80325