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 {