From 8e2c2be7a5476aad61c449993fbca93a7d3de98e Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Wed, 21 May 2025 00:39:56 +0200 Subject: [PATCH 01/42] PHP-8.3 is now for PHP 8.3.23-dev --- NEWS | 5 ++++- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index aed9bd06181a0..b0188da1c3a61 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.3.22 +?? ??? ????, PHP 8.3.23 + + +05 Jun 2025, PHP 8.3.22 - Core: . Fixed GH-18480 (array_splice with large values for offset/length arguments). diff --git a/Zend/zend.h b/Zend/zend.h index 99b88992475bf..704df32e9c145 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.3.22-dev" +#define ZEND_VERSION "4.3.23-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 278dbd110d248..965c0bdd853ee 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.22-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.3.23-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 6cec820ba471b..28e4162421ded 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 22 +#define PHP_RELEASE_VERSION 23 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.3.22-dev" -#define PHP_VERSION_ID 80322 +#define PHP_VERSION "8.3.23-dev" +#define PHP_VERSION_ID 80323 From 910aeaafc1fbfe82cd51ea461695affdc3590fdc Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 20 May 2025 22:13:48 +0200 Subject: [PATCH 02/42] Add missing filter cleanups on phar failure Closes GH-18609. --- NEWS | 2 ++ ext/phar/phar.c | 5 +++++ ext/phar/zip.c | 5 +++++ 3 files changed, 12 insertions(+) diff --git a/NEWS b/NEWS index b0188da1c3a61..6ae1dfce79730 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.23 +- Phar: + . Add missing filter cleanups on phar failure. (nielsdos) 05 Jun 2025, PHP 8.3.22 diff --git a/ext/phar/phar.c b/ext/phar/phar.c index dfdbbb8fb8390..125fc84703612 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -1704,6 +1704,7 @@ static int phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char php_stream_filter_append(&temp->writefilters, filter); if (SUCCESS != php_stream_copy_to_stream_ex(fp, temp, PHP_STREAM_COPY_ALL, NULL)) { + php_stream_filter_remove(filter, 1); if (err) { php_stream_close(temp); MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\", ext/zlib is buggy in PHP versions older than 5.2.6") @@ -1750,6 +1751,7 @@ static int phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char php_stream_filter_append(&temp->writefilters, filter); if (SUCCESS != php_stream_copy_to_stream_ex(fp, temp, PHP_STREAM_COPY_ALL, NULL)) { + php_stream_filter_remove(filter, 1); php_stream_close(temp); MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\" to temporary file") } @@ -2833,6 +2835,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv } entry->cfp = shared_cfp; if (!entry->cfp) { + php_stream_filter_free(filter); if (error) { spprintf(error, 0, "unable to create temporary file"); } @@ -2847,6 +2850,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv entry->header_offset = php_stream_tell(entry->cfp); php_stream_flush(file); if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) { + php_stream_filter_free(filter); if (closeoldfile) { php_stream_close(oldfile); } @@ -2858,6 +2862,7 @@ int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int conv } php_stream_filter_append((&entry->cfp->writefilters), filter); if (SUCCESS != php_stream_copy_to_stream_ex(file, entry->cfp, entry->uncompressed_filesize, NULL)) { + php_stream_filter_remove(filter, 1); if (closeoldfile) { php_stream_close(oldfile); } diff --git a/ext/phar/zip.c b/ext/phar/zip.c index c1f7d47c761cf..87681c69959a7 100644 --- a/ext/phar/zip.c +++ b/ext/phar/zip.c @@ -642,6 +642,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia } if (!entry.uncompressed_filesize || !actual_alias) { + php_stream_filter_remove(filter, 1); pefree(entry.filename, entry.is_persistent); PHAR_ZIP_FAIL("unable to read in alias, truncated"); } @@ -674,6 +675,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia } if (!entry.uncompressed_filesize || !actual_alias) { + php_stream_filter_remove(filter, 1); pefree(entry.filename, entry.is_persistent); PHAR_ZIP_FAIL("unable to read in alias, truncated"); } @@ -968,6 +970,7 @@ static int phar_zip_changed_apply_int(phar_entry_info *entry, void *arg) /* {{{ entry->cfp = php_stream_fopen_tmpfile(); if (!entry->cfp) { + php_stream_filter_free(filter); spprintf(p->error, 0, "unable to create temporary file for file \"%s\" while creating zip-based phar \"%s\"", entry->filename, entry->phar->fname); return ZEND_HASH_APPLY_STOP; } @@ -975,6 +978,7 @@ static int phar_zip_changed_apply_int(phar_entry_info *entry, void *arg) /* {{{ php_stream_flush(efp); if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) { + php_stream_filter_free(filter); spprintf(p->error, 0, "unable to seek to start of file \"%s\" to zip-based phar \"%s\"", entry->filename, entry->phar->fname); return ZEND_HASH_APPLY_STOP; } @@ -982,6 +986,7 @@ static int phar_zip_changed_apply_int(phar_entry_info *entry, void *arg) /* {{{ php_stream_filter_append((&entry->cfp->writefilters), filter); if (SUCCESS != php_stream_copy_to_stream_ex(efp, entry->cfp, entry->uncompressed_filesize, NULL)) { + php_stream_filter_remove(filter, 1); spprintf(p->error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, entry->phar->fname); return ZEND_HASH_APPLY_STOP; } From c30bddc18fe91d80947091bf088871760dcb91cb Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 21 May 2025 19:26:15 +0100 Subject: [PATCH 03/42] Fix GH-18617: socket_import_file_descriptor return check. to_zval_read_fd_array() helper when retrieving the socket protocol did not check it. close GH-18617 --- NEWS | 4 ++++ ext/sockets/conversions.c | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 6ae1dfce79730..d712f80818a79 100644 --- a/NEWS +++ b/NEWS @@ -47,6 +47,10 @@ PHP NEWS - PDO_OCI: . Fixed bug GH-18494 (PDO OCI segfault in statement GC). (nielsdos) +- Sockets: + - Fixed bug GH-18617 (socket_import_file_descriptor return value + unchecked). (David Carlier) + - SPL: . Fixed bug GH-18421 (Integer overflow with large numbers in LimitIterator). (nielsdos) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index 4059758f44719..d03ef8ef68d59 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -1457,7 +1457,11 @@ void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) object_init_ex(&elem, socket_ce); php_socket *sock = Z_SOCKET_P(&elem); - socket_import_file_descriptor(fd, sock); + if (!socket_import_file_descriptor(fd, sock)) { + do_to_zval_err(ctx, "error getting protocol descriptor %d: getsockopt() call failed with errno %d", fd, errno); + zval_ptr_dtor(&elem); + return; + } } else { php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); php_stream_to_zval(stream, &elem); From d6836fb345977c5d03b47e761220a4c5cbb96fb0 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 24 May 2025 13:53:15 +0200 Subject: [PATCH 04/42] Fix memory leak in intl_datetime_decompose() Closes GH-18635. --- NEWS | 3 +++ ext/intl/common/common_date.cpp | 1 + 2 files changed, 4 insertions(+) diff --git a/NEWS b/NEWS index d712f80818a79..a8d6d6ee6fe92 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.23 +- Intl: + . Fix memory leak in intl_datetime_decompose() on failure. (nielsdos) + - Phar: . Add missing filter cleanups on phar failure. (nielsdos) diff --git a/ext/intl/common/common_date.cpp b/ext/intl/common/common_date.cpp index 2a52b7e63be5c..a412ddcdffcde 100644 --- a/ext/intl/common/common_date.cpp +++ b/ext/intl/common/common_date.cpp @@ -118,6 +118,7 @@ U_CFUNC int intl_datetime_decompose(zval *z, double *millis, TimeZone **tz, ZVAL_STRING(&zfuncname, "getTimestamp"); if (call_user_function(NULL, z, &zfuncname, &retval, 0, NULL) != SUCCESS || Z_TYPE(retval) != IS_LONG) { + zval_ptr_dtor(&retval); spprintf(&message, 0, "%s: error calling ::getTimeStamp() on the " "object", func); intl_errors_set(err, U_INTERNAL_PROGRAM_ERROR, From c9781111e1d22b45ebdee294aa679240b6fd41de Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 24 May 2025 16:17:43 +0200 Subject: [PATCH 05/42] Fix memory leak when calloc() fails in php_readline_completion_cb() Closes GH-18637. --- NEWS | 4 ++++ ext/readline/readline.c | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index a8d6d6ee6fe92..fc7e534d60432 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,10 @@ PHP NEWS - Phar: . Add missing filter cleanups on phar failure. (nielsdos) +- Readline: + . Fix memory leak when calloc() fails in php_readline_completion_cb(). + (nielsdos) + 05 Jun 2025, PHP 8.3.22 - Core: diff --git a/ext/readline/readline.c b/ext/readline/readline.c index 1bd5e2fd6059a..4da9f35951527 100644 --- a/ext/readline/readline.c +++ b/ext/readline/readline.c @@ -458,13 +458,14 @@ char **php_readline_completion_cb(const char *text, int start, int end) /* libedit will read matches[2] */ matches = calloc(3, sizeof(char *)); if (!matches) { - return NULL; + goto out; } matches[0] = strdup(""); } } } +out: zval_ptr_dtor(¶ms[0]); zval_ptr_dtor(&_readline_array); From a44e3f442f3e52d35e4e8606785b52433fc78f37 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 24 May 2025 16:19:53 +0200 Subject: [PATCH 06/42] Fix memory leaks in php_http.c when call_user_function() fails retval can be refcounted but is not destroyed. Closes GH-18638. --- NEWS | 3 +++ ext/soap/php_http.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/NEWS b/NEWS index fc7e534d60432..c533ad33e490c 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,9 @@ PHP NEWS . Fix memory leak when calloc() fails in php_readline_completion_cb(). (nielsdos) +- Soap: + . Fix memory leaks in php_http.c when call_user_function() fails. (nielsdos) + 05 Jun 2025, PHP 8.3.22 - Core: diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index 00aa54c83efdb..c908bb4d8ff16 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -416,6 +416,7 @@ int make_http_soap_request(zval *this_ptr, } else { zval_ptr_dtor(¶ms[0]); zval_ptr_dtor(&func); + zval_ptr_dtor(&retval); if (request != buf) { zend_string_release_ex(request, 0); } @@ -1314,6 +1315,7 @@ int make_http_soap_request(zval *this_ptr, } else { zval_ptr_dtor(¶ms[0]); zval_ptr_dtor(&func); + zval_ptr_dtor(&retval); efree(content_encoding); zend_string_release_ex(http_headers, 0); zend_string_release_ex(http_body, 0); From 772479ea2ff92115d67b39572e265dee4e91b444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sat, 24 May 2025 22:26:06 +0200 Subject: [PATCH 07/42] Fix build on Apple Clang 17+ (#18629) Fixing "invalid cpu feature string for builtin" errors that started to appear on Apple Clang 17.0.0 --- Zend/zend_cpuinfo.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Zend/zend_cpuinfo.h b/Zend/zend_cpuinfo.h index 9d221c59e541a..5cc161eab9949 100644 --- a/Zend/zend_cpuinfo.h +++ b/Zend/zend_cpuinfo.h @@ -126,58 +126,86 @@ ZEND_API int zend_cpu_supports(zend_cpu_feature feature); * functions */ ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_sse2(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("sse2"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_sse3(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("sse3"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_ssse3(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("ssse3"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_sse41(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("sse4.1"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_sse42(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("sse4.2"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_avx(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("avx"); +#endif } ZEND_NO_SANITIZE_ADDRESS static inline int zend_cpu_supports_avx2(void) { +#ifdef __aarch64__ + return 0; +#else #if PHP_HAVE_BUILTIN_CPU_INIT __builtin_cpu_init(); #endif return __builtin_cpu_supports("avx2"); +#endif } #if PHP_HAVE_AVX512_SUPPORTS From 5d4846b24181881f33ea299697b5392075e5f6a4 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 24 May 2025 21:09:53 +0100 Subject: [PATCH 08/42] ext/ldap: simplify ldap_connect() workflow, fix url leak. delaying the object creation only before ldap initialisation. fix forgotten url freeing on TLS error code path. close GH-18645 --- ext/ldap/ldap.c | 11 +++++++--- ext/ldap/tests/ldap_connect_port_error.phpt | 24 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 ext/ldap/tests/ldap_connect_port_error.phpt diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index a1b7e7322a5de..fecb8846400a6 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -984,8 +984,6 @@ PHP_FUNCTION(ldap_connect) RETURN_FALSE; } - object_init_ex(return_value, ldap_link_ce); - ld = Z_LDAP_LINK_P(return_value); { int rc = LDAP_SUCCESS; @@ -1008,13 +1006,17 @@ PHP_FUNCTION(ldap_connect) /* ensure all pending TLS options are applied in a new context */ if (ldap_set_option(NULL, LDAP_OPT_X_TLS_NEWCTX, &val) != LDAP_OPT_SUCCESS) { - zval_ptr_dtor(return_value); + if (url != host) { + efree(url); + } php_error_docref(NULL, E_WARNING, "Could not create new security context"); RETURN_FALSE; } LDAPG(tls_newctx) = false; } #endif + object_init_ex(return_value, ldap_link_ce); + ld = Z_LDAP_LINK_P(return_value); #ifdef LDAP_API_FEATURE_X_OPENLDAP /* ldap_init() is deprecated, use ldap_initialize() instead. @@ -1027,6 +1029,9 @@ PHP_FUNCTION(ldap_connect) ldap = ldap_init(host, port); if (ldap == NULL) { zval_ptr_dtor(return_value); + if (url != host) { + efree(url); + } php_error_docref(NULL, E_WARNING, "Could not create session handle"); RETURN_FALSE; } diff --git a/ext/ldap/tests/ldap_connect_port_error.phpt b/ext/ldap/tests/ldap_connect_port_error.phpt new file mode 100644 index 0000000000000..55d41d395b13c --- /dev/null +++ b/ext/ldap/tests/ldap_connect_port_error.phpt @@ -0,0 +1,24 @@ +--TEST-- +ldap_connect() - Connection errors +--EXTENSIONS-- +ldap +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +getMessage(), PHP_EOL; +} + +try { + ldap_connect("nope://$host", 0); +} catch (\ValueError $e) { + echo $e->getMessage(), PHP_EOL; +} +?> +--EXPECT-- +ldap_connect(): Argument #2 ($port) must be between 1 and 65535 +ldap_connect(): Argument #2 ($port) must be between 1 and 65535 From ff2c7dc0f8ebf3ae811b4b13eb7bc7662051a988 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 22 May 2025 23:06:00 +0200 Subject: [PATCH 09/42] Fix leaks with multiple calls to DatePeriod iterator current() Destroy the old value first. We can't skip recreating the value because the object may have been changed in between calls. Closes GH-18624. --- NEWS | 3 ++ ext/date/php_date.c | 1 + ...le_calls_date_period_iterator_current.phpt | 42 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 ext/date/tests/multiple_calls_date_period_iterator_current.phpt diff --git a/NEWS b/NEWS index c533ad33e490c..4dce12d98f00d 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.23 +- Date: + . Fix leaks with multiple calls to DatePeriod iterator current(). (nielsdos) + - Intl: . Fix memory leak in intl_datetime_decompose() on failure. (nielsdos) diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 2347fd55706fa..910149efae658 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -1607,6 +1607,7 @@ static zval *date_period_it_current_data(zend_object_iterator *iter) php_date_obj *newdateobj; /* Create new object */ + zval_ptr_dtor(&iterator->current); php_date_instantiate(get_base_date_class(object->start_ce), &iterator->current); newdateobj = Z_PHPDATE_P(&iterator->current); newdateobj->time = timelib_time_ctor(); diff --git a/ext/date/tests/multiple_calls_date_period_iterator_current.phpt b/ext/date/tests/multiple_calls_date_period_iterator_current.phpt new file mode 100644 index 0000000000000..b0e90873e6126 --- /dev/null +++ b/ext/date/tests/multiple_calls_date_period_iterator_current.phpt @@ -0,0 +1,42 @@ +--TEST-- +Multiple calls to DatePeriod iterator current() leak objects +--FILE-- +getIterator(); +var_dump($iter->current()); +var_dump($iter->current()); +$iter->current()->setTimestamp(0); +var_dump($iter->current()); + +?> +--EXPECT-- +object(DateTime)#9 (3) { + ["date"]=> + string(26) "2018-12-31 00:00:00.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(3) "UTC" +} +object(DateTime)#9 (3) { + ["date"]=> + string(26) "2018-12-31 00:00:00.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(3) "UTC" +} +object(DateTime)#9 (3) { + ["date"]=> + string(26) "2018-12-31 00:00:00.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(3) "UTC" +} From b39e17b06c4c33ee95dc5c05456e9f794d5a8629 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 25 May 2025 10:17:19 +0200 Subject: [PATCH 10/42] Fix memory leak in tidy output handler on error Closes GH-18649. --- NEWS | 3 +++ ext/tidy/tidy.c | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index 4dce12d98f00d..f790b5164cb36 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,9 @@ PHP NEWS - Soap: . Fix memory leaks in php_http.c when call_user_function() fails. (nielsdos) +- Tidy: + . Fix memory leak in tidy output handler on error. (nielsdos) + 05 Jun 2025, PHP 8.3.22 - Core: diff --git a/ext/tidy/tidy.c b/ext/tidy/tidy.c index 831fcb3815399..46dd637f40e54 100644 --- a/ext/tidy/tidy.c +++ b/ext/tidy/tidy.c @@ -965,6 +965,11 @@ static int php_tidy_output_handler(void **nothing, php_output_context *output_co TidyBuffer inbuf, outbuf, errbuf; if (TG(clean_output) && (output_context->op & PHP_OUTPUT_HANDLER_START) && (output_context->op & PHP_OUTPUT_HANDLER_FINAL)) { + if (ZEND_SIZE_T_UINT_OVFL(output_context->in.used)) { + php_error_docref(NULL, E_WARNING, "Input string is too long"); + return status; + } + doc = tidyCreate(); tidyBufInit(&errbuf); @@ -972,11 +977,6 @@ static int php_tidy_output_handler(void **nothing, php_output_context *output_co tidyOptSetBool(doc, TidyForceOutput, yes); tidyOptSetBool(doc, TidyMark, no); - if (ZEND_SIZE_T_UINT_OVFL(output_context->in.used)) { - php_error_docref(NULL, E_WARNING, "Input string is too long"); - return status; - } - TIDY_SET_DEFAULT_CONFIG(doc); tidyBufInit(&inbuf); From fab0a6d75cbc5730a4d57cf4ebb8fa45363d0811 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 27 May 2025 20:40:35 +0200 Subject: [PATCH 11/42] Backport fix for GH-17687 Introducing a new attribute macro in lower branches is an ABI break and not allowed. However, we still need to fix the warnings such that -Werror builds don't break. So we copy the macro from the master branch to the C files in the appropriate places. --- ext/fileinfo/data_file.c | 2 +- ext/fileinfo/libmagic/apprentice.c | 6 ++++++ ext/pdo/pdo_sqlstate.c | 8 +++++++- ext/standard/crypt_sha256.c | 8 +++++++- ext/standard/crypt_sha512.c | 8 +++++++- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ext/fileinfo/data_file.c b/ext/fileinfo/data_file.c index 212addc4d0541..af4c139bed0a0 100644 --- a/ext/fileinfo/data_file.c +++ b/ext/fileinfo/data_file.c @@ -1,6 +1,6 @@ /* This is a generated file, do not modify */ /* Usage: php create_data_file.php /path/to/magic.mgc > data_file.c */ -const unsigned char php_magic_database[7955032] = { +const unsigned char php_magic_database[7955032] ZEND_NONSTRING = { 0x1C, 0x04, 0x1E, 0xF1, 0x12, 0x00, 0x00, 0x00, 0xD2, 0x3B, 0x00, 0x00, 0xD2, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/ext/fileinfo/libmagic/apprentice.c b/ext/fileinfo/libmagic/apprentice.c index 5a5ae197467bb..922adcf55f1cd 100644 --- a/ext/fileinfo/libmagic/apprentice.c +++ b/ext/fileinfo/libmagic/apprentice.c @@ -185,6 +185,12 @@ private struct { { NULL, 0, NULL } }; +#if __has_attribute(nonstring) +# define ZEND_NONSTRING __attribute__((nonstring)) +#else +# define ZEND_NONSTRING +#endif + #include "../data_file.c" #ifdef COMPILE_ONLY diff --git a/ext/pdo/pdo_sqlstate.c b/ext/pdo/pdo_sqlstate.c index 5858566a818eb..a5e23890cee7f 100644 --- a/ext/pdo/pdo_sqlstate.c +++ b/ext/pdo/pdo_sqlstate.c @@ -24,8 +24,14 @@ #include "php_pdo.h" #include "php_pdo_driver.h" +#if __has_attribute(nonstring) +# define ZEND_NONSTRING __attribute__((nonstring)) +#else +# define ZEND_NONSTRING +#endif + struct pdo_sqlstate_info { - const char state[5]; + const char state[5] ZEND_NONSTRING; const char *desc; }; diff --git a/ext/standard/crypt_sha256.c b/ext/standard/crypt_sha256.c index 9e86db6020cdf..3e99bedc54129 100644 --- a/ext/standard/crypt_sha256.c +++ b/ext/standard/crypt_sha256.c @@ -317,8 +317,14 @@ static const char sha256_rounds_prefix[] = "rounds="; /* Maximum number of rounds. */ #define ROUNDS_MAX 999999999 +#if __has_attribute(nonstring) +# define ZEND_NONSTRING __attribute__((nonstring)) +#else +# define ZEND_NONSTRING +#endif + /* Table with characters for base64 transformation. */ -static const char b64t[64] = +static const char b64t[64] ZEND_NONSTRING = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; char * php_sha256_crypt_r(const char *key, const char *salt, char *buffer, int buflen) diff --git a/ext/standard/crypt_sha512.c b/ext/standard/crypt_sha512.c index 6ead7f2964a48..ae0eaecdca9a1 100644 --- a/ext/standard/crypt_sha512.c +++ b/ext/standard/crypt_sha512.c @@ -350,8 +350,14 @@ static const char sha512_rounds_prefix[] = "rounds="; /* Maximum number of rounds. */ #define ROUNDS_MAX 999999999 +#if __has_attribute(nonstring) +# define ZEND_NONSTRING __attribute__((nonstring)) +#else +# define ZEND_NONSTRING +#endif + /* Table with characters for base64 transformation. */ -static const char b64t[64] = +static const char b64t[64] ZEND_NONSTRING = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; From 61884c3b52012249f571d3ce00827e7a1914b062 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 24 May 2025 17:37:22 +0200 Subject: [PATCH 12/42] Fix GH-18642: Signed integer overflow in ext/phar fseek The overflow checking code already existed, but didn't work because the math was done on signed numbers instead of unsigned numbers. In the process I also discovered a pre-existing issue that needs to be fixed (and seems that other stream wrappers can have this issue too). Closes GH-18644. --- NEWS | 1 + ext/phar/stream.c | 20 +++++++++++--------- ext/phar/tests/gh18642.phpt | 29 +++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 ext/phar/tests/gh18642.phpt diff --git a/NEWS b/NEWS index f790b5164cb36..87c038ebc7ea0 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ PHP NEWS - Phar: . Add missing filter cleanups on phar failure. (nielsdos) + . Fixed bug GH-18642 (Signed integer overflow in ext/phar fseek). (nielsdos) - Readline: . Fix memory leak when calloc() fails in php_readline_completion_cb(). diff --git a/ext/phar/stream.c b/ext/phar/stream.c index b53d4297c4227..fee100cc31a10 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -405,7 +405,7 @@ static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, z phar_entry_data *data = (phar_entry_data *)stream->abstract; phar_entry_info *entry; int res; - zend_off_t temp; + zend_ulong temp; if (data->internal_file->link) { entry = phar_get_link_source(data->internal_file); @@ -415,26 +415,28 @@ static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, z switch (whence) { case SEEK_END : - temp = data->zero + entry->uncompressed_filesize + offset; + temp = (zend_ulong) data->zero + (zend_ulong) entry->uncompressed_filesize + (zend_ulong) offset; break; case SEEK_CUR : - temp = data->zero + data->position + offset; + temp = (zend_ulong) data->zero + (zend_ulong) data->position + (zend_ulong) offset; break; case SEEK_SET : - temp = data->zero + offset; + temp = (zend_ulong) data->zero + (zend_ulong) offset; break; default: temp = 0; } - if (temp > data->zero + (zend_off_t) entry->uncompressed_filesize) { - *newoffset = -1; + + zend_off_t temp_signed = (zend_off_t) temp; + if (temp_signed > data->zero + (zend_off_t) entry->uncompressed_filesize) { + *newoffset = -1; /* FIXME: this will invalidate the ZEND_ASSERT(stream->position >= 0); assertion in streams.c */ return -1; } - if (temp < data->zero) { - *newoffset = -1; + if (temp_signed < data->zero) { + *newoffset = -1; /* FIXME: this will invalidate the ZEND_ASSERT(stream->position >= 0); assertion in streams.c */ return -1; } - res = php_stream_seek(data->fp, temp, SEEK_SET); + res = php_stream_seek(data->fp, temp_signed, SEEK_SET); *newoffset = php_stream_tell(data->fp) - data->zero; data->position = *newoffset; return res; diff --git a/ext/phar/tests/gh18642.phpt b/ext/phar/tests/gh18642.phpt new file mode 100644 index 0000000000000..a6872f7a62d86 --- /dev/null +++ b/ext/phar/tests/gh18642.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-18642 (Signed integer overflow in ext/phar fseek) +--EXTENSIONS-- +phar +--INI-- +phar.require_hash=0 +--FILE-- +setInfoClass('MyFile'); +$f = $phar['a.php']; +var_dump($f->fseek(PHP_INT_MAX)); +var_dump($f->fseek(0)); +var_dump($f->fseek(PHP_INT_MIN, SEEK_END)); +var_dump($f->fseek(0, SEEK_SET)); +var_dump($f->fseek(1, SEEK_CUR)); +var_dump($f->fseek(PHP_INT_MAX, SEEK_CUR)); +?> +--EXPECT-- +int(-1) +int(0) +int(-1) +int(0) +int(0) +int(-1) From 40422e9c55c83e5a453416fe1ae1a1e0c880a6dc Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 29 May 2025 15:41:22 +0100 Subject: [PATCH 13/42] ext/pgsql: Fix warning not being emittedd when failure to cancel a query --- NEWS | 4 ++++ ext/pgsql/pgsql.c | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 87c038ebc7ea0..466ba5d89653e 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,10 @@ PHP NEWS . Add missing filter cleanups on phar failure. (nielsdos) . Fixed bug GH-18642 (Signed integer overflow in ext/phar fseek). (nielsdos) +- PGSQL: + . Fix warning not being emitted when failure to cancel a query with + pg_cancel_query(). (Girgias) + - Readline: . Fix memory leak when calloc() fails in php_readline_completion_cb(). (nielsdos) diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index e955eed0928c8..63acd26ea01f5 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -3576,8 +3576,14 @@ static void php_pgsql_do_async(INTERNAL_FUNCTION_PARAMETERS, int entry_type) int rc; c = PQgetCancel(pgsql); + /* PQcancel + * The return value of PQcancel is 1 if the cancel request was successfully dispatched and 0 if not. + * If not, errbuf is filled with an explanatory error message. + * errbuf must be a char array of size errbufsize (the recommended size is 256 bytes). + * https://www.postgresql.org/docs/current/libpq-cancel.html#LIBPQ-PQCANCEL + */ RETVAL_LONG((rc = PQcancel(c, err, sizeof(err)))); - if (rc < 0) { + if (rc == 0) { zend_error(E_WARNING, "cannot cancel the query: %s", err); } while ((pgsql_result = PQgetResult(pgsql))) { From dd856d5ad90c642d82eaf5fe4ab511c8a3971557 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 29 May 2025 16:57:31 +0200 Subject: [PATCH 14/42] Fix potential NULL deref Backported from GH-18697. --- ext/soap/php_schema.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/soap/php_schema.c b/ext/soap/php_schema.c index 423714545ae30..2fc0eacc530f7 100644 --- a/ext/soap/php_schema.c +++ b/ext/soap/php_schema.c @@ -1872,7 +1872,7 @@ static int schema_attribute(sdlPtr sdl, xmlAttrPtr tns, xmlNodePtr attrType, sdl } else { xmlNsPtr nsPtr = attr_find_ns(attr); - if (strncmp((char*)nsPtr->href, SCHEMA_NAMESPACE, sizeof(SCHEMA_NAMESPACE))) { + if (nsPtr && strncmp((char*)nsPtr->href, SCHEMA_NAMESPACE, sizeof(SCHEMA_NAMESPACE))) { smart_str key2 = {0}; sdlExtraAttributePtr ext; xmlNsPtr nsptr; From 087f38f3476b2f6c73cf2567ccbe19636c2c975d Mon Sep 17 00:00:00 2001 From: Oleg Efimov Date: Thu, 29 May 2025 21:46:11 +0100 Subject: [PATCH 15/42] Fix GH-18695: float numbers zero fraction is now preserved in zend_ast_export() (#18699) --- NEWS | 4 ++++ Zend/tests/ast/ast_serialize_floats.phpt | 26 ++++++++++++++++++++++++ Zend/zend_ast.c | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/ast/ast_serialize_floats.phpt diff --git a/NEWS b/NEWS index 466ba5d89653e..379718ba2a9ba 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.23 +- Core: + . Fixed GH-18695 (zend_ast_export() - float number is not preserved). + (Oleg Efimov) + - Date: . Fix leaks with multiple calls to DatePeriod iterator current(). (nielsdos) diff --git a/Zend/tests/ast/ast_serialize_floats.phpt b/Zend/tests/ast/ast_serialize_floats.phpt new file mode 100644 index 0000000000000..164b8b03338cf --- /dev/null +++ b/Zend/tests/ast/ast_serialize_floats.phpt @@ -0,0 +1,26 @@ +--TEST-- +Serialization of floats are correct +--INI-- +zend.assertions=1 +--FILE-- +getMessage(), ' failed', PHP_EOL; +} +try { + assert(!is_float(1.1)); +} catch (AssertionError $e) { + echo 'assert(): ', $e->getMessage(), ' failed', PHP_EOL; +} +try { + assert(!is_float(1234.5678)); +} catch (AssertionError $e) { + echo 'assert(): ', $e->getMessage(), ' failed', PHP_EOL; +} +?> +--EXPECT-- +assert(): assert(!is_float(0.0)) failed +assert(): assert(!is_float(1.1)) failed +assert(): assert(!is_float(1234.5678)) failed diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 0fb50e2eae1f5..f8c4ca17a9b95 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1563,7 +1563,7 @@ static ZEND_COLD void zend_ast_export_zval(smart_str *str, zval *zv, int priorit break; case IS_DOUBLE: smart_str_append_double( - str, Z_DVAL_P(zv), (int) EG(precision), /* zero_fraction */ false); + str, Z_DVAL_P(zv), (int) EG(precision), /* zero_fraction */ true); break; case IS_STRING: smart_str_appendc(str, '\''); From 48b492269b9761d0cc606667cf20521c7c1b6449 Mon Sep 17 00:00:00 2001 From: txuna Date: Mon, 26 May 2025 13:25:21 +0000 Subject: [PATCH 16/42] Fix GH-18595: fpm_get_status segfault This fixes null dereference error when calling fpm_get_status() and one of the children is just being created. Closes GH-18662 Co-authored-by: Jakub Zelenka --- NEWS | 3 +++ sapi/fpm/fpm/fpm_request.c | 1 + sapi/fpm/fpm/fpm_request.h | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 379718ba2a9ba..3b3900afff9d8 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,9 @@ PHP NEWS - Date: . Fix leaks with multiple calls to DatePeriod iterator current(). (nielsdos) +- FPM: + . Fixed GH-18662 (fpm_get_status segfault). (txuna) + - Intl: . Fix memory leak in intl_datetime_decompose() on failure. (nielsdos) diff --git a/sapi/fpm/fpm/fpm_request.c b/sapi/fpm/fpm/fpm_request.c index 0eb75884d367a..9ea7d8aeaaa35 100644 --- a/sapi/fpm/fpm/fpm_request.c +++ b/sapi/fpm/fpm/fpm_request.c @@ -22,6 +22,7 @@ #include "zlog.h" static const char *requests_stages[] = { + [FPM_REQUEST_CREATING] = "Creating", [FPM_REQUEST_ACCEPTING] = "Idle", [FPM_REQUEST_READING_HEADERS] = "Reading headers", [FPM_REQUEST_INFO] = "Getting request information", diff --git a/sapi/fpm/fpm/fpm_request.h b/sapi/fpm/fpm/fpm_request.h index c1cde0111be47..1dcc7f78902fc 100644 --- a/sapi/fpm/fpm/fpm_request.h +++ b/sapi/fpm/fpm/fpm_request.h @@ -25,7 +25,8 @@ const char *fpm_request_get_stage_name(int stage); int fpm_request_last_activity(struct fpm_child_s *child, struct timeval *tv); enum fpm_request_stage_e { - FPM_REQUEST_ACCEPTING = 1, + FPM_REQUEST_CREATING, + FPM_REQUEST_ACCEPTING, FPM_REQUEST_READING_HEADERS, FPM_REQUEST_INFO, FPM_REQUEST_EXECUTING, From d9d991928f03c493de524ae19608e7f10373ee6e Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 30 May 2025 21:03:41 +0200 Subject: [PATCH 17/42] Fix memory leak when curl_slist_append() fails If curl_slist_append() returns NULL, then the original pointer is lost and not freed. Closes GH-18711. --- NEWS | 3 +++ ext/curl/interface.c | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 3b3900afff9d8..4088849616bc7 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,9 @@ PHP NEWS . Fixed GH-18695 (zend_ast_export() - float number is not preserved). (Oleg Efimov) +- Curl: + . Fix memory leak when setting a list via curl_setopt fails. (nielsdos) + - Date: . Fix leaks with multiple calls to DatePeriod iterator current(). (nielsdos) diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 1a270a1c32cea..61d830e8abfe1 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -2220,12 +2220,14 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue ZEND_HASH_FOREACH_VAL(ph, current) { ZVAL_DEREF(current); val = zval_get_tmp_string(current, &tmp_val); - slist = curl_slist_append(slist, ZSTR_VAL(val)); + struct curl_slist *new_slist = curl_slist_append(slist, ZSTR_VAL(val)); zend_tmp_string_release(tmp_val); - if (!slist) { + if (!new_slist) { + curl_slist_free_all(slist); php_error_docref(NULL, E_WARNING, "Could not build curl_slist"); return FAILURE; } + slist = new_slist; } ZEND_HASH_FOREACH_END(); if (slist) { From 75cea65c997a7d580abc41e61db2b1de15ee66c3 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 31 May 2025 12:14:27 +0200 Subject: [PATCH 18/42] Fix reference type confusion and leak in user random engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes GH-18718. Co-authored-by: Tim Düsterhus --- NEWS | 4 +++ ext/random/engine_user.c | 12 ++++++--- .../02_engine/user_reference_return.phpt | 25 +++++++++++++++++++ .../engine_unsafe_empty_string.phpt | 3 ++- 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 ext/random/tests/02_engine/user_reference_return.phpt diff --git a/NEWS b/NEWS index 4088849616bc7..02b90ff3dc7f6 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,10 @@ PHP NEWS . Fix warning not being emitted when failure to cancel a query with pg_cancel_query(). (Girgias) +- Random: + . Fix reference type confusion and leak in user random engine. + (nielsdos, timwolla) + - Readline: . Fix memory leak when calloc() fails in php_readline_completion_cb(). (nielsdos) diff --git a/ext/random/engine_user.c b/ext/random/engine_user.c index b45924d3bb7da..ce68521c129a1 100644 --- a/ext/random/engine_user.c +++ b/ext/random/engine_user.c @@ -27,6 +27,7 @@ static uint64_t generate(php_random_status *status) uint64_t result = 0; size_t size; zval retval; + zend_string *zstr; zend_call_known_instance_method_with_0_params(s->generate_method, s->object, &retval); @@ -34,8 +35,14 @@ static uint64_t generate(php_random_status *status) return 0; } + if (UNEXPECTED(Z_ISREF(retval))) { + zstr = Z_STR_P(Z_REFVAL(retval)); + } else { + zstr = Z_STR(retval); + } + /* Store generated size in a state */ - size = Z_STRLEN(retval); + size = ZSTR_LEN(zstr); /* Guard for over 64-bit results */ if (size > sizeof(uint64_t)) { @@ -46,11 +53,10 @@ static uint64_t generate(php_random_status *status) if (size > 0) { /* Endianness safe copy */ for (size_t i = 0; i < size; i++) { - result += ((uint64_t) (unsigned char) Z_STRVAL(retval)[i]) << (8 * i); + result += ((uint64_t) (unsigned char) ZSTR_VAL(zstr)[i]) << (8 * i); } } else { zend_throw_error(random_ce_Random_BrokenRandomEngineError, "A random engine must return a non-empty string"); - return 0; } zval_ptr_dtor(&retval); diff --git a/ext/random/tests/02_engine/user_reference_return.phpt b/ext/random/tests/02_engine/user_reference_return.phpt new file mode 100644 index 0000000000000..f54e65bed7559 --- /dev/null +++ b/ext/random/tests/02_engine/user_reference_return.phpt @@ -0,0 +1,25 @@ +--TEST-- +Random: Engine: User: Returning by reference works +--FILE-- +field; + } +} + +$randomizer = new Randomizer(new ReferenceEngine()); + +var_dump($randomizer->getBytes(64)); + +?> +--EXPECT-- +string(64) "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd" diff --git a/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt b/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt index 6aec7180b1ee5..8333df88d6454 100644 --- a/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt +++ b/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt @@ -10,7 +10,8 @@ final class EmptyStringEngine implements Engine { public function generate(): string { - return ''; + // Create a non-interned empty string. + return preg_replace('/./', '', random_bytes(4)); } } From 7f2299c8eb2304dbd599fcf98c06624dc8f31a63 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 31 May 2025 14:37:22 +0100 Subject: [PATCH 19/42] tests: Fix expectations Closes GH-18712 --- ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt | 2 +- ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt index 37a47df060a1c..f3302d77c1d63 100644 --- a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt @@ -44,7 +44,7 @@ array(6) { [0]=> string(14) "GET / HTTP/1.1" [1]=> - string(21) "Host: 127.0.0.1:%d" + string(%d) "Host: 127.0.0.1:%d" [2]=> string(17) "Connection: close" [3]=> diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt index 6c84679ff63bd..30d20f855419c 100644 --- a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt @@ -44,7 +44,7 @@ array(7) { [0]=> string(14) "GET / HTTP/1.1" [1]=> - string(21) "Host: 127.0.0.1:%d" + string(%d) "Host: 127.0.0.1:%d" [2]=> string(17) "Connection: close" [3]=> From d39d261b7eba3c4ee91e7387713a9e42dde8112f Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 31 May 2025 15:21:44 +0200 Subject: [PATCH 20/42] Fix memory leak in lookup_loc_range() Closes GH-18723. --- NEWS | 1 + ext/intl/locale/locale_methods.c | 1 + 2 files changed, 2 insertions(+) diff --git a/NEWS b/NEWS index 02b90ff3dc7f6..40806102246ab 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ PHP NEWS - Intl: . Fix memory leak in intl_datetime_decompose() on failure. (nielsdos) + . Fix memory leak in locale lookup on failure. (nielsdos) - Phar: . Add missing filter cleanups on phar failure. (nielsdos) diff --git a/ext/intl/locale/locale_methods.c b/ext/intl/locale/locale_methods.c index f810a61b6be12..44ae9a901a56e 100644 --- a/ext/intl/locale/locale_methods.c +++ b/ext/intl/locale/locale_methods.c @@ -1499,6 +1499,7 @@ static zend_string* lookup_loc_range(const char* loc_range, HashTable* hash_arr, zend_string_release_ex(can_loc_range, 0); } if(result == 0) { + efree(cur_loc_range); intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0); LOOKUP_CLEAN_RETURN(NULL); } From d8a17ca7c213242d4156931da496e1522f7286fd Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 1 Jun 2025 15:45:36 +0200 Subject: [PATCH 21/42] Fix test failures in engine_unsafe_empty_string.phpt (#18727) `/./` matches all characters but newlines, so if `random_bytes` generates a string with newlines in it, the resulting string is not empty. Fix this by adding the `s` modifier. --- ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt b/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt index 8333df88d6454..fe402b82b57b3 100644 --- a/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt +++ b/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt @@ -11,7 +11,7 @@ final class EmptyStringEngine implements Engine public function generate(): string { // Create a non-interned empty string. - return preg_replace('/./', '', random_bytes(4)); + return preg_replace('/./s', '', random_bytes(4)); } } From 9187caeab121b506afb1369a7d05eb8d90c18507 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 1 Jun 2025 17:34:17 +0200 Subject: [PATCH 22/42] Fix ext/enchant test SKIPIFs The resource check makes no sense, and the is_array() check doesn't achieve anything. Drop the former, and replace the latter with a ! check. Discovered while working on GH-18729. Closes GH-18731. --- ext/enchant/tests/broker_dict_exists.phpt | 3 +-- ext/enchant/tests/broker_free.phpt | 4 ---- ext/enchant/tests/broker_free_01.phpt | 4 ---- ext/enchant/tests/broker_free_02.phpt | 3 +-- ext/enchant/tests/broker_free_dict.phpt | 3 +-- ext/enchant/tests/broker_get_error.phpt | 4 ---- ext/enchant/tests/broker_init.phpt | 4 ---- ext/enchant/tests/broker_request_dict.phpt | 3 +-- ext/enchant/tests/broker_request_dict_01.phpt | 3 +-- ext/enchant/tests/broker_request_dict_error_on_empty_tag.phpt | 4 ---- ext/enchant/tests/broker_request_pwl_dict.phpt | 4 ---- ext/enchant/tests/broker_set_ordering.phpt | 3 +-- ext/enchant/tests/bug53070.phpt | 1 - ext/enchant/tests/dict_add_to_personal.phpt | 3 +-- ext/enchant/tests/dict_add_to_session.phpt | 3 +-- ext/enchant/tests/dict_check.phpt | 3 +-- ext/enchant/tests/dict_describe.phpt | 3 +-- ext/enchant/tests/dict_get_error.phpt | 3 +-- ext/enchant/tests/dict_is_in_session.phpt | 3 +-- ext/enchant/tests/dict_quick_check.phpt | 3 +-- ext/enchant/tests/dict_quick_check_01.phpt | 3 +-- ext/enchant/tests/dict_store_replacement.phpt | 3 +-- ext/enchant/tests/dict_suggest.phpt | 3 +-- ext/enchant/tests/enchant_broker_set_dict_path.phpt | 3 +-- ext/enchant/tests/invalidobj.phpt | 4 ---- ext/enchant/tests/null_bytes.phpt | 3 +-- 26 files changed, 18 insertions(+), 65 deletions(-) diff --git a/ext/enchant/tests/broker_dict_exists.phpt b/ext/enchant/tests/broker_dict_exists.phpt index c4ca5d91eb3bd..494d2a539b3cc 100644 --- a/ext/enchant/tests/broker_dict_exists.phpt +++ b/ext/enchant/tests/broker_dict_exists.phpt @@ -6,8 +6,7 @@ marcosptf - enchant --SKIPIF-- --FILE-- --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- --FILE-- --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- enchant --SKIPIF-- --FILE-- ")) die('skip libenchant v1 only'); ?> --FILE-- diff --git a/ext/enchant/tests/dict_add_to_personal.phpt b/ext/enchant/tests/dict_add_to_personal.phpt index 1fe6c1ff4a475..54d2be5e5d177 100644 --- a/ext/enchant/tests/dict_add_to_personal.phpt +++ b/ext/enchant/tests/dict_add_to_personal.phpt @@ -6,8 +6,7 @@ marcosptf - enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- --FILE-- enchant --SKIPIF-- ")) die('skip libenchant v1 only'); ?> --FILE-- diff --git a/ext/enchant/tests/invalidobj.phpt b/ext/enchant/tests/invalidobj.phpt index 8dbfc2e1f9a60..d9f60075754d9 100644 --- a/ext/enchant/tests/invalidobj.phpt +++ b/ext/enchant/tests/invalidobj.phpt @@ -2,10 +2,6 @@ invalid object raise exception() function --EXTENSIONS-- enchant ---SKIPIF-- - --FILE-- --FILE-- Date: Tue, 3 Jun 2025 21:19:15 +0200 Subject: [PATCH 23/42] Fix memory leak of X509_STORE in php_openssl_setup_verify() on failure Closes GH-18750. --- NEWS | 4 ++++ ext/openssl/openssl.c | 1 + ext/openssl/tests/memory_leak_x509_store.phpt | 22 +++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 ext/openssl/tests/memory_leak_x509_store.phpt diff --git a/NEWS b/NEWS index 40806102246ab..d6f38f9adceb9 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,10 @@ PHP NEWS . Fix memory leak in intl_datetime_decompose() on failure. (nielsdos) . Fix memory leak in locale lookup on failure. (nielsdos) +- OpenSSL: + . Fix memory leak of X509_STORE in php_openssl_setup_verify() on failure. + (nielsdos) + - Phar: . Add missing filter cleanups on phar failure. (nielsdos) . Fixed bug GH-18642 (Signed integer overflow in ext/phar fseek). (nielsdos) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index c978859b7ec00..718f946ad176d 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -2421,6 +2421,7 @@ static X509_STORE *php_openssl_setup_verify(zval *calist, uint32_t arg_num) ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(calist), item) { zend_string *str = zval_try_get_string(item); if (UNEXPECTED(!str)) { + X509_STORE_free(store); return NULL; } diff --git a/ext/openssl/tests/memory_leak_x509_store.phpt b/ext/openssl/tests/memory_leak_x509_store.phpt new file mode 100644 index 0000000000000..bc9b113602a33 --- /dev/null +++ b/ext/openssl/tests/memory_leak_x509_store.phpt @@ -0,0 +1,22 @@ +--TEST-- +Memory leak of X509_STORE in php_openssl_setup_verify() on failure +--EXTENSIONS-- +openssl +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +stop From e13ba36abbce730b552768d13a575799937a6e2d Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 4 Jun 2025 19:22:06 +0100 Subject: [PATCH 24/42] ext/tidy: anticipate tidyOptIsReadOnly retirement. using tidyOptGetCategory when possible. related GH-18751 close GH-18763 --- NEWS | 1 + ext/tidy/config.m4 | 5 +++++ ext/tidy/tidy.c | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/NEWS b/NEWS index d6f38f9adceb9..9b2c97db0b1cf 100644 --- a/NEWS +++ b/NEWS @@ -44,6 +44,7 @@ PHP NEWS - Tidy: . Fix memory leak in tidy output handler on error. (nielsdos) + . Fix tidyOptIsReadonly deprecation, using tidyOptGetCategory. (David Carlier) 05 Jun 2025, PHP 8.3.22 diff --git a/ext/tidy/config.m4 b/ext/tidy/config.m4 index bc0976a1dd9b3..569cb3672944b 100644 --- a/ext/tidy/config.m4 +++ b/ext/tidy/config.m4 @@ -62,6 +62,11 @@ if test "$PHP_TIDY" != "no"; then AC_DEFINE(HAVE_TIDYRELEASEDATE,1,[ ]) ], [], []) + PHP_CHECK_LIBRARY($TIDY_LIB_NAME,tidyOptGetCategory, + [ + AC_DEFINE(HAVE_TIDYOPTGETCATEGORY,1,[ ]) + ], [], []) + PHP_ADD_LIBRARY_WITH_PATH($TIDY_LIB_NAME, $TIDY_LIBDIR, TIDY_SHARED_LIBADD) PHP_ADD_INCLUDE($TIDY_INCDIR) diff --git a/ext/tidy/tidy.c b/ext/tidy/tidy.c index 46dd637f40e54..a42e2bc203770 100644 --- a/ext/tidy/tidy.c +++ b/ext/tidy/tidy.c @@ -232,7 +232,11 @@ static int _php_tidy_set_tidy_opt(TidyDoc doc, char *optname, zval *value) return FAILURE; } +#if defined(HAVE_TIDYOPTGETCATEGORY) + if (tidyOptGetCategory(opt) == TidyInternalCategory) { +#else if (tidyOptIsReadOnly(opt)) { +#endif php_error_docref(NULL, E_WARNING, "Attempting to set read-only option \"%s\"", optname); return FAILURE; } From 42f6c15186b289fa16d71bf0a33cbc5da6098c43 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Fri, 21 Feb 2025 15:23:24 +0100 Subject: [PATCH 25/42] Fix bug #74796: Requests through http proxy set peer name This issue happens because http wrapper sets peer_name but then does not remove so it stays in the context. The fix removes the peer name from the context after enabling crypto. In addition to bug #74796, this also fixes bug #76196. In addition it should be a final fix for those SOAP bugs: bug #69783 bug #52913 bug #61463 --- NEWS | 2 + ext/openssl/tests/bug74796.phpt | 175 ++++++++++++++++++++++++++++++ ext/standard/http_fopen_wrapper.c | 6 + main/streams/php_stream_context.h | 3 +- main/streams/streams.c | 14 +++ 5 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 ext/openssl/tests/bug74796.phpt diff --git a/NEWS b/NEWS index 9b2c97db0b1cf..6867a3dbcaeb8 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,8 @@ PHP NEWS - OpenSSL: . Fix memory leak of X509_STORE in php_openssl_setup_verify() on failure. (nielsdos) + . Fixed bug #74796 (Requests through http proxy set peer name). + (Jakub Zelenka) - Phar: . Add missing filter cleanups on phar failure. (nielsdos) diff --git a/ext/openssl/tests/bug74796.phpt b/ext/openssl/tests/bug74796.phpt new file mode 100644 index 0000000000000..a5c99e61697a8 --- /dev/null +++ b/ext/openssl/tests/bug74796.phpt @@ -0,0 +1,175 @@ +--TEST-- +Bug #74796: TLS encryption fails behind HTTP proxy +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + [ + 'SNI_server_certs' => [ + "cs.php.net" => __DIR__ . "/sni_server_cs.pem", + "uk.php.net" => __DIR__ . "/sni_server_uk.pem", + "us.php.net" => __DIR__ . "/sni_server_us.pem" + ] + ]]); + + $server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, $serverFlags, $ctx); + phpt_notify_server_start($server); + + for ($i=0; $i < 3; $i++) { + $conn = stream_socket_accept($server, 3); + fwrite($conn, "HTTP/1.0 200 OK\r\n\r\nHello from server $i"); + fclose($conn); + } + + phpt_wait(); +CODE; + +$proxyCode = <<<'CODE' + function parse_sni_from_client_hello($data) { + $sni = null; + + if (strlen($data) < 5 || ord($data[0]) != 0x16) return null; + + $session_id_len = ord($data[43]); + $ptr = 44 + $session_id_len; + + // Cipher suites length + $cipher_suites_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]); + $ptr += 2 + $cipher_suites_len; + + // Compression methods length + $compression_methods_len = ord($data[$ptr]); + $ptr += 1 + $compression_methods_len; + + // Extensions length + if ($ptr + 2 > strlen($data)) return null; + $extensions_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]); + $ptr += 2; + + $extensions_end = $ptr + $extensions_len; + + while ($ptr + 4 <= $extensions_end) { + $ext_type = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]); + $ext_len = (ord($data[$ptr+2]) << 8) | ord($data[$ptr+3]); + $ptr += 4; + + if ($ext_type === 0x00) { // SNI extension + if ($ptr + 2 > strlen($data)) break; + $name_list_len = (ord($data[$ptr]) << 8) | ord($data[$ptr+1]); + $ptr += 2; + + if ($ptr + 3 > strlen($data)) break; + $name_type = ord($data[$ptr]); + $name_len = (ord($data[$ptr+1]) << 8) | ord($data[$ptr+2]); + $ptr += 3; + + if ($name_type === 0) { // host_name type + $sni = substr($data, $ptr, $name_len); + break; + } + } + + $ptr += $ext_len; + } + + return $sni; + } + + $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; + $server = stream_socket_server("tcp://127.0.0.1:0", $errornum, $errorstr, $flags); + phpt_notify_server_start($server); + + for ($i=0; $i < 3; $i++) { + $upstream = stream_socket_client("tcp://{{ ADDR }}", $errornum, $errorstr, 30, STREAM_CLIENT_CONNECT); + stream_set_blocking($upstream, false); + + $conn = stream_socket_accept($server); + stream_set_blocking($conn, true); + + // reading CONNECT request headers + while (($line = fgets($conn)) !== false) { + if (rtrim($line) === '') break; // empty line means end of headers + } + + // successful CONNECT response + fwrite($conn, "HTTP/1.0 200 Connection established\r\n\r\n"); + + // tunnel data + stream_set_blocking($conn, false); + $firstRead = true; + while (!feof($conn) && !feof($upstream)) { + $clientData = fread($conn, 8192); + if ($clientData !== false && $clientData !== '') { + if ($firstRead) { + $sni = parse_sni_from_client_hello($clientData); + if ($sni !== null) { + file_put_contents(__DIR__ . "/bug74796_proxy_sni.log", $sni . "\n", FILE_APPEND); + } + $firstRead = false; + } + fwrite($upstream, $clientData); + } + + $serverData = fread($upstream, 8192); + if ($serverData !== false && $serverData !== '') { + fwrite($conn, $serverData); + } + } + fclose($conn); + fclose($upstream); + phpt_wait(); + } +CODE; + +$clientCode = <<<'CODE' + $clientCtx = stream_context_create([ + 'ssl' => [ + 'cafile' => __DIR__ . '/sni_server_ca.pem', + 'verify_peer' => true, + 'verify_peer_name' => true, + ], + "http" => [ + "proxy" => "tcp://{{ ADDR }}" + ], + ]); + + // servers + $hosts = ["cs.php.net", "uk.php.net", "us.php.net"]; + foreach ($hosts as $host) { + var_dump(file_get_contents("https://$host/", false, $clientCtx)); + var_dump(stream_context_get_options($clientCtx)['ssl']['peer_name'] ?? null); + phpt_notify('proxy'); + } + + echo file_get_contents(__DIR__ . "/bug74796_proxy_sni.log"); + + phpt_notify('server'); +CODE; + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, [ + 'server' => $serverCode, + 'proxy' => $proxyCode, +]); +?> +--CLEAN-- + +--EXPECT-- +string(19) "Hello from server 0" +NULL +string(19) "Hello from server 1" +NULL +string(19) "Hello from server 2" +NULL +cs.php.net +uk.php.net +us.php.net diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index b4d065dd0b625..040ee4eabf78b 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -470,12 +470,14 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (stream && use_proxy && use_ssl) { smart_str header = {0}; + bool reset_ssl_peer_name = false; /* Set peer_name or name verification will try to use the proxy server name */ if (!context || (tmpzval = php_stream_context_get_option(context, "ssl", "peer_name")) == NULL) { ZVAL_STR_COPY(&ssl_proxy_peer_name, resource->host); php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name", &ssl_proxy_peer_name); zval_ptr_dtor(&ssl_proxy_peer_name); + reset_ssl_peer_name = true; } smart_str_appendl(&header, "CONNECT ", sizeof("CONNECT ")-1); @@ -572,6 +574,10 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, stream = NULL; } } + + if (reset_ssl_peer_name) { + php_stream_context_unset_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name"); + } } php_stream_http_response_header_info_init(&header_info); diff --git a/main/streams/php_stream_context.h b/main/streams/php_stream_context.h index d4ebe29bc162e..56a1f53747116 100644 --- a/main/streams/php_stream_context.h +++ b/main/streams/php_stream_context.h @@ -59,7 +59,8 @@ PHPAPI zval *php_stream_context_get_option(php_stream_context *context, const char *wrappername, const char *optionname); PHPAPI void php_stream_context_set_option(php_stream_context *context, const char *wrappername, const char *optionname, zval *optionvalue); - +void php_stream_context_unset_option(php_stream_context *context, + const char *wrappername, const char *optionname); PHPAPI php_stream_notifier *php_stream_notification_alloc(void); PHPAPI void php_stream_notification_free(php_stream_notifier *notifier); END_EXTERN_C() diff --git a/main/streams/streams.c b/main/streams/streams.c index 4e0aaa53b443e..4f9c88e4774c4 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2432,6 +2432,20 @@ PHPAPI void php_stream_context_set_option(php_stream_context *context, SEPARATE_ARRAY(wrapperhash); zend_hash_str_update(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname), optionvalue); } + +void php_stream_context_unset_option(php_stream_context *context, + const char *wrappername, const char *optionname) +{ + zval *wrapperhash; + + wrapperhash = zend_hash_str_find(Z_ARRVAL(context->options), wrappername, strlen(wrappername)); + if (NULL == wrapperhash) { + return; + } + SEPARATE_ARRAY(&context->options); + SEPARATE_ARRAY(wrapperhash); + zend_hash_str_del(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname)); +} /* }}} */ /* {{{ php_stream_dirent_alphasort */ From 444cc78a3e94e90a9424e7200ff9dca410d3f28f Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 5 Jun 2025 16:18:06 +0200 Subject: [PATCH 26/42] Skip OpenSSL proxy test for bug #74796 on Windows --- ext/openssl/tests/bug74796.phpt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/openssl/tests/bug74796.phpt b/ext/openssl/tests/bug74796.phpt index a5c99e61697a8..b3f594d5e60f4 100644 --- a/ext/openssl/tests/bug74796.phpt +++ b/ext/openssl/tests/bug74796.phpt @@ -5,6 +5,9 @@ openssl --SKIPIF-- --FILE-- Date: Thu, 5 Jun 2025 21:51:30 +0200 Subject: [PATCH 27/42] Fix compile without ZEND_MM_STORAGE --- Zend/zend_alloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index d531270d445ed..573fd5fa26b80 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -3043,7 +3043,7 @@ ZEND_API zend_mm_storage *zend_mm_get_storage(zend_mm_heap *heap) #if ZEND_MM_STORAGE return heap->storage; #else - return NULL + return NULL; #endif } From ae92b85572ac9a5f14103cdedbf6448f9e8db09d Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Wed, 14 Feb 2024 13:52:01 +0100 Subject: [PATCH 28/42] Fix linking ext/curl against OpenSSL (#13262) This is backport for 8.3 of b222c020bfa876ae1cea87406beb8af0b53f0fab that originally targeted only 8.4+. This is however a bug fix. Following 68f6ab711323678382d2746e57358d3f57a3446b, the ext/curl doesn't need to be linked against OpenSSL anymore, if curl_version_info_data ssl_version is OpenSSL/1.1 or later. With OpenSSL 3 and later the check for old SSL crypto locking callbacks was detected here. This also uses a common PHP_SETUP_OPENSSL macro for checking OpenSSL and syncs the minimum OpenSSL version (currently 1.0.2 or later) across the PHP build system. --- NEWS | 1 + ext/curl/config.m4 | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index 6867a3dbcaeb8..5eb1694b5e816 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ PHP NEWS - Curl: . Fix memory leak when setting a list via curl_setopt fails. (nielsdos) + . Fix incorrect OpenSSL version detection. (Peter Kokot) - Date: . Fix leaks with multiple calls to DatePeriod iterator current(). (nielsdos) diff --git a/ext/curl/config.m4 b/ext/curl/config.m4 index 3b11739654bd6..c0325f990ad11 100644 --- a/ext/curl/config.m4 +++ b/ext/curl/config.m4 @@ -28,6 +28,7 @@ if test "$PHP_CURL" != "no"; then AC_MSG_CHECKING([for libcurl linked against old openssl]) AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include #include #include @@ -39,9 +40,18 @@ int main(int argc, char *argv[]) const char *ptr = data->ssl_version; while(*ptr == ' ') ++ptr; - if (strncasecmp(ptr, "OpenSSL/1.1", sizeof("OpenSSL/1.1")-1) == 0) { - /* New OpenSSL version */ - return 3; + int major, minor; + if (sscanf(ptr, "OpenSSL/%d", &major) == 1) { + if (major >= 3) { + /* OpenSSL version 3 or later */ + return 4; + } + } + if (sscanf(ptr, "OpenSSL/%d.%d", &major, &minor) == 2) { + if (major > 1 || (major == 1 && minor >= 1)) { + /* OpenSSL version 1.1 or later */ + return 3; + } } if (strncasecmp(ptr, "OpenSSL", sizeof("OpenSSL")-1) == 0) { /* Old OpenSSL version */ @@ -56,11 +66,7 @@ int main(int argc, char *argv[]) ]])],[ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_CURL_OLD_OPENSSL], [1], [Have cURL with old OpenSSL]) - PKG_CHECK_MODULES([OPENSSL], [openssl], [ - PHP_EVAL_LIBLINE($OPENSSL_LIBS, CURL_SHARED_LIBADD) - PHP_EVAL_INCLINE($OPENSSL_CFLAGS) - AC_CHECK_HEADERS([openssl/crypto.h]) - ], []) + PHP_SETUP_OPENSSL(CURL_SHARED_LIBADD,[AC_CHECK_HEADERS([openssl/crypto.h])],[]) ], [ AC_MSG_RESULT([no]) ], [ From 9a9d98e02fe19c0f0df5aca9da50da5f74446515 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 4 Jun 2025 12:09:37 +0200 Subject: [PATCH 29/42] Do not delete main chunk in zend_gc Closes GH-18756. Co-authored-by: Arnaud Le Blanc --- NEWS | 1 + Zend/tests/gh18756.phpt | 13 +++++++++++++ Zend/zend_alloc.c | 2 +- ext/zend_test/test.c | 10 ++++++++++ ext/zend_test/test.stub.php | 2 ++ ext/zend_test/test_arginfo.h | 6 +++++- 6 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/gh18756.phpt diff --git a/NEWS b/NEWS index 5eb1694b5e816..4db7d93ff9808 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ PHP NEWS - Core: . Fixed GH-18695 (zend_ast_export() - float number is not preserved). (Oleg Efimov) + . Do not delete main chunk in zend_gc. (danog, Arnaud) - Curl: . Fix memory leak when setting a list via curl_setopt fails. (nielsdos) diff --git a/Zend/tests/gh18756.phpt b/Zend/tests/gh18756.phpt new file mode 100644 index 0000000000000..6e112d9060499 --- /dev/null +++ b/Zend/tests/gh18756.phpt @@ -0,0 +1,13 @@ +--TEST-- +Bug GH-18756: Zend MM may delete the main chunk +--EXTENSIONS-- +zend_test +--FILE-- + +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 573fd5fa26b80..2f80bdae3cfbd 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2047,7 +2047,7 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) i++; } } - if (chunk->free_pages == ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE) { + if (chunk->free_pages == ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE && chunk != heap->main_chunk) { zend_mm_chunk *next_chunk = chunk->next; zend_mm_delete_chunk(heap, chunk); diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index a7dd604d89ef3..04ece8bd2537e 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1516,3 +1516,13 @@ static PHP_FUNCTION(zend_test_create_throwing_resource) zend_resource *res = zend_register_resource(NULL, le_throwing_resource); ZVAL_RES(return_value, res); } + +static PHP_FUNCTION(zend_test_gh18756) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zend_mm_heap *heap = zend_mm_startup(); + zend_mm_gc(heap); + zend_mm_gc(heap); + zend_mm_shutdown(heap, true, false); +} diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index c9477eef52712..f9cb93b5a1ccb 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -262,6 +262,8 @@ function zend_test_cast_fread($stream): void {} function zend_test_is_zend_ptr(int $addr): bool {} function zend_test_log_err_debug(string $str): void {} + + function zend_test_gh18756(): void {} } namespace ZendTestNS { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 5947a6587bbed..c7e3df5c58d24 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 9ddaf4d659226c55d49221c71702fa373d42695e */ + * Stub hash: 2f161861ab09b6b5b594dc2db7c2c9df49d76aa7 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -162,6 +162,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_log_err_debug, 0, 1, I ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0) ZEND_END_ARG_INFO() +#define arginfo_zend_test_gh18756 arginfo_zend_test_void_return + #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_test_is_pcre_bundled #define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_void_return @@ -292,6 +294,7 @@ static ZEND_FUNCTION(zend_test_set_fmode); static ZEND_FUNCTION(zend_test_cast_fread); static ZEND_FUNCTION(zend_test_is_zend_ptr); static ZEND_FUNCTION(zend_test_log_err_debug); +static ZEND_FUNCTION(zend_test_gh18756); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -372,6 +375,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_cast_fread, arginfo_zend_test_cast_fread) ZEND_FE(zend_test_is_zend_ptr, arginfo_zend_test_is_zend_ptr) ZEND_FE(zend_test_log_err_debug, arginfo_zend_test_log_err_debug) + ZEND_FE(zend_test_gh18756, arginfo_zend_test_gh18756) ZEND_NS_FALIAS("ZendTestNS2", namespaced_func, ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func) ZEND_NS_DEP_FALIAS("ZendTestNS2", namespaced_deprecated_func, ZendTestNS2_namespaced_deprecated_func, arginfo_ZendTestNS2_namespaced_deprecated_func) ZEND_NS_FALIAS("ZendTestNS2", namespaced_aliased_func, zend_test_void_return, arginfo_ZendTestNS2_namespaced_aliased_func) From ef92e06de19b51f5655b80ab41e5abd849ca6ffa Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 7 Jun 2025 00:33:34 +0200 Subject: [PATCH 30/42] Fix memory leak on php_odbc_fetch_hash() failure The array is initialized but not freed. Closes GH-18787. --- NEWS | 3 +++ ext/odbc/php_odbc.c | 1 + 2 files changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 4db7d93ff9808..2956cb8c6af0f 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,9 @@ PHP NEWS . Fix memory leak in intl_datetime_decompose() on failure. (nielsdos) . Fix memory leak in locale lookup on failure. (nielsdos) +- ODBC: + . Fix memory leak on php_odbc_fetch_hash() failure. (nielsdos) + - OpenSSL: . Fix memory leak of X509_STORE in php_openssl_setup_verify() on failure. (nielsdos) diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c index 579b5e989bd3a..77ba85fe12ae8 100644 --- a/ext/odbc/php_odbc.c +++ b/ext/odbc/php_odbc.c @@ -1370,6 +1370,7 @@ static void php_odbc_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int result_type) if (rc == SQL_ERROR) { odbc_sql_error(result->conn_ptr, result->stmt, "SQLGetData"); efree(buf); + zval_ptr_dtor(return_value); RETURN_FALSE; } From 786090b35d2c339912c76588dacf32698f2e2d31 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 7 Jun 2025 00:36:08 +0200 Subject: [PATCH 31/42] pdo_odbc: Fix memory leak if WideCharToMultiByte() fails Closes GH-18788. --- NEWS | 3 +++ ext/pdo_odbc/odbc_stmt.c | 1 + 2 files changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 2956cb8c6af0f..2208cd6e59aad 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,9 @@ PHP NEWS . Add missing filter cleanups on phar failure. (nielsdos) . Fixed bug GH-18642 (Signed integer overflow in ext/phar fseek). (nielsdos) +- PDO ODBC: + . Fix memory leak if WideCharToMultiByte() fails. (nielsdos) + - PGSQL: . Fix warning not being emitted when failure to cancel a query with pg_cancel_query(). (Girgias) diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index 4bf7162ea06e6..9f7ab24f8fadc 100644 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -104,6 +104,7 @@ static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, zval *result) zend_string *str = zend_string_alloc(ret, 0); ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result)/sizeof(WCHAR), ZSTR_VAL(str), ZSTR_LEN(str), NULL, NULL); if (ret == 0) { + zend_string_efree(str); return PDO_ODBC_CONV_FAIL; } From b3c8afe272a6919248986c703c2e1defc73ff707 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 5 Jun 2025 19:37:46 +0200 Subject: [PATCH 32/42] Fix GH-18743: Incompatibility in Inline TLS Assembly on Alpine 3.22 GAS started checking the relocation for tlsgd: it must use the %rdi register. However, the inline assembly now uses %rax instead. Fix it by changing the "=a" output register to "=D". Source: https://github.com/bminor/binutils-gdb/blob/ec181e1710e37007a8d95c284609bfaa5868d086/gas/config/tc-i386.c#L6793 gottpoff is unaffected. Closes GH-18779. --- NEWS | 4 ++++ ext/opcache/jit/zend_jit_x86.dasc | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 2208cd6e59aad..bf705c1da0b4d 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,10 @@ PHP NEWS - ODBC: . Fix memory leak on php_odbc_fetch_hash() failure. (nielsdos) +- Opcache: + . Fixed bug GH-18743 (Incompatibility in Inline TLS Assembly on Alpine 3.22). + (nielsdos, Arnaud) + - OpenSSL: . Fix memory leak of X509_STORE in php_openssl_setup_verify() on failure. (nielsdos) diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index 1f1abb59a1c24..7061f6b2b73ad 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -2910,7 +2910,7 @@ static int zend_jit_setup(void) __asm__( "leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n" - : "=a" (ti)); + : "=D" (ti)); tsrm_tls_offset = ti[1]; tsrm_tls_index = ti[0] * 8; #elif defined(__FreeBSD__) @@ -2918,7 +2918,7 @@ static int zend_jit_setup(void) __asm__( "leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n" - : "=a" (ti)); + : "=D" (ti)); tsrm_tls_offset = ti[1]; /* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/bf56e8b9c8639ac4447d223b83cdc128107cc3cd/libexec/rtld-elf/rtld.c#L5260) */ tsrm_tls_index = (ti[0] + 1) * 8; @@ -2927,7 +2927,7 @@ static int zend_jit_setup(void) __asm__( "leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n" - : "=a" (ti)); + : "=D" (ti)); tsrm_tls_offset = ti[1]; tsrm_tls_index = ti[0] * 16; #endif From 186a8116beaf1148911032016e539e0ed52524f0 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:16:57 +0200 Subject: [PATCH 33/42] Fix test conflict between copy_variation2-win32-mb.phpt and copy_variation2-win32.phpt Closes GH-18809. --- .../tests/file/copy_variation2-win32-mb.phpt | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/ext/standard/tests/file/copy_variation2-win32-mb.phpt b/ext/standard/tests/file/copy_variation2-win32-mb.phpt index 4251a24e54cf7..67d84ee3e32c1 100644 --- a/ext/standard/tests/file/copy_variation2-win32-mb.phpt +++ b/ext/standard/tests/file/copy_variation2-win32-mb.phpt @@ -22,24 +22,24 @@ fclose($file_handle); $dest_files = array( /* File names containing special(non-alpha numeric) characters */ - "_copy_variation2.tmp", - "@copy_variation2.tmp", - "#copy_variation2.tmp", - "+copy_variation2.tmp", - "?copy_variation2.tmp", - ">copy_variation2.tmp", - "!copy_variation2.tmp", - "©_variation2.tmp", - "(copy_variation2.tmp", - ":copy_variation2.tmp", - ";copy_variation2.tmp", - "=copy_variation2.tmp", - "[copy_variation2.tmp", - "^copy_variation2.tmp", - "{copy_variation2.tmp", - "|copy_variation2.tmp", - "~copy_variation2.tmp", - "\$copy_variation2.tmp" + "_copy_variation2_mb.tmp", + "@copy_variation2_mb.tmp", + "#copy_variation2_mb.tmp", + "+copy_variation2_mb.tmp", + "?copy_variation2_mb.tmp", + ">copy_variation2_mb.tmp", + "!copy_variation2_mb.tmp", + "©_variation2_mb.tmp", + "(copy_variation2_mb.tmp", + ":copy_variation2_mb.tmp", + ";copy_variation2_mb.tmp", + "=copy_variation2_mb.tmp", + "[copy_variation2_mb.tmp", + "^copy_variation2_mb.tmp", + "{copy_variation2_mb.tmp", + "|copy_variation2_mb.tmp", + "~copy_variation2_mb.tmp", + "\$copy_variation2_mb.tmp" ); echo "Size of the source file before copy operation => "; @@ -90,28 +90,28 @@ Size of the source file before copy operation => int(1500) -- Iteration 1 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/_copy_variation2.tmp +Destination file name => %s/_copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 2 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/@copy_variation2.tmp +Destination file name => %s/@copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 3 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/#copy_variation2.tmp +Destination file name => %s/#copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 4 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/+copy_variation2.tmp +Destination file name => %s/+copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) @@ -130,21 +130,21 @@ Existence of destination file => bool(false) -- Iteration 7 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/!copy_variation2.tmp +Destination file name => %s/!copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 8 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/©_variation2.tmp +Destination file name => %s/©_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 9 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/(copy_variation2.tmp +Destination file name => %s/(copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) @@ -157,35 +157,35 @@ Existence of destination file => bool(false) -- Iteration 11 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/;copy_variation2.tmp +Destination file name => %s/;copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 12 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/=copy_variation2.tmp +Destination file name => %s/=copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 13 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/[copy_variation2.tmp +Destination file name => %s/[copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 14 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/^copy_variation2.tmp +Destination file name => %s/^copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 15 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/{copy_variation2.tmp +Destination file name => %s/{copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) @@ -198,14 +198,14 @@ Existence of destination file => bool(false) -- Iteration 17 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/~copy_variation2.tmp +Destination file name => %s/~copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) -- Iteration 18 -- Copy operation => bool(true) Existence of destination file => bool(true) -Destination file name => %s/$copy_variation2.tmp +Destination file name => %s/$copy_variation2_mb.tmp Size of source file => int(1500) Size of destination file => int(1500) *** Done *** From d11f9717fdb10bccc4e17bd20508fb7b6d5c9359 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:34:22 +0200 Subject: [PATCH 34/42] zend_alloc: Fix compile with ZEND_MM_STAT=0 Closes GH-18811. --- NEWS | 1 + Zend/zend_alloc.c | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/NEWS b/NEWS index bf705c1da0b4d..11fb787662c4a 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,7 @@ PHP NEWS . Fixed GH-18695 (zend_ast_export() - float number is not preserved). (Oleg Efimov) . Do not delete main chunk in zend_gc. (danog, Arnaud) + . Fix compile issues with zend_alloc and some non-default options. (nielsdos) - Curl: . Fix memory leak when setting a list via curl_setopt fails. (nielsdos) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 2f80bdae3cfbd..47e9967a1e29f 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2274,7 +2274,9 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent) /* Make sure the heap free below does not use tracked_free(). */ heap->custom_heap.std._free = free; } +#if ZEND_MM_STAT heap->size = 0; +#endif } if (full) { @@ -2820,6 +2822,7 @@ static zend_always_inline zval *tracked_get_size_zv(zend_mm_heap *heap, void *pt } static zend_always_inline void tracked_check_limit(zend_mm_heap *heap, size_t add_size) { +#if ZEND_MM_STAT if (add_size > heap->limit - heap->size && !heap->overflow) { #if ZEND_DEBUG zend_mm_safe_error(heap, @@ -2831,6 +2834,7 @@ static zend_always_inline void tracked_check_limit(zend_mm_heap *heap, size_t ad heap->limit, add_size); #endif } +#endif } static void *tracked_malloc(size_t size) @@ -2844,7 +2848,9 @@ static void *tracked_malloc(size_t size) } tracked_add(heap, ptr, size); +#if ZEND_MM_STAT heap->size += size; +#endif return ptr; } @@ -2855,7 +2861,9 @@ static void tracked_free(void *ptr) { zend_mm_heap *heap = AG(mm_heap); zval *size_zv = tracked_get_size_zv(heap, ptr); +#if ZEND_MM_STAT heap->size -= Z_LVAL_P(size_zv); +#endif zend_hash_del_bucket(heap->tracked_allocs, (Bucket *) size_zv); free(ptr); } @@ -2880,7 +2888,9 @@ static void *tracked_realloc(void *ptr, size_t new_size) { ptr = __zend_realloc(ptr, new_size); tracked_add(heap, ptr, new_size); +#if ZEND_MM_STAT heap->size += new_size - old_size; +#endif return ptr; } From fe3bea090e598cc7d543dc6086c7a65f6e6787f1 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:17:33 +0200 Subject: [PATCH 35/42] Fix technically incorrect sizeof This doesn't actually matter because both `*sal` and `**sal` are pointer sized, but this makes analysers happy. Fixes bug #68866. Closes GH-18816. --- main/network.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/network.c b/main/network.c index d4938a4a08c1e..8de81a6271a2f 100644 --- a/main/network.c +++ b/main/network.c @@ -227,7 +227,7 @@ PHPAPI int php_network_getaddresses(const char *host, int socktype, struct socka for (n = 1; (sai = sai->ai_next) != NULL; n++) ; - *sal = safe_emalloc((n + 1), sizeof(*sal), 0); + *sal = safe_emalloc((n + 1), sizeof(**sal), 0); sai = res; sap = *sal; @@ -266,7 +266,7 @@ PHPAPI int php_network_getaddresses(const char *host, int socktype, struct socka in = *((struct in_addr *) host_info->h_addr); } - *sal = safe_emalloc(2, sizeof(*sal), 0); + *sal = safe_emalloc(2, sizeof(**sal), 0); sap = *sal; *sap = emalloc(sizeof(struct sockaddr_in)); (*sap)->sa_family = AF_INET; From 0cd3ebfc40c6f4aa90fad8bab05b09f274465385 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:49:27 +0200 Subject: [PATCH 36/42] Fix 'phpdbg --help' segfault on shutdown with USE_ZEND_ALLOC=0 This hack not only breaks the handling of custom allocators, but also breaks if zend_alloc is compiled with USE_CUSTOM_MM. This hack is just no good, if you want leak information then use ASAN. Closes GH-18813. --- NEWS | 3 +++ sapi/phpdbg/phpdbg.c | 6 ------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 11fb787662c4a..a665633283d0c 100644 --- a/NEWS +++ b/NEWS @@ -39,6 +39,9 @@ PHP NEWS . Add missing filter cleanups on phar failure. (nielsdos) . Fixed bug GH-18642 (Signed integer overflow in ext/phar fseek). (nielsdos) +- PHPDBG: + . Fix 'phpdbg --help' segfault on shutdown with USE_ZEND_ALLOC=0. (nielsdos) + - PDO ODBC: . Fix memory leak if WideCharToMultiByte() fails. (nielsdos) diff --git a/sapi/phpdbg/phpdbg.c b/sapi/phpdbg/phpdbg.c index 5a4dd6acdbe2f..f27d87de84187 100644 --- a/sapi/phpdbg/phpdbg.c +++ b/sapi/phpdbg/phpdbg.c @@ -179,12 +179,6 @@ static PHP_MSHUTDOWN_FUNCTION(phpdbg) /* {{{ */ phpdbg_notice("Script ended normally"); } - /* hack to restore mm_heap->use_custom_heap in order to receive memory leak info */ - if (use_mm_wrappers) { - /* ASSUMING that mm_heap->use_custom_heap is the first element of the struct ... */ - *(int *) zend_mm_get_heap() = 0; - } - if (PHPDBG_G(buffer)) { free(PHPDBG_G(buffer)); PHPDBG_G(buffer) = NULL; From afb1c574700e035ba20a3b8ddd203471a31ba6c6 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:52:21 +0200 Subject: [PATCH 37/42] Fix GH-14551: PGO build fails with xxhash Turns out that the instrumentation added for gcov can change inlining decisions of the compiler, which results in a mismatch between the profile data CFG and the actual generated CFG between compiles. There are two functions that suffer from this issue: 1. _PHP_XXH3_Init: Removing the inline hint fixes this one. In fact, always inlining this makes no sense as there's no real opportunity for specialising. It just bloats the binary and increases I$ pressure. So besides fixing this issue it's beneficial on its own to drop the attribute. 2. PHP_XXH3_128_Final: Sometimes XXH128_canonicalFromHash gets inlined and sometimes not. Make sure it gets always inlined. Closes GH-18814. --- NEWS | 3 +++ ext/hash/hash_xxhash.c | 2 +- ext/hash/xxhash/xxhash.h | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index a665633283d0c..d32c60625ed23 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,9 @@ PHP NEWS - FPM: . Fixed GH-18662 (fpm_get_status segfault). (txuna) +- Hash: + . Fixed bug GH-14551 (PGO build fails with xxhash). (nielsdos) + - Intl: . Fix memory leak in intl_datetime_decompose() on failure. (nielsdos) . Fix memory leak in locale lookup on failure. (nielsdos) diff --git a/ext/hash/hash_xxhash.c b/ext/hash/hash_xxhash.c index 24da754d8835a..070bd06bff070 100644 --- a/ext/hash/hash_xxhash.c +++ b/ext/hash/hash_xxhash.c @@ -154,7 +154,7 @@ const php_hash_ops php_hash_xxh3_64_ops = { typedef XXH_errorcode (*xxh3_reset_with_secret_func_t)(XXH3_state_t*, const void*, size_t); typedef XXH_errorcode (*xxh3_reset_with_seed_func_t)(XXH3_state_t*, XXH64_hash_t); -zend_always_inline static void _PHP_XXH3_Init(PHP_XXH3_64_CTX *ctx, HashTable *args, +static void _PHP_XXH3_Init(PHP_XXH3_64_CTX *ctx, HashTable *args, xxh3_reset_with_seed_func_t func_init_seed, xxh3_reset_with_secret_func_t func_init_secret, const char* algo_name) { memset(&ctx->s, 0, sizeof ctx->s); diff --git a/ext/hash/xxhash/xxhash.h b/ext/hash/xxhash/xxhash.h index 8e816c0584ebd..5874c9a1f97b5 100644 --- a/ext/hash/xxhash/xxhash.h +++ b/ext/hash/xxhash/xxhash.h @@ -931,7 +931,7 @@ XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2); /******* Canonical representation *******/ typedef struct { unsigned char digest[sizeof(XXH128_hash_t)]; } XXH128_canonical_t; -XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash); +static zend_always_inline void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash); XXH_PUBLIC_API XXH128_hash_t XXH128_hashFromCanonical(const XXH128_canonical_t* src); @@ -5503,7 +5503,7 @@ XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2) /*====== Canonical representation ======*/ /*! @ingroup xxh3_family */ -XXH_PUBLIC_API void +static zend_always_inline void XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash) { XXH_STATIC_ASSERT(sizeof(XXH128_canonical_t) == sizeof(XXH128_hash_t)); From c074645ac3f5dcf6b831e968053a01923ea5bf74 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 10 Apr 2025 15:15:36 +0200 Subject: [PATCH 38/42] Fix GHSA-3cr5-j632-f35r: Null byte in hostnames This fixes stream_socket_client() and fsockopen(). Specifically it adds a check to parse_ip_address_ex and it also makes sure that the \0 is not ignored in fsockopen() hostname formatting. --- ext/standard/fsock.c | 27 +++++++++++++++++-- .../tests/network/ghsa-3cr5-j632-f35r.phpt | 21 +++++++++++++++ .../tests/streams/ghsa-3cr5-j632-f35r.phpt | 26 ++++++++++++++++++ main/streams/xp_socket.c | 9 ++++--- 4 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt create mode 100644 ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c index cb7a471e935a6..2b9e00a57554c 100644 --- a/ext/standard/fsock.c +++ b/ext/standard/fsock.c @@ -23,6 +23,28 @@ #include "php_network.h" #include "file.h" +static size_t php_fsockopen_format_host_port(char **message, const char *prefix, size_t prefix_len, + const char *host, size_t host_len, zend_long port) +{ + char portbuf[32]; + int portlen = snprintf(portbuf, sizeof(portbuf), ":" ZEND_LONG_FMT, port); + size_t total_len = prefix_len + host_len + portlen; + + char *result = emalloc(total_len + 1); + + if (prefix_len > 0) { + memcpy(result, prefix, prefix_len); + } + memcpy(result + prefix_len, host, host_len); + memcpy(result + prefix_len + host_len, portbuf, portlen); + + result[total_len] = '\0'; + + *message = result; + + return total_len; +} + /* {{{ php_fsockopen() */ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) @@ -62,11 +84,12 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) } if (persistent) { - spprintf(&hashkey, 0, "pfsockopen__%s:" ZEND_LONG_FMT, host, port); + php_fsockopen_format_host_port(&hashkey, "pfsockopen__", strlen("pfsockopen__"), host, + host_len, port); } if (port > 0) { - hostname_len = spprintf(&hostname, 0, "%s:" ZEND_LONG_FMT, host, port); + hostname_len = php_fsockopen_format_host_port(&hostname, "", 0, host, host_len, port); } else { hostname_len = host_len; hostname = host; diff --git a/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt new file mode 100644 index 0000000000000..7556c3be94ccd --- /dev/null +++ b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt @@ -0,0 +1,21 @@ +--TEST-- +GHSA-3cr5-j632-f35r: Null byte termination in fsockopen() +--FILE-- + +--EXPECTF-- + +Warning: fsockopen(): Unable to connect to localhost:%d (The hostname must not contain null bytes) in %s +bool(false) diff --git a/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt new file mode 100644 index 0000000000000..52f9263c99aaa --- /dev/null +++ b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt @@ -0,0 +1,26 @@ +--TEST-- +GHSA-3cr5-j632-f35r: Null byte termination in stream_socket_client() +--FILE-- + +--EXPECTF-- + +Warning: stream_socket_client(): Unable to connect to tcp://localhost\0.example.com:%d (The hostname must not contain null bytes) in %s +bool(false) diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index 606d1499456a2..b1d89bc44cb2b 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -616,12 +616,15 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po char *colon; char *host = NULL; -#ifdef HAVE_IPV6 - char *p; + if (memchr(str, '\0', str_len)) { + *err = ZSTR_INIT_LITERAL("The hostname must not contain null bytes", 0); + return NULL; + } +#ifdef HAVE_IPV6 if (*(str) == '[' && str_len > 1) { /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ - p = memchr(str + 1, ']', str_len - 2); + char *p = memchr(str + 1, ']', str_len - 2); if (!p || *(p + 1) != ':') { if (get_err) { *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); From 65eade719fd378148be1ffb70322498a8e729bee Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Tue, 4 Mar 2025 17:23:01 +0100 Subject: [PATCH 39/42] Fix GHSA-hrwm-9436-5mv3: pgsql escaping no error checks This adds error checks for escape function is pgsql and pdo_pgsql extensions. It prevents possibility of storing not properly escaped data which could potentially lead to some security issues. --- ext/pdo_pgsql/pgsql_driver.c | 10 +- ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt | 24 ++++ ext/pgsql/pgsql.c | 125 ++++++++++++++++--- ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt | 64 ++++++++++ 4 files changed, 202 insertions(+), 21 deletions(-) create mode 100644 ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt create mode 100644 ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 46b3f25f4086b..1cccfd2ab07ae 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -353,11 +353,15 @@ static zend_string* pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo zend_string *quoted_str; pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; size_t tmp_len; + int err; switch (paramtype) { case PDO_PARAM_LOB: /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */ escaped = PQescapeByteaConn(H->server, (unsigned char *)ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &tmp_len); + if (escaped == NULL) { + return NULL; + } quotedlen = tmp_len + 1; quoted = emalloc(quotedlen + 1); memcpy(quoted+1, escaped, quotedlen-2); @@ -369,7 +373,11 @@ static zend_string* pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo default: quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); quoted[0] = '\''; - quotedlen = PQescapeStringConn(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), NULL); + quotedlen = PQescapeStringConn(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &err); + if (err) { + efree(quoted); + return NULL; + } quoted[quotedlen + 1] = '\''; quoted[quotedlen + 2] = '\0'; quotedlen += 2; diff --git a/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt b/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt new file mode 100644 index 0000000000000..8566a26753b40 --- /dev/null +++ b/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt @@ -0,0 +1,24 @@ +--TEST-- +#GHSA-hrwm-9436-5mv3: pdo_pgsql extension does not check for errors during escaping +--EXTENSIONS-- +pdo +pdo_pgsql +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +$invalid = "ABC\xff\x30';"; +var_dump($db->quote($invalid)); + +?> +--EXPECT-- +bool(false) diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 63acd26ea01f5..11ce814cbec0f 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -3269,8 +3269,14 @@ PHP_FUNCTION(pg_escape_string) to = zend_string_safe_alloc(ZSTR_LEN(from), 2, 0, 0); if (link) { + int err; pgsql = link->conn; - ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), NULL); + ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), &err); + if (err) { + zend_argument_value_error(ZEND_NUM_ARGS(), "Escaping string failed"); + zend_string_efree(to); + RETURN_THROWS(); + } } else { ZSTR_LEN(to) = PQescapeString(ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from)); @@ -3313,6 +3319,10 @@ PHP_FUNCTION(pg_escape_bytea) } else { to = (char *)PQescapeBytea((unsigned char *)ZSTR_VAL(from), ZSTR_LEN(from), &to_len); } + if (to == NULL) { + zend_argument_value_error(ZEND_NUM_ARGS(), "Escape failure"); + RETURN_THROWS(); + } RETVAL_STRINGL(to, to_len-1); /* to_len includes additional '\0' */ PQfreemem(to); @@ -4245,7 +4255,7 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string char *escaped; smart_str querystr = {0}; size_t new_len; - int i, num_rows; + int i, num_rows, err; zval elem; ZEND_ASSERT(ZSTR_LEN(table_name) != 0); @@ -4283,7 +4293,14 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string "WHERE a.attnum > 0 AND c.relname = '"); } escaped = (char *)safe_emalloc(strlen(tmp_name2), 2, 1); - new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), NULL); + new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), &err); + if (err) { + php_error_docref(NULL, E_WARNING, "Escaping table name '%s' failed", ZSTR_VAL(table_name)); + efree(src); + efree(escaped); + smart_str_free(&querystr); + return FAILURE; + } if (new_len) { smart_str_appendl(&querystr, escaped, new_len); } @@ -4291,7 +4308,14 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string smart_str_appends(&querystr, "' AND n.nspname = '"); escaped = (char *)safe_emalloc(strlen(tmp_name), 2, 1); - new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), NULL); + new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), &err); + if (err) { + php_error_docref(NULL, E_WARNING, "Escaping table namespace '%s' failed", ZSTR_VAL(table_name)); + efree(src); + efree(escaped); + smart_str_free(&querystr); + return FAILURE; + } if (new_len) { smart_str_appendl(&querystr, escaped, new_len); } @@ -4552,7 +4576,7 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * { zend_string *field = NULL; zval meta, *def, *type, *not_null, *has_default, *is_enum, *val, new_val; - int err = 0, skip_field; + int err = 0, escape_err = 0, skip_field; php_pgsql_data_type data_type; ZEND_ASSERT(pg_link != NULL); @@ -4804,8 +4828,13 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * /* PostgreSQL ignores \0 */ str = zend_string_alloc(Z_STRLEN_P(val) * 2, 0); /* better to use PGSQLescapeLiteral since PGescapeStringConn does not handle special \ */ - ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); - ZVAL_STR(&new_val, php_pgsql_add_quotes(str)); + ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), + Z_STRVAL_P(val), Z_STRLEN_P(val), &escape_err); + if (escape_err) { + err = 1; + } else { + ZVAL_STR(&new_val, php_pgsql_add_quotes(str)); + } zend_string_release_ex(str, false); } break; @@ -4828,7 +4857,14 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * } PGSQL_CONV_CHECK_IGNORE(); if (err) { - zend_type_error("%s(): Field \"%s\" must be of type string|null, %s given", get_active_function_name(), ZSTR_VAL(field), Z_STRVAL_P(type)); + if (escape_err) { + php_error_docref(NULL, E_NOTICE, + "String value escaping failed for PostgreSQL '%s' (%s)", + Z_STRVAL_P(type), ZSTR_VAL(field)); + } else { + zend_type_error("%s(): Field \"%s\" must be of type string|null, %s given", + get_active_function_name(), ZSTR_VAL(field), Z_STRVAL_P(type)); + } } break; @@ -5103,6 +5139,11 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * zend_string *tmp_zstr; tmp = PQescapeByteaConn(pg_link, (unsigned char *)Z_STRVAL_P(val), Z_STRLEN_P(val), &to_len); + if (tmp == NULL) { + php_error_docref(NULL, E_NOTICE, "Escaping value failed for %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); + err = 1; + break; + } tmp_zstr = zend_string_init((char *)tmp, to_len - 1, false); /* PQescapeBytea's to_len includes additional '\0' */ PQfreemem(tmp); @@ -5181,6 +5222,12 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string * zend_hash_update(Z_ARRVAL_P(result), field, &new_val); } else { char *escaped = PQescapeIdentifier(pg_link, ZSTR_VAL(field), ZSTR_LEN(field)); + if (escaped == NULL) { + /* This cannot fail because of invalid string but only due to failed memory allocation */ + php_error_docref(NULL, E_NOTICE, "Escaping field '%s' failed", ZSTR_VAL(field)); + err = 1; + break; + } add_assoc_zval(result, escaped, &new_val); PQfreemem(escaped); } @@ -5259,7 +5306,7 @@ static bool do_exec(smart_str *querystr, ExecStatusType expect, PGconn *pg_link, } /* }}} */ -static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const zend_string *table) /* {{{ */ +static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link, const zend_string *table) /* {{{ */ { /* schema.table should be "schema"."table" */ const char *dot = memchr(ZSTR_VAL(table), '.', ZSTR_LEN(table)); @@ -5269,6 +5316,10 @@ static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const z smart_str_appendl(querystr, ZSTR_VAL(table), len); } else { char *escaped = PQescapeIdentifier(pg_link, ZSTR_VAL(table), len); + if (escaped == NULL) { + php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", ZSTR_VAL(table)); + return FAILURE; + } smart_str_appends(querystr, escaped); PQfreemem(escaped); } @@ -5281,11 +5332,17 @@ static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const z smart_str_appendl(querystr, after_dot, len); } else { char *escaped = PQescapeIdentifier(pg_link, after_dot, len); + if (escaped == NULL) { + php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", ZSTR_VAL(table)); + return FAILURE; + } smart_str_appendc(querystr, '.'); smart_str_appends(querystr, escaped); PQfreemem(escaped); } } + + return SUCCESS; } /* }}} */ @@ -5306,7 +5363,9 @@ PHP_PGSQL_API zend_result php_pgsql_insert(PGconn *pg_link, const zend_string *t ZVAL_UNDEF(&converted); if (zend_hash_num_elements(Z_ARRVAL_P(var_array)) == 0) { smart_str_appends(&querystr, "INSERT INTO "); - build_tablename(&querystr, pg_link, table); + if (build_tablename(&querystr, pg_link, table) == FAILURE) { + goto cleanup; + } smart_str_appends(&querystr, " DEFAULT VALUES"); goto no_values; @@ -5322,7 +5381,9 @@ PHP_PGSQL_API zend_result php_pgsql_insert(PGconn *pg_link, const zend_string *t } smart_str_appends(&querystr, "INSERT INTO "); - build_tablename(&querystr, pg_link, table); + if (build_tablename(&querystr, pg_link, table) == FAILURE) { + goto cleanup; + } smart_str_appends(&querystr, " ("); ZEND_HASH_FOREACH_STR_KEY(Z_ARRVAL_P(var_array), fld) { @@ -5332,6 +5393,10 @@ PHP_PGSQL_API zend_result php_pgsql_insert(PGconn *pg_link, const zend_string *t } if (opt & PGSQL_DML_ESCAPE) { tmp = PQescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1); + if (tmp == NULL) { + php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld)); + goto cleanup; + } smart_str_appends(&querystr, tmp); PQfreemem(tmp); } else { @@ -5343,15 +5408,19 @@ PHP_PGSQL_API zend_result php_pgsql_insert(PGconn *pg_link, const zend_string *t smart_str_appends(&querystr, ") VALUES ("); /* make values string */ - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(var_array), val) { + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(var_array), fld, val) { /* we can avoid the key_type check here, because we tested it in the other loop */ switch (Z_TYPE_P(val)) { case IS_STRING: if (opt & PGSQL_DML_ESCAPE) { - size_t new_len; - char *tmp; - tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1); - new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); + int error; + char *tmp = safe_emalloc(Z_STRLEN_P(val), 2, 1); + size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error); + if (error) { + php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld)); + efree(tmp); + goto cleanup; + } smart_str_appendc(&querystr, '\''); smart_str_appendl(&querystr, tmp, new_len); smart_str_appendc(&querystr, '\''); @@ -5507,6 +5576,10 @@ static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr, } if (opt & PGSQL_DML_ESCAPE) { char *tmp = PQescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1); + if (tmp == NULL) { + php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld)); + return -1; + } smart_str_appends(querystr, tmp); PQfreemem(tmp); } else { @@ -5522,8 +5595,14 @@ static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr, switch (Z_TYPE_P(val)) { case IS_STRING: if (opt & PGSQL_DML_ESCAPE) { + int error; char *tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1); - size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); + size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error); + if (error) { + php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld)); + efree(tmp); + return -1; + } smart_str_appendc(querystr, '\''); smart_str_appendl(querystr, tmp, new_len); smart_str_appendc(querystr, '\''); @@ -5591,7 +5670,9 @@ PHP_PGSQL_API zend_result php_pgsql_update(PGconn *pg_link, const zend_string *t } smart_str_appends(&querystr, "UPDATE "); - build_tablename(&querystr, pg_link, table); + if (build_tablename(&querystr, pg_link, table) == FAILURE) { + goto cleanup; + } smart_str_appends(&querystr, " SET "); if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(var_array), 0, ",", 1, opt)) @@ -5694,7 +5775,9 @@ PHP_PGSQL_API zend_result php_pgsql_delete(PGconn *pg_link, const zend_string *t } smart_str_appends(&querystr, "DELETE FROM "); - build_tablename(&querystr, pg_link, table); + if (build_tablename(&querystr, pg_link, table) == FAILURE) { + goto cleanup; + } smart_str_appends(&querystr, " WHERE "); if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt)) @@ -5834,7 +5917,9 @@ PHP_PGSQL_API zend_result php_pgsql_select(PGconn *pg_link, const zend_string *t } smart_str_appends(&querystr, "SELECT * FROM "); - build_tablename(&querystr, pg_link, table); + if (build_tablename(&querystr, pg_link, table) == FAILURE) { + goto cleanup; + } smart_str_appends(&querystr, " WHERE "); if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt)) diff --git a/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt b/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt new file mode 100644 index 0000000000000..c1c5e05dce623 --- /dev/null +++ b/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt @@ -0,0 +1,64 @@ +--TEST-- +#GHSA-hrwm-9436-5mv3: pgsql extension does not check for errors during escaping +--EXTENSIONS-- +pgsql +--SKIPIF-- + +--FILE-- + 'test'])); // table name str escape in php_pgsql_meta_data +var_dump(pg_insert($db, "$invalid.tbl", ['bar' => 'test'])); // schema name str escape in php_pgsql_meta_data +var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', ['bar' => $invalid])); // converted value str escape in php_pgsql_convert +var_dump(pg_insert($db, $invalid, [])); // ident escape in build_tablename +var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', [$invalid => 'foo'], $flags)); // ident escape for field php_pgsql_insert +var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', ['bar' => $invalid], $flags)); // str escape for field value in php_pgsql_insert +var_dump(pg_update($db, 'ghsa_hrmw_9436_5mv3', ['bar' => 'val'], [$invalid => 'test'], $flags)); // ident escape in build_assignment_string +var_dump(pg_update($db, 'ghsa_hrmw_9436_5mv3', ['bar' => 'val'], ['bar' => $invalid], $flags)); // invalid str escape in build_assignment_string +var_dump(pg_escape_literal($db, $invalid)); // pg_escape_literal escape +var_dump(pg_escape_identifier($db, $invalid)); // pg_escape_identifier escape + +?> +--EXPECTF-- + +Warning: pg_insert(): Escaping table name 'ABC%s';' failed in %s on line %d +bool(false) + +Warning: pg_insert(): Escaping table namespace 'ABC%s';.tbl' failed in %s on line %d +bool(false) + +Notice: pg_insert(): String value escaping failed for PostgreSQL 'text' (bar) in %s on line %d +bool(false) + +Notice: pg_insert(): Failed to escape table name 'ABC%s';' in %s on line %d +bool(false) + +Notice: pg_insert(): Failed to escape field 'ABC%s';' in %s on line %d +bool(false) + +Notice: pg_insert(): Failed to escape field 'bar' value in %s on line %d +bool(false) + +Notice: pg_update(): Failed to escape field 'ABC%s';' in %s on line %d +bool(false) + +Notice: pg_update(): Failed to escape field 'bar' value in %s on line %d +bool(false) + +Warning: pg_escape_literal(): Failed to escape in %s on line %d +bool(false) + +Warning: pg_escape_identifier(): Failed to escape in %s on line %d +bool(false) From 026ab919d041312368bbbbeb4bfc50670cdd013b Mon Sep 17 00:00:00 2001 From: Ahmed Lekssays Date: Tue, 3 Jun 2025 09:00:55 +0000 Subject: [PATCH 40/42] Fix GHSA-453j-q27h-5p8x Libxml versions prior to 2.13 cannot correctly handle a call to xmlNodeSetName() with a name longer than 2G. It will leave the node object in an invalid state with a NULL name. This later causes a NULL pointer dereference when using the name during message serialization. To solve this, implement a workaround that resets the name to the sentinel name if this situation arises. Versions of libxml of 2.13 and higher are not affected. This can be exploited if a SoapVar is created with a fully qualified name that is longer than 2G. This would be possible if some application code uses a namespace prefix from an untrusted source like from a remote SOAP service. Co-authored-by: Niels Dossche <7771979+nielsdos@users.noreply.github.com> --- ext/soap/soap.c | 6 ++-- ext/soap/tests/soap_qname_crash.phpt | 48 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 ext/soap/tests/soap_qname_crash.phpt diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 3f8a50ba6ed2c..fc51e32658f3a 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -3980,8 +3980,10 @@ static xmlNodePtr serialize_zval(zval *val, sdlParamPtr param, char *paramName, } xmlParam = master_to_xml(enc, val, style, parent); zval_ptr_dtor(&defval); - if (!strcmp((char*)xmlParam->name, "BOGUS")) { - xmlNodeSetName(xmlParam, BAD_CAST(paramName)); + if (xmlParam != NULL) { + if (xmlParam->name == NULL || strcmp((char*)xmlParam->name, "BOGUS") == 0) { + xmlNodeSetName(xmlParam, BAD_CAST(paramName)); + } } return xmlParam; } diff --git a/ext/soap/tests/soap_qname_crash.phpt b/ext/soap/tests/soap_qname_crash.phpt new file mode 100644 index 0000000000000..bcf01d574fab4 --- /dev/null +++ b/ext/soap/tests/soap_qname_crash.phpt @@ -0,0 +1,48 @@ +--TEST-- +Test SoapClient with excessively large QName prefix in SoapVar +--EXTENSIONS-- +soap +--SKIPIF-- + +--INI-- +memory_limit=6144M +--FILE-- + 'http://127.0.0.1/', + 'uri' => 'urn:dummy', + 'trace' => 1, + 'exceptions' => true, +]; +$client = new TestSoapClient(null, $options); +$client->__soapCall("DummyFunction", [$var]); +?> +--EXPECT-- +Attempting to create SoapVar with very large QName +Attempting encoding + +value From 5e5b164bb4a25532685b2337ac0ca08795aa14d3 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 26 Jun 2025 11:29:28 +0200 Subject: [PATCH 41/42] Update NEWS with entries for security fixes --- NEWS | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index d32c60625ed23..6a6b51fe95758 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.3.23 +03 Jul 2025, PHP 8.3.23 - Core: . Fixed GH-18695 (zend_ast_export() - float number is not preserved). @@ -38,6 +38,10 @@ PHP NEWS . Fixed bug #74796 (Requests through http proxy set peer name). (Jakub Zelenka) +- PGSQL: + . Fixed GHSA-hrwm-9436-5mv3 (pgsql extension does not check for errors during + escaping). (CVE-2025-1735) (Jakub Zelenka) + - Phar: . Add missing filter cleanups on phar failure. (nielsdos) . Fixed bug GH-18642 (Signed integer overflow in ext/phar fseek). (nielsdos) @@ -60,8 +64,14 @@ PHP NEWS . Fix memory leak when calloc() fails in php_readline_completion_cb(). (nielsdos) -- Soap: +- SOAP: . Fix memory leaks in php_http.c when call_user_function() fails. (nielsdos) + . Fixed GHSA-453j-q27h-5p8x (NULL Pointer Dereference in PHP SOAP Extension + via Large XML Namespace Prefix). (CVE-2025-6491) (Lekssays, nielsdos) + +- Standard: + . Fixed GHSA-3cr5-j632-f35r (Null byte termination in hostnames). + (CVE-2025-1220) (Jakub Zelenka) - Tidy: . Fix memory leak in tidy output handler on error. (nielsdos) From ad881e753961115ceaf340bc47e7151fd9be1b45 Mon Sep 17 00:00:00 2001 From: Eric Mann Date: Tue, 1 Jul 2025 09:52:12 -0700 Subject: [PATCH 42/42] Update versions for PHP 8.3.23 --- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Zend/zend.h b/Zend/zend.h index 704df32e9c145..ad06ee8d4ad41 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.3.23-dev" +#define ZEND_VERSION "4.3.23" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 965c0bdd853ee..cd0b8820006ce 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.23-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.3.23],[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 28e4162421ded..b7139a67d4776 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 23 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.3.23-dev" +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.3.23" #define PHP_VERSION_ID 80323