From 19fc6ed5fbc3f97807f659dd9ce7b2ae7859fdc3 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:45:59 +0200 Subject: [PATCH 1/9] soap: Switch to new XML parser option setting API (#20020) This API does not require the "global" security workaround. We also pass some additional options for hardening. --- ext/soap/php_xml.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c index b6b0c09b9d48b..a2536b98f39bb 100644 --- a/ext/soap/php_xml.c +++ b/ext/soap/php_xml.c @@ -79,12 +79,15 @@ static xmlDocPtr soap_xmlParse_ex(xmlParserCtxtPtr ctxt) { xmlDocPtr ret; if (ctxt) { +#if LIBXML_VERSION >= 21300 + xmlCtxtSetOptions(ctxt, XML_PARSE_HUGE | XML_PARSE_NO_XXE | XML_PARSE_NONET | XML_PARSE_NOBLANKS); +#else php_libxml_sanitize_parse_ctxt_options(ctxt); - /* TODO: In libxml2 2.14.0 change this to the new options API so we don't rely on deprecated APIs. */ ZEND_DIAGNOSTIC_IGNORED_START("-Wdeprecated-declarations") ctxt->keepBlanks = 0; ctxt->options |= XML_PARSE_HUGE; ZEND_DIAGNOSTIC_IGNORED_END +#endif ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace; ctxt->sax->comment = soap_Comment; ctxt->sax->warning = NULL; From 7c859268c0306ccf237d18ef8cae97e60b1ac278 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 30 Sep 2025 10:48:42 +0200 Subject: [PATCH 2/9] Fix memory leak and invalid continuation after tar header writing fails Closes GH-20003. --- NEWS | 4 ++ ext/phar/tar.c | 17 ++++---- .../tests/tar_flush_too_long_filename.phpt | 41 +++++++++++++++++++ 3 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 ext/phar/tests/tar_flush_too_long_filename.phpt diff --git a/NEWS b/NEWS index c7765950f8cc8..9f32973e94a23 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,10 @@ PHP NEWS . Fixed bug #67563 (mysqli compiled with mysqlnd does not take ipv6 adress as parameter). (nielsdos) +- Phar: + . Fix memory leak and invalid continuation after tar header writing fails. + (nielsdos) + - SimpleXML: . Fixed bug GH-19988 (zend_string_init with NULL pointer in simplexml (UB)). (nielsdos) diff --git a/ext/phar/tar.c b/ext/phar/tar.c index 687ca34c173c9..e675b86943217 100644 --- a/ext/phar/tar.c +++ b/ext/phar/tar.c @@ -1211,7 +1211,16 @@ int phar_tar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int } zend_hash_apply_with_argument(&phar->manifest, phar_tar_writeheaders, (void *) &pass); - /* TODO: memory leak and incorrect continuation if phar_tar_writeheaders fails? */ + + if (error && *error) { + if (closeoldfile) { + php_stream_close(oldfile); + } + + /* on error in the hash iterator above, error is set */ + php_stream_close(newfile); + return EOF; + } /* add signature for executable tars or tars explicitly set with setSignatureAlgorithm */ if (!phar->is_data || phar->sig_flags) { @@ -1294,12 +1303,6 @@ int phar_tar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int php_stream_close(oldfile); } - /* on error in the hash iterator above, error is set */ - if (error && *error) { - php_stream_close(newfile); - return EOF; - } - if (phar->fp && pass.free_fp) { php_stream_close(phar->fp); } diff --git a/ext/phar/tests/tar_flush_too_long_filename.phpt b/ext/phar/tests/tar_flush_too_long_filename.phpt new file mode 100644 index 0000000000000..be18f5e481688 --- /dev/null +++ b/ext/phar/tests/tar_flush_too_long_filename.phpt @@ -0,0 +1,41 @@ +--TEST-- +Tar flush with too long file name +--EXTENSIONS-- +phar +--SKIPIF-- + +--INI-- +phar.require_hash=0 +--FILE-- +addEmptyDir('blah1/'); +$phar->setSignatureAlgorithm(Phar::OPENSSL, "randomcrap"); +try { + $phar->addEmptyDir('blah2/' . str_repeat('X', 1000)); +} catch (PharException $e) { + echo $e->getMessage(); +} + +?> +--CLEAN-- + +--EXPECTF-- +tar-based phar "%s" cannot be created, filename "%s" is too long for tar file format From 9bd9e3a1f93a5b7bd624857e94eb6961797cfd90 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:54:41 +0200 Subject: [PATCH 3/9] Fix build --- ext/phar/tar.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/phar/tar.c b/ext/phar/tar.c index c6cc50ce8bf62..63a9cdbf88fd1 100644 --- a/ext/phar/tar.c +++ b/ext/phar/tar.c @@ -1172,13 +1172,13 @@ void phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_def zend_hash_apply_with_argument(&phar->manifest, phar_tar_writeheaders, (void *) &pass); if (error && *error) { - if (closeoldfile) { + if (must_close_old_file) { php_stream_close(oldfile); } /* on error in the hash iterator above, error is set */ php_stream_close(newfile); - return EOF; + return; } /* add signature for executable tars or tars explicitly set with setSignatureAlgorithm */ From d79baba77ddf1a43b54455c5ba0549b284c709a4 Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Tue, 30 Sep 2025 13:18:34 -0500 Subject: [PATCH 4/9] Fix typos and grammar in UPGRADING --- UPGRADING | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/UPGRADING b/UPGRADING index 80c286cae5a19..0a610e3092c91 100644 --- a/UPGRADING +++ b/UPGRADING @@ -579,7 +579,7 @@ PHP 8.5 UPGRADE NOTES . Passing a string which is not a single byte to ord() is now deprecated, this is indicative of a bug. RFC: https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_passing_string_which_are_not_one_byte_long_to_ord - . Relying locally predefined variable $http_response_header is deprecated. + . The locally predefined variable $http_response_header is deprecated. Instead one should call the http_get_last_response_headers() function. RFC: https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_http_response_header_predefined_variable @@ -610,16 +610,16 @@ PHP 8.5 UPGRADE NOTES RFC: https://wiki.php.net/rfc/grapheme_add_locale_for_case_insensitive - LDAP: - . ldap_get_option() now accepts a NULL connection, as ldap_set_option(), + . ldap_get_option() now accepts a NULL connection, like ldap_set_option(), to allow retrieval of global options. - libxml: . libxml_set_external_entity_loader() now has a formal return type of true. - OpenSSL: - . openssl_public_encrypt() and openssl_private_decrypt() have new parameter - $digest_algo that allows specifying hash digest algorithm for OEAP padding. - . openssl_sign() and openssl_verify() have new parameter $padding to allow + . openssl_public_encrypt() and openssl_private_decrypt() have a new parameter + $digest_algo that allows specifying the hash digest algorithm for OAEP padding. + . openssl_sign() and openssl_verify() have a new parameter $padding to allow using more secure RSA PSS padding. . openssl_cms_encrypt() $cipher_algo parameter can be a string with the cipher name. That allows to use more algorithms including AES GCM cipher @@ -631,9 +631,9 @@ PHP 8.5 UPGRADE NOTES gather various platform specific metrics about the child process. - PDO_PGSQL: - . PDO::pgsqlCopyFromArray also supports inputs as Iterable. - . Pdo\Pgsql::setAttribute and Pdo\Pgsql::prepare supports - PDO::ATTR_PREFETCH sets to 0 which set to lazy fetch mode. + . PDO::pgsqlCopyFromArray now supports Iterable inputs. + . Pdo\Pgsql::setAttribute and Pdo\Pgsql::prepare support setting + PDO::ATTR_PREFETCH to 0 which enters lazy fetch mode. In this mode, statements cannot be run in parallel. - PDO_SQLITE: @@ -644,7 +644,7 @@ PHP 8.5 UPGRADE NOTES in line with Pdo_Sqlite::createCollation behavior. - PGSQL: - . pg_copy_from also supports inputs as Iterable. + . pg_copy_from now supports Iterable inputs. . pg_connect checks if the connection_string argument contains any null byte. . pg_close_stmt checks if the statement_name argument contains @@ -658,12 +658,12 @@ PHP 8.5 UPGRADE NOTES . posix_fpathconf checks invalid file descriptors and sets last_error to EBADF and raises an E_WARNING message. . posix_kill throws a ValueError when the process_id argument is lower - or greater than what supports the platform (signed integer or long + or greater than what the platform supports (signed integer or long range), posix_setpgid throws a ValueError when the process_id or the process_group_id is lower than zero or greater than - what supports the platform. - . posix_setrlimit throws a ValueError when the hard_limit of soft_limit - argument are lower than -1 or if soft_limit is greater than hard_limit. + what the platform supports. + . posix_setrlimit throws a ValueError when the hard_limit or soft_limit + arguments are lower than -1 or if soft_limit is greater than hard_limit. - Reflection: . The output of ReflectionClass::__toString() for enums has changed to @@ -676,14 +676,14 @@ PHP 8.5 UPGRADE NOTES properties. . ReflectionAttribute::newInstance() can now throw errors for internal attributes if the attribute was applied on an invalid target and the - error was delayed from compile-time to runtime via the + error was delayed from compile time to runtime via the #[\DelayedTargetValidation] attribute. RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute - Session: - . session_start is stricter in regard to the option argument. - It throws a ValueError if the whole is not a hashmap or - a TypeError if read_and_close value is not a valid type + . session_start is stricter in regard to the options argument. + It throws a ValueError if the array is not a hashmap, or + a TypeError if the read_and_close value is not a valid type compatible with int. - SNMP: @@ -710,14 +710,13 @@ PHP 8.5 UPGRADE NOTES when the created socket is not of AF_INET/AF_INET6 family. - Tidy: - . tidy::__construct/parseFile/parseString now throws a ValueError - if the configuration contains an invalid or set a read-only - internal entry, a TypeError contains, at least, one element - when the key is not a string. + . tidy::__construct/parseFile/parseString now throws a ValueError if the + configuration contains an invalid value or attempts to set a read-only + internal entry, and a TypeError if a configuration key is not a string. - Zlib: . The "use_include_path" argument for the - gzfile, gzopen and readgzfile functions had been changed + gzfile, gzopen and readgzfile functions has been changed from int to boolean. . gzfile, gzopen and readgzfile functions now respect the default stream context. @@ -739,7 +738,7 @@ PHP 8.5 UPGRADE NOTES . Added Closure::getCurrent() to receive currently executing closure. - Curl: - . curl_multi_get_handles() allows retrieving all CurlHandles current + . curl_multi_get_handles() allows retrieving all CurlHandles currently attached to a CurlMultiHandle. This includes both handles added using curl_multi_add_handle() and handles accepted by CURLMOPT_PUSHFUNCTION. . curl_share_init_persistent() allows creating a share handle that is @@ -967,7 +966,7 @@ PHP 8.5 UPGRADE NOTES PHP_RELEASE_VERSION are now always numbers. Previously, they have been strings for buildconf builds. -* phpize builds now reflect the source tree in the build dir (like that already +* phpize builds now reflect the source tree in the build dir (as it already worked for in-tree builds); some extension builds (especially when using Makefile.frag.w32) may need adjustments. @@ -1063,4 +1062,4 @@ PHP 8.5 UPGRADE NOTES . Improved property access performance. - XMLWriter: - . Improved performance and reduce memory consumption. + . Improved performance and reduced memory consumption. From b7fdfb71478f55d6ebf6c812c467ed260ad26d80 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sat, 26 Jul 2025 17:22:28 +0200 Subject: [PATCH 5/9] Fix GH-19248: Use strerror_r instead of strerror in main Or on Windows it is going to use either FormatMessageW or strerror_s for compatibility with previous error messages. It also needs to accomodate for GNU and BSD versions of strerror_r returning different type. Closes GH-19251 --- NEWS | 4 ++ main/network.c | 76 +++++++++++++++++++++++++++++++----- main/php_network.h | 5 +++ main/rfc1867.c | 3 +- main/streams/plain_wrapper.c | 47 +++++++++++++++------- main/streams/streams.c | 3 +- main/streams/xp_socket.c | 3 +- 7 files changed, 114 insertions(+), 27 deletions(-) diff --git a/NEWS b/NEWS index 9f32973e94a23..e60af85379c82 100644 --- a/NEWS +++ b/NEWS @@ -52,6 +52,10 @@ PHP NEWS . Fixed bug GH-19801 (leaks in var_dump() and debug_zval_dump()). (alexandre-daubois) +- Streams: + . Fixed bug GH-19248 (Use strerror_r instead of strerror in main). + (Jakub Zelenka) + - XMLReader: . Fixed bug GH-20009 (XMLReader leak on RelaxNG schema failure). (nielsdos) diff --git a/main/network.c b/main/network.c index 8de81a6271a2f..7102dad20798f 100644 --- a/main/network.c +++ b/main/network.c @@ -1052,6 +1052,28 @@ PHPAPI int php_sockaddr_size(php_sockaddr_storage *addr) } /* }}} */ +#ifdef PHP_WIN32 +char *php_socket_strerror_s(long err, char *buf, size_t bufsize) +{ + if (buf == NULL) { + char ebuf[1024]; + errno_t res = strerror_s(ebuf, sizeof(ebuf), err); + if (res == 0) { + buf = estrdup(ebuf); + } else { + buf = estrdup("Unknown error"); + } + } else { + errno_t res = strerror_s(buf, bufsize, err); + if (res != 0) { + strncpy(buf, "Unknown error", bufsize); + buf[bufsize?(bufsize-1):0] = 0; + } + } + return buf; +} +#endif + /* Given a socket error code, if buf == NULL: * emallocs storage for the error message and returns * else @@ -1061,16 +1083,40 @@ PHPAPI int php_sockaddr_size(php_sockaddr_storage *addr) PHPAPI char *php_socket_strerror(long err, char *buf, size_t bufsize) { #ifndef PHP_WIN32 - char *errstr; - - errstr = strerror(err); +# ifdef HAVE_STRERROR_R + if (buf == NULL) { + char ebuf[1024]; +# ifdef STRERROR_R_CHAR_P + char *errstr = strerror_r(err, ebuf, sizeof(ebuf)); + buf = estrdup(errstr); +# else + errno_t res = strerror_r(err, ebuf, sizeof(ebuf)); + if (res == 0) { + buf = estrdup(ebuf); + } else { + buf = estrdup("Unknown error"); + } +# endif + } else { +# ifdef STRERROR_R_CHAR_P + buf = strerror_r(err, buf, bufsize); +# else + errno_t res = strerror_r(err, buf, bufsize); + if (res != 0) { + strncpy(buf, "Unknown error", bufsize); + buf[bufsize?(bufsize-1):0] = 0; + } +# endif + } +# else + char *errstr = strerror(err); if (buf == NULL) { buf = estrdup(errstr); } else { strncpy(buf, errstr, bufsize); buf[bufsize?(bufsize-1):0] = 0; } - return buf; +# endif #else char *sysbuf = php_win32_error_to_msg(err); if (!sysbuf[0]) { @@ -1085,9 +1131,8 @@ PHPAPI char *php_socket_strerror(long err, char *buf, size_t bufsize) } php_win32_error_msg_free(sysbuf); - - return buf; #endif + return buf; } /* }}} */ @@ -1095,9 +1140,22 @@ PHPAPI char *php_socket_strerror(long err, char *buf, size_t bufsize) PHPAPI zend_string *php_socket_error_str(long err) { #ifndef PHP_WIN32 - char *errstr; - - errstr = strerror(err); +# ifdef HAVE_STRERROR_R + char ebuf[1024]; +# ifdef STRERROR_R_CHAR_P + char *errstr = strerror_r(err, ebuf, sizeof(ebuf)); +# else + const char *errstr; + errno_t res = strerror_r(err, ebuf, sizeof(ebuf)); + if (res == 0) { + errstr = ebuf; + } else { + errstr = "Unknown error"; + } +# endif +# else + char *errstr = strerror(err); +# endif return zend_string_init(errstr, strlen(errstr), 0); #else zend_string *ret; diff --git a/main/php_network.h b/main/php_network.h index 6fc210b56837b..6b6e486fe48fc 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -66,6 +66,11 @@ * unless buf is not NULL. * Also works sensibly for win32 */ BEGIN_EXTERN_C() +#ifdef PHP_WIN32 +char *php_socket_strerror_s(long err, char *buf, size_t bufsize); +#else +#define php_socket_strerror_s php_socket_strerror +#endif PHPAPI char *php_socket_strerror(long err, char *buf, size_t bufsize); PHPAPI zend_string *php_socket_error_str(long err); END_EXTERN_C() diff --git a/main/rfc1867.c b/main/rfc1867.c index cbdf8aaa07c5d..43c4185677a45 100644 --- a/main/rfc1867.c +++ b/main/rfc1867.c @@ -1038,7 +1038,8 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ if (wlen == (size_t)-1) { /* write failed */ #if DEBUG_FILE_UPLOAD - sapi_module.sapi_error(E_NOTICE, "write() failed - %s", strerror(errno)); + char errstr[256]; + sapi_module.sapi_error(E_NOTICE, "write() failed - %s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); #endif cancel_upload = PHP_UPLOAD_ERROR_F; } else if (wlen < blen) { diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 1d5b7cfdac40d..9ac049f746847 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -368,7 +368,9 @@ static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t coun return bytes_written; } if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) { - php_error_docref(NULL, E_NOTICE, "Write of %zu bytes failed with errno=%d %s", count, errno, strerror(errno)); + char errstr[256]; + php_error_docref(NULL, E_NOTICE, "Write of %zu bytes failed with errno=%d %s", + count, errno, php_socket_strerror_s(errno, errstr, sizeof(errstr))); } } } else { @@ -444,7 +446,9 @@ static ssize_t php_stdiop_read(php_stream *stream, char *buf, size_t count) /* TODO: Should this be treated as a proper error or not? */ } else { if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) { - php_error_docref(NULL, E_NOTICE, "Read of %zu bytes failed with errno=%d %s", count, errno, strerror(errno)); + char errstr[256]; + php_error_docref(NULL, E_NOTICE, "Read of %zu bytes failed with errno=%d %s", + count, errno, php_socket_strerror_s(errno, errstr, sizeof(errstr))); } /* TODO: Remove this special-case? */ @@ -1278,7 +1282,9 @@ static int php_plain_files_unlink(php_stream_wrapper *wrapper, const char *url, ret = VCWD_UNLINK(url); if (ret == -1) { if (options & REPORT_ERRORS) { - php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno)); + char errstr[256]; + php_error_docref1(NULL, url, E_WARNING, "%s", + php_socket_strerror_s(errno, errstr, sizeof(errstr))); } return 0; } @@ -1324,6 +1330,7 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f if (ret == -1) { #ifndef PHP_WIN32 + char errstr[256]; # ifdef EXDEV if (errno == EXDEV) { zend_stat_t sb; @@ -1344,7 +1351,8 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f * access to the file in the meantime. */ if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) { - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); + php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", + php_socket_strerror_s(errno, errstr, sizeof(errstr))); if (errno != EPERM) { success = 0; } @@ -1352,7 +1360,8 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f if (success) { if (VCWD_CHMOD(url_to, sb.st_mode)) { - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); + php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", + php_socket_strerror_s(errno, errstr, sizeof(errstr))); if (errno != EPERM) { success = 0; } @@ -1363,10 +1372,12 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f VCWD_UNLINK(url_from); } } else { - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); + php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", + php_socket_strerror_s(errno, errstr, sizeof(errstr))); } } else { - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); + php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", + php_socket_strerror_s(errno, errstr, sizeof(errstr))); } # if !defined(ZTS) && !defined(TSRM_WIN32) umask(oldmask); @@ -1379,7 +1390,8 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f #ifdef PHP_WIN32 php_win32_docref2_from_error(GetLastError(), url_from, url_to); #else - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); + php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", + php_socket_strerror_s(errno, errstr, sizeof(errstr))); #endif return 0; } @@ -1449,11 +1461,12 @@ static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, i if (!p) { p = buf; } + char errstr[256]; while (true) { int ret = VCWD_MKDIR(buf, (mode_t) mode); if (ret < 0 && errno != EEXIST) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); + php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } return 0; } @@ -1473,7 +1486,7 @@ static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, i /* issue a warning to client when the last directory was created failed */ if (ret < 0) { if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); + php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); } return 0; } @@ -1492,15 +1505,16 @@ static int php_plain_files_rmdir(php_stream_wrapper *wrapper, const char *url, i return 0; } + char errstr[256]; #ifdef PHP_WIN32 if (!php_win32_check_trailing_space(url, strlen(url))) { - php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT)); + php_error_docref1(NULL, url, E_WARNING, "%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr))); return 0; } #endif if (VCWD_RMDIR(url) < 0) { - php_error_docref1(NULL, url, E_WARNING, "%s", strerror(errno)); + php_error_docref1(NULL, url, E_WARNING, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr))); return 0; } @@ -1519,10 +1533,11 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url #endif mode_t mode; int ret = 0; + char errstr[256]; #ifdef PHP_WIN32 if (!php_win32_check_trailing_space(url, strlen(url))) { - php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT)); + php_error_docref1(NULL, url, E_WARNING, "%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr))); return 0; } #endif @@ -1541,7 +1556,8 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url if (VCWD_ACCESS(url, F_OK) != 0) { FILE *file = VCWD_FOPEN(url, "w"); if (file == NULL) { - php_error_docref1(NULL, url, E_WARNING, "Unable to create file %s because %s", url, strerror(errno)); + php_error_docref1(NULL, url, E_WARNING, "Unable to create file %s because %s", url, + php_socket_strerror_s(errno, errstr, sizeof(errstr))); return 0; } fclose(file); @@ -1584,7 +1600,8 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url return 0; } if (ret == -1) { - php_error_docref1(NULL, url, E_WARNING, "Operation failed: %s", strerror(errno)); + php_error_docref1(NULL, url, E_WARNING, "Operation failed: %s", + php_socket_strerror_s(errno, errstr, sizeof(errstr))); return 0; } php_clear_stat_cache(0, NULL, 0); diff --git a/main/streams/streams.c b/main/streams/streams.c index 6dc073cd0baa3..46fd85e05e35d 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -203,7 +203,8 @@ static void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const free_msg = 1; } else { if (wrapper == &php_plain_files_wrapper) { - msg = strerror(errno); /* TODO: not ts on linux */ + char errstr[256]; + msg = php_socket_strerror_s(errno, errstr, sizeof(errstr)); } else { msg = "operation failed"; } diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c index b1d89bc44cb2b..ef07f5f486606 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -674,9 +674,10 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t * if (sock->socket == SOCK_ERR) { if (xparam->want_errortext) { + char errstr[256]; xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s", stream->ops == &php_stream_unix_socket_ops ? "" : "datagram", - strerror(errno)); + php_socket_strerror_s(errno, errstr, sizeof(errstr))); } return -1; } From 9fc14a90c6d16f447d50612e0626fbcefcc89343 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:24:58 +0200 Subject: [PATCH 6/9] Fix GH-16319: protect fiber backtrace with null filename from crashing (#19973) --- ext/zend_test/observer.c | 4 +- .../tests/observer_fiber_backtrace_crash.phpt | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 ext/zend_test/tests/observer_fiber_backtrace_crash.phpt diff --git a/ext/zend_test/observer.c b/ext/zend_test/observer.c index 9d0fb27c8a559..bff1d6ff93424 100644 --- a/ext/zend_test/observer.c +++ b/ext/zend_test/observer.c @@ -155,7 +155,7 @@ static void observer_show_init(zend_function *fbc) php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name)); } } else { - php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->op_array.filename)); + php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", fbc->op_array.filename ? ZSTR_VAL(fbc->op_array.filename) : "[no active file]"); } } @@ -178,7 +178,7 @@ static void observer_show_init_backtrace(zend_execute_data *execute_data) php_printf("%*s%s()\n", indent, "", ZSTR_VAL(fbc->common.function_name)); } } else { - php_printf("%*s{main} %s\n", indent, "", ZSTR_VAL(fbc->op_array.filename)); + php_printf("%*s{main} %s\n", indent, "", fbc->op_array.filename ? ZSTR_VAL(fbc->op_array.filename) : "[no active file]"); } } while ((ex = ex->prev_execute_data) != NULL); php_printf("%*s-->\n", 2 * ZT_G(observer_nesting_depth), ""); diff --git a/ext/zend_test/tests/observer_fiber_backtrace_crash.phpt b/ext/zend_test/tests/observer_fiber_backtrace_crash.phpt new file mode 100644 index 0000000000000..f19005179ac26 --- /dev/null +++ b/ext/zend_test/tests/observer_fiber_backtrace_crash.phpt @@ -0,0 +1,48 @@ +--TEST-- +GH-16319 (Fiber backtrace with null filename should not crash) +--EXTENSIONS-- +zend_test +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.show_init_backtrace=1 +zend_test.observer.show_output=1 +zend_test.observer.observe_all=1 +zend_test.observer.show_opcode=0 +opcache.jit=0 +--FILE-- +start(); +echo "Test completed without crash\n"; +?> +--EXPECTF-- + + + + + + + + + + + + + <{closure}> + + +Test completed without crash + From 0ffa337a5405906b680677aadc5b63eb5cde4606 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 29 Jun 2025 01:01:45 +0200 Subject: [PATCH 7/9] Fix GH-17345: Bug #35916 was not completely fixed Change the reproducer code in `bug35916.phpt` from `stream_bucket_append` to `stream_bucket_prepend` and you have the same bug. Furthermore, even in the append case the check is incorrect because the bucket can already be in the brigade at a position other than the tail. To solve this properly, unlink the brigade first and also use that as a condition to manage the refcount. Closes GH-18973. --- NEWS | 1 + ext/standard/tests/filters/gh17345.phpt | 49 +++++++++++++++++++++++++ ext/standard/user_filters.c | 14 ++++--- main/streams/filter.c | 1 + 4 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 ext/standard/tests/filters/gh17345.phpt diff --git a/NEWS b/NEWS index e60af85379c82..952d122fb9172 100644 --- a/NEWS +++ b/NEWS @@ -55,6 +55,7 @@ PHP NEWS - Streams: . Fixed bug GH-19248 (Use strerror_r instead of strerror in main). (Jakub Zelenka) + . Fixed bug GH-17345 (Bug #35916 was not completely fixed). (nielsdos) - XMLReader: . Fixed bug GH-20009 (XMLReader leak on RelaxNG schema failure). (nielsdos) diff --git a/ext/standard/tests/filters/gh17345.phpt b/ext/standard/tests/filters/gh17345.phpt new file mode 100644 index 0000000000000..f64659390a14f --- /dev/null +++ b/ext/standard/tests/filters/gh17345.phpt @@ -0,0 +1,49 @@ +--TEST-- +GH-17345 (Bug #35916 was not completely fixed) +--FILE-- +data = strtoupper($bucket->data); + $consumed += $bucket->datalen; + stream_bucket_prepend($out, $bucket); + // Interleave new bucket + stream_bucket_prepend($out, clone $bucket); + stream_bucket_prepend($out, $bucket); + } + return PSFS_PASS_ON; + } + + function onCreate(): bool + { + echo "fffffffffff\n"; + return true; + } + + function onClose(): void + { + echo "hello\n"; + } +} + +stream_filter_register("strtoupper", "strtoupper_filter"); +$fp=fopen($file, "w"); +stream_filter_append($fp, "strtoupper"); +fread($fp, 1024); +fwrite($fp, "Thank you\n"); +fclose($fp); +readfile($file); +unlink($file); +?> +--EXPECTF-- +fffffffffff + +Notice: fread(): Read of 8192 bytes failed with errno=9 Bad file descriptor in %s on line %d +hello +THANK YOU diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c index acef5146fa25e..962eaaba7d6b0 100644 --- a/ext/standard/user_filters.c +++ b/ext/standard/user_filters.c @@ -404,17 +404,19 @@ static void php_stream_bucket_attach(int append, INTERNAL_FUNCTION_PARAMETERS) memcpy(bucket->buf, Z_STRVAL_P(pzdata), bucket->buflen); } + /* If the bucket is already on a brigade we have to unlink it first to keep the + * linked list consistent. Furthermore, we can transfer the refcount in that case. */ + if (bucket->brigade) { + php_stream_bucket_unlink(bucket); + } else { + bucket->refcount++; + } + if (append) { php_stream_bucket_append(brigade, bucket); } else { php_stream_bucket_prepend(brigade, bucket); } - /* This is a hack necessary to accommodate situations where bucket is appended to the stream - * multiple times. See bug35916.phpt for reference. - */ - if (bucket->refcount == 1) { - bucket->refcount++; - } } /* }}} */ diff --git a/main/streams/filter.c b/main/streams/filter.c index a1d63c15f0a52..1ce03d41513cd 100644 --- a/main/streams/filter.c +++ b/main/streams/filter.c @@ -173,6 +173,7 @@ PHPAPI void php_stream_bucket_prepend(php_stream_bucket_brigade *brigade, php_st PHPAPI void php_stream_bucket_append(php_stream_bucket_brigade *brigade, php_stream_bucket *bucket) { + /* TODO: this was added as a bad workaround for bug #35916 and should be removed in the future. */ if (brigade->tail == bucket) { return; } From 71f8c399623c146fc991e5c67cd8fd897259abef Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:58:50 +0200 Subject: [PATCH 8/9] Fix test --- ext/zend_test/tests/observer_fiber_backtrace_crash.phpt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/zend_test/tests/observer_fiber_backtrace_crash.phpt b/ext/zend_test/tests/observer_fiber_backtrace_crash.phpt index f19005179ac26..3d3629af45bdb 100644 --- a/ext/zend_test/tests/observer_fiber_backtrace_crash.phpt +++ b/ext/zend_test/tests/observer_fiber_backtrace_crash.phpt @@ -34,15 +34,15 @@ echo "Test completed without crash\n"; {main} %s --> - + - <{closure}> - + <{closure:%s:%d}> + Test completed without crash From b1d487a2763b2314f921926f4223d406477a43f4 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:01:31 +0200 Subject: [PATCH 9/9] Fix GH-20022: docker-php-ext-install DOM failed Closes GH-20023. --- NEWS | 1 + ext/dom/lexbor/lexbor/selectors-adapted/selectors.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 30910ba6b9693..0cf43531c13b1 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,7 @@ PHP NEWS - DOM: . Fix macro name clash on macOS. (Ruoyu Zhong) + . Fixed bug GH-20022 (docker-php-ext-install DOM failed). (nielsdos) - GD: . Fixed GH-19955 (imagefttext() memory leak). (David Carlier) diff --git a/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c b/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c index 2bb41e43bec5d..4e094f632ef79 100644 --- a/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c +++ b/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c @@ -13,7 +13,7 @@ #include #include -#include "ext/dom/lexbor/lexbor/selectors-adapted/selectors.h" +#include "lexbor/selectors-adapted/selectors.h" #include "../../../namespace_compat.h" #include "../../../domexception.h" #include "../../../php_dom.h"