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