Commit e0a1098ed42 for php
commit e0a1098ed4222aaf7f596d1c67397bfcbcfd60fb
Merge: 1228a4c3b4d d5710aefdf9
Author: Weilin Du <weilindu@php.net>
Date: Thu Oct 8 17:37:03 2026 +0800
Merge branch 'PHP-8.5'
* PHP-8.5:
ext/zip: Fix use-after-free in the archive destructor path (#23779)
diff --cc NEWS
index d11116613be,9b485ee4559..2f82b0f2b5b
--- a/NEWS
+++ b/NEWS
@@@ -7,15 -7,11 +7,19 @@@ PH
root trace at the opcache.jit_max_root_traces limit, causing spurious
"Too few arguments" errors and crashes). (RV7PR)
+- Phar:
+ . Fixed GH-24166 (Double-free in Phar::webPhar() in CGI without PATH_INFO).
+ (RigelYoung, Jakub Zelenka)
+
+- Standard:
+ . Fixed chown() and lchown() failing to resolve user names in ZTS builds
+ when getpwnam_r() needs a larger buffer. (Ilia Alshanetsky)
+
+ - Zip:
+ . Fixed use-after-free when re-entering ZipArchive during destruction or
+ a close warning, and rejected opening streams while closing. (jvoisin)
+
-22 Oct 2026, PHP 8.5.12
+08 Oct 2026, PHP 8.6.0RC3
- BCMath:
. Fixed BcMath\Number results that truncate to zero keeping a negative sign
diff --cc ext/zip/php_zip.c
index 40f4a34f34f,8fce5d4a2c2..fc2998a7ca3
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@@ -634,85 -632,7 +634,85 @@@ static char * php_zipobj_get_zip_commen
}
/* }}} */
-int php_zip_glob(char *pattern, int pattern_len, zend_long flags, zval *return_value) /* {{{ */
+static bool php_zipobj_closing(ze_zip_object *obj) /* {{{ */
+{
+ if (obj->archive && obj->archive->close) {
+ zend_throw_error(NULL, "Already being closed");
+ return true;
+ }
+ return false;
+}
+/* }}} */
+
+/* Close and free the zip_t. If the archive was opened as a string, the
+ * final contents of the archive will be assigned to *out_str and that
+ * string will afterwards be owned by the caller.
+ *
+ * If out_str is NULL, the final string contents, if any, will be discarded. */
+static bool php_zipobj_close(ze_zip_object *obj, zend_string **out_str) /* {{{ */
+{
+ php_zip_archive *archive = obj->archive;
+ struct zip *intern = archive ? archive->za : NULL;
+ bool bailout = false;
+ bool success = false;
+
+ if (intern) {
+ archive->close = true;
+ int err = zip_close(intern);
- archive->close = false;
+ if (err) {
+ php_error_docref(NULL, E_WARNING, "%s", zip_strerror(intern));
+ /* Save error for property reader */
+ zip_error_t *ziperr = zip_get_error(intern);
+ obj->err_zip = zip_error_code_zip(ziperr);
+ obj->err_sys = zip_error_code_system(ziperr);
+ zip_error_fini(ziperr);
+ zip_discard(intern);
+ } else {
+ obj->err_zip = 0;
+ obj->err_sys = 0;
+ }
+ success = !err;
+ }
+
+ /* if we have a filename, we need to free it */
+ if (obj->filename) {
+ /* clear cache as empty zip are not created but deleted */
+ php_clear_stat_cache(1, obj->filename, obj->filename_len);
+
+ efree(obj->filename);
+ obj->filename = NULL;
+ obj->filename_len = 0;
+ }
+
+ if (archive && archive->out_str) {
+ if (out_str) {
+ *out_str = archive->out_str;
+ } else {
+ zend_string_release(archive->out_str);
+ }
+ archive->out_str = NULL;
+ } else {
+ ZEND_ASSERT(!out_str);
+ }
+
+ if (archive) {
+ archive->za = NULL;
++ archive->close = false;
+ bailout = archive->bailout_callback;
+ archive->bailout_callback = false;
+ obj->archive = NULL;
+ bailout |= php_zip_archive_release(archive);
+ }
+
+ if (bailout) {
+ zend_bailout();
+ }
+
+ return success;
+}
+/* }}} */
+
+static int php_zip_glob(zend_string *spattern, zend_long flags, zval *return_value) /* {{{ */
{
int cwd_skip = 0;
#ifdef ZTS
@@@ -1128,6 -1046,10 +1128,10 @@@ static void php_zip_progress_callback_f
{
php_zip_archive *archive = ptr;
- if (UNEXPECTED(!EG(active))) {
++ if (UNEXPECTED(!EG(active) || archive->bailout_callback)) {
+ return;
+ }
+
if (ZEND_FCC_INITIALIZED(archive->progress_callback)) {
zend_fcc_dtor(&archive->progress_callback);
}
@@@ -1139,6 -1061,10 +1143,10 @@@ static void php_zip_cancel_callback_fre
{
php_zip_archive *archive = ptr;
- if (UNEXPECTED(!EG(active))) {
++ if (UNEXPECTED(!EG(active) || archive->bailout_callback)) {
+ return;
+ }
+
if (ZEND_FCC_INITIALIZED(archive->cancel_callback)) {
zend_fcc_dtor(&archive->cancel_callback);
}
@@@ -1169,13 -1095,15 +1177,18 @@@ bool php_zip_archive_release(php_zip_ar
}
if (archive->za) {
- if (zip_close(archive->za) != 0) {
+ /* Guard against a re-entrant close() or open() from a progress/cancel
+ * callback fired during zip_close(), which would run a nested zip_close()
- * on the same archive (see ZipArchive::close()). */
++ * on the same archive (see php_zipobj_close()). */
+ archive->close = true;
+ int err = zip_close(archive->za);
+ if (err != 0) {
- php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(archive->za));
+ if (!archive->bailout_callback) {
+ php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(archive->za));
+ }
zip_discard(archive->za);
}
+ archive->za = NULL;
}
#ifdef HAVE_PROGRESS_CALLBACK
@@@ -3131,6 -3128,11 +3144,10 @@@ static void php_zip_get_stream(INTERNAL
ZIP_FROM_OBJECT(intern, self);
- if (Z_ZIP_P(self)->archive->close) {
- zend_throw_error(NULL, "Already being closed");
++ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (type) {
PHP_ZIP_STAT_PATH(intern, ZSTR_VAL(filename), ZSTR_LEN(filename), flags, sb);
} else {