[php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5

From: Date: Thu, 08 Oct 2026 10:03:17 +0000
Subject: [php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-142080@lists.php.net to get a copy of this message
Author: Weilin Du (LamentXU123) Date: 2026-10-08T17:35:57+08:00 Commit: https://github.com/php/php-src/commit/d5710aefdf95f119555264d3bb2d866ce60e9278 Raw diff: https://github.com/php/php-src/commit/d5710aefdf95f119555264d3bb2d866ce60e9278.diff Merge branch 'PHP-8.4' into PHP-8.5 * PHP-8.4: ext/zip: Fix use-after-free in the archive destructor path (#23779) Changed paths: A ext/zip/tests/gh23747_close_error.phpt A ext/zip/tests/gh23747_dtor.phpt M NEWS M ext/zip/php_zip.c Diff: diff --git a/NEWS b/NEWS index 061ecfdf74ce..9b485ee45596 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,10 @@ PHP NEWS root trace at the opcache.jit_max_root_traces limit, causing spurious "Too few arguments" errors and crashes). (RV7PR) +- 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 - BCMath: diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index 12d004cc27d8..8fce5d4a2c28 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -1046,6 +1046,10 @@ static void php_zip_progress_callback_free(void *ptr) { php_zip_archive *archive = ptr; + if (UNEXPECTED(!EG(active))) { + return; + } + if (ZEND_FCC_INITIALIZED(archive->progress_callback)) { zend_fcc_dtor(&archive->progress_callback); } @@ -1057,6 +1061,10 @@ static void php_zip_cancel_callback_free(void *ptr) { php_zip_archive *archive = ptr; + if (UNEXPECTED(!EG(active))) { + return; + } + if (ZEND_FCC_INITIALIZED(archive->cancel_callback)) { zend_fcc_dtor(&archive->cancel_callback); } @@ -1087,7 +1095,12 @@ void php_zip_archive_release(php_zip_archive *archive) } 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()). */ + 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)); zip_discard(archive->za); } @@ -1119,6 +1132,7 @@ static void php_zip_object_detach_archive(ze_zip_object *ze_obj, struct zip *rel ZEND_ASSERT(ze_obj->archive != NULL); ZEND_ASSERT(ze_obj->archive->za == released_za); ze_obj->archive->za = NULL; + ze_obj->archive->close = false; php_zip_archive_release(ze_obj->archive); ze_obj->archive = NULL; } @@ -1638,7 +1652,6 @@ PHP_METHOD(ZipArchive, close) ze_obj->archive->close = true; err = zip_close(intern); - ze_obj->archive->close = false; if (err) { php_error_docref(NULL, E_WARNING, "%s", zip_strerror(intern)); /* Save error for property reader */ @@ -3115,6 +3128,11 @@ static void php_zip_get_stream(INTERNAL_FUNCTION_PARAMETERS, int type, bool acce ZIP_FROM_OBJECT(intern, self); + if (Z_ZIP_P(self)->archive->close) { + zend_throw_error(NULL, "Already being closed"); + RETURN_THROWS(); + } + if (type) { PHP_ZIP_STAT_PATH(intern, ZSTR_VAL(filename), ZSTR_LEN(filename), flags, sb); } else { diff --git a/ext/zip/tests/gh23747_close_error.phpt b/ext/zip/tests/gh23747_close_error.phpt new file mode 100644 index 000000000000..2af621867e85 --- /dev/null +++ b/ext/zip/tests/gh23747_close_error.phpt @@ -0,0 +1,48 @@ +--TEST-- +GH-23747 (Re-entrant close from a ZipArchive close warning is rejected) +--EXTENSIONS-- +zip +--FILE-- +<?php +$filename = __DIR__ . '/gh23747_close_error.zip'; +$source = __DIR__ . '/gh23747_close_error.txt'; + +foreach (['close', 'destruct'] as $operation) { + echo $operation, ":\n"; + file_put_contents($source, 'contents'); + $zip = new ZipArchive(); + $zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE); + $zip->addFile($source, 'file.txt'); + unlink($source); + + $weak = WeakReference::create($zip); + set_error_handler(static function (int $errno, string $message) use ($weak): bool { + try { + $weak->get()->close(); + } catch (Error $error) { + echo $error::class, ': ', $error->getMessage(), "\n"; + } + return true; + }); + + if ($operation === 'close') { + var_dump($zip->close()); + } else { + unset($zip); + echo "destroyed\n"; + } + restore_error_handler(); +} +?> +--CLEAN-- +<?php +@unlink(__DIR__ . '/gh23747_close_error.zip'); +@unlink(__DIR__ . '/gh23747_close_error.txt'); +?> +--EXPECT-- +close: +Error: Already being closed +bool(false) +destruct: +Error: Already being closed +destroyed diff --git a/ext/zip/tests/gh23747_dtor.phpt b/ext/zip/tests/gh23747_dtor.phpt new file mode 100644 index 000000000000..5732eb98383f --- /dev/null +++ b/ext/zip/tests/gh23747_dtor.phpt @@ -0,0 +1,58 @@ +--TEST-- +GH-23747 (Re-entrant operations during ZipArchive destruction are rejected) +--EXTENSIONS-- +zip +--SKIPIF-- +<?php +if (!method_exists(ZipArchive::class, 'registerProgressCallback')) { + die('skip progress callbacks are not supported'); +} +?> +--FILE-- +<?php +$filename = __DIR__ . '/gh23747_dtor.zip'; + +foreach ([ + ['close', []], + ['getStream', ['f0.txt']], + ['getStreamName', ['f0.txt']], + ['getStreamIndex', [0]], +] as [$method, $arguments]) { + $zip = new ZipArchive(); + $zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE); + for ($index = 0; $index < 64; $index++) { + $zip->addFromString("f$index.txt", str_repeat('x', 2000)); + } + + $weak = WeakReference::create($zip); + $callback = static function (float $rate) use ($weak, $method, $arguments): void { + static $done = false; + if ($done) { + return; + } + $done = true; + + try { + $weak->get()->$method(...$arguments); + } catch (Error $error) { + echo $method, ': ', $error->getMessage(), "\n"; + } + }; + $zip->registerProgressCallback(0.0, $callback); + unset($zip); + echo "destroyed\n"; +} +?> +--CLEAN-- +<?php +@unlink(__DIR__ . '/gh23747_dtor.zip'); +?> +--EXPECT-- +close: Already being closed +destroyed +getStream: Already being closed +destroyed +getStreamName: Already being closed +destroyed +getStreamIndex: Already being closed +destroyed

« previous php.cvs (#142080) next »