Commit 614afe7b09f for php.net

commit 614afe7b09fd37c57f71a02fecfac33191ea141a
Author: David Carlier <devnexen@gmail.com>
Date:   Sat Aug 29 20:58:17 2026 +0100

    ext/zip: php_zip_ops_stat() succeeds when the archive cannot be opened.

    When zip_open() failed the whole stat block was skipped, yet the function
    still returned 0. fstat() on a zip:// stream therefore succeeded with the
    zeroed statbuf it was given, reporting a zero size and no file type bits,
    instead of failing. Return -1 on that path.

    Close GH-23511

diff --git a/NEWS b/NEWS
index aa7862f63bb..db1aba15a8e 100644
--- a/NEWS
+++ b/NEWS
@@ -82,6 +82,8 @@ PHP                                                                        NEWS
     on corrupted entries. (David Carlier)
   . Fixed ZipArchive::getNameIndex() truncating the entry index to int.
     (David Carlier)
+  . Fixed fstat() on a zip:// stream reporting success when the archive cannot
+    be opened. (David Carlier)

 - SAPI:
   . Fixed fuzzer targets failing to build in isolation. (Mrmaxmeier)
diff --git a/ext/zip/tests/stream_fstat_unreadable_archive.phpt b/ext/zip/tests/stream_fstat_unreadable_archive.phpt
new file mode 100644
index 00000000000..a81fc8fc3cf
--- /dev/null
+++ b/ext/zip/tests/stream_fstat_unreadable_archive.phpt
@@ -0,0 +1,38 @@
+--TEST--
+fstat() on a zip:// stream whose archive can no longer be opened
+--EXTENSIONS--
+zip
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY === 'Windows') die('skip the archive cannot be rewritten while it is open');
+?>
+--FILE--
+<?php
+$file = __DIR__ . '/stream_fstat_unreadable_archive.zip';
+
+@unlink($file);
+
+$zip = new ZipArchive;
+if (!$zip->open($file, ZipArchive::CREATE)) {
+    exit('failed');
+}
+
+$zip->addFromString('entry.txt', 'entry');
+$zip->close();
+
+$fp = fopen('zip://' . $file . '#entry.txt', 'rb');
+var_dump($fp !== false);
+
+file_put_contents($file, 'this is not a zip archive');
+
+var_dump(fstat($fp));
+
+fclose($fp);
+?>
+--EXPECT--
+bool(true)
+bool(false)
+--CLEAN--
+<?php
+unlink(__DIR__ . '/stream_fstat_unreadable_archive.zip');
+?>
diff --git a/ext/zip/zip_stream.c b/ext/zip/zip_stream.c
index 0356863ef7c..b70b82a415e 100644
--- a/ext/zip/zip_stream.c
+++ b/ext/zip/zip_stream.c
@@ -195,6 +195,9 @@ static int php_zip_ops_stat(php_stream *stream, php_stream_statbuf *ssb) /* {{{
 		ssb->sb.st_blocks = -1;
 #endif
 		ssb->sb.st_ino = -1;
+	} else {
+		zend_string_release_ex(file_basename, 0);
+		return -1;
 	}
 	zend_string_release_ex(file_basename, 0);
 	return 0;