Commit f3db5d60f39 for php

commit f3db5d60f3951eb880723f30bd8cb0d989d1d6a6
Author: Weilin Du <weilindu@php.net>
Date:   Mon Sep 28 00:36:42 2026 +0800

    Fix GH-23899: Handle bailout from ZipArchive cancel callback return validation (#23914)

    Co-authored-by: David Carlier <devnexen@gmail.com>

diff --git a/NEWS b/NEWS
index 5dadee43b6c..24cbe0a83a5 100644
--- a/NEWS
+++ b/NEWS
@@ -26,6 +26,10 @@ PHP                                                                        NEWS
 - PDO_PGSQL:
   . Fixed crash when a persistent connection fails. (KentarouTakeda)

+- Zip:
+  . Fixed bug GH-23899 (Assertion failure when a cancel callback returns an
+    invalid type during shutdown). (Weilin Du)
+
 24 Sep 2026, PHP 8.6.0RC2

 - Core:
diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c
index 88fdcaa9b03..a095f4fcca3 100644
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@ -3241,15 +3241,21 @@ static int php_zip_cancel_callback(zip_t *arch, void *ptr)
 		/* Cancel if an exception has been thrown */
 		return -1;
 	}
-	bool failed;
-	zend_long retval = zval_try_get_long(&cb_retval, &failed);
-	if (failed) {
-		zend_type_error("Return value of callback provided to ZipArchive::registerCancelCallback()"
-			" must be of type int, %s returned", zend_zval_value_name(&cb_retval));
+	zend_long retval;
+	/* Conversion and reporting an invalid return type can both bail out during shutdown. */
+	zend_try {
+		bool failed;
+		retval = zval_try_get_long(&cb_retval, &failed);
+		if (failed) {
+			retval = -1;
+			zend_type_error("Return value of callback provided to ZipArchive::registerCancelCallback()"
+				" must be of type int, %s returned", zend_zval_value_name(&cb_retval));
+		}
 		zval_ptr_dtor(&cb_retval);
+	} zend_catch {
+		archive->bailout_callback = true;
 		return -1;
-	}
-	zval_ptr_dtor(&cb_retval);
+	} zend_end_try();

 	return (int) retval;
 }
diff --git a/ext/zip/tests/gh23899.phpt b/ext/zip/tests/gh23899.phpt
new file mode 100644
index 00000000000..233a5a2666f
--- /dev/null
+++ b/ext/zip/tests/gh23899.phpt
@@ -0,0 +1,31 @@
+--TEST--
+GH-23899 (Invalid cancel callback return type during shutdown causes an assertion failure)
+--EXTENSIONS--
+zip
+--SKIPIF--
+<?php
+if (!method_exists(ZipArchive::class, 'registerCancelCallback')) {
+    die('skip cancel callbacks are not supported');
+}
+?>
+--FILE--
+<?php
+$zip = new ZipArchive;
+$zip->open(__DIR__ . '/gh23899.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE);
+$zip->registerCancelCallback(function () {
+    return [new stdClass];
+});
+$zip->addFromString('test', 'test');
+echo "Done\n";
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/gh23899.zip');
+?>
+--EXPECTF--
+Done
+
+Fatal error: Uncaught TypeError: Return value of callback provided to ZipArchive::registerCancelCallback() must be of type int, array returned in %s:%d
+Stack trace:
+#0 {main}
+  thrown in %s on line %d
diff --git a/ext/zip/tests/gh23899_conversion.phpt b/ext/zip/tests/gh23899_conversion.phpt
new file mode 100644
index 00000000000..1e86ce5be82
--- /dev/null
+++ b/ext/zip/tests/gh23899_conversion.phpt
@@ -0,0 +1,38 @@
+--TEST--
+GH-23899 (Bailout during cancel callback return value conversion at shutdown)
+--EXTENSIONS--
+zip
+--SKIPIF--
+<?php
+if (!method_exists(ZipArchive::class, 'registerCancelCallback')) {
+    die('skip cancel callbacks are not supported');
+}
+?>
+--INI--
+zend.exception_ignore_args=1
+--FILE--
+<?php
+set_error_handler(function ($errno, $message) {
+    throw new Exception($message);
+});
+
+$zip = new ZipArchive;
+$zip->open(__DIR__ . '/gh23899_conversion.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE);
+$zip->registerCancelCallback(function () {
+    return '123abc';
+});
+$zip->addFromString('test', 'test');
+echo "Done\n";
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/gh23899_conversion.zip');
+?>
+--EXPECTF--
+Done
+
+Fatal error: Uncaught Exception: A non-numeric value encountered in %s:%d
+Stack trace:
+#0 [internal function]: {closure:%s:%d}()
+#1 {main}
+  thrown in %s on line %d
diff --git a/ext/zip/tests/gh23899_destructor.phpt b/ext/zip/tests/gh23899_destructor.phpt
new file mode 100644
index 00000000000..fad10717e59
--- /dev/null
+++ b/ext/zip/tests/gh23899_destructor.phpt
@@ -0,0 +1,32 @@
+--TEST--
+GH-23899 (Cancel callback return value whose destructor throws during shutdown)
+--EXTENSIONS--
+zip
+--SKIPIF--
+<?php
+if (!method_exists(ZipArchive::class, 'registerCancelCallback')) {
+    die('skip cancel callbacks are not supported');
+}
+?>
+--FILE--
+<?php
+class ThrowingDestructor {
+    public function __destruct() {
+        throw new Exception('destructor');
+    }
+}
+
+$zip = new ZipArchive;
+$zip->open(__DIR__ . '/gh23899_destructor.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE);
+$zip->registerCancelCallback(function () {
+    return new ThrowingDestructor;
+});
+$zip->addFromString('test', 'test');
+echo "Done\n";
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/gh23899_destructor.zip');
+?>
+--EXPECT--
+Done