Fix GH-17787: ZipArchive stream truncates when the archive is freed - #22555
Fix GH-17787: ZipArchive stream truncates when the archive is freed#22555eyupcanakman wants to merge 2 commits into
Conversation
…e is freed getStream(), getStreamIndex() and getStreamName() return a stream that reads from the ZipArchive's underlying zip_t but keeps no reference to it. When the object is freed while the stream is still open, the destructor closes the zip_t and reads stop partway through. Keep the archive object alive for the stream's lifetime so the borrowed handle stays valid until the stream is closed.
|
is this PR still needed? current master running the test from this PR: which is the correct error. |
|
That output is the bug the PR fixes. gh17787.phpt fails on current master the same way for me and passes with the patch applied, and cmb69 opened the issue back up because the zip_t shouldn't be closed while a stream is still open. |
LamentXU123
left a comment
There was a problem hiding this comment.
Could we have a test for getStreamName?
|
|
||
| php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC); | ||
| php_stream *php_stream_zip_open(struct zip *arch, struct zip_stat *sb, const char *mode, zip_flags_t flags STREAMS_DC); | ||
| php_stream *php_stream_zip_open(ze_zip_object *obj, struct zip_stat *sb, const char *mode, zip_flags_t flags STREAMS_DC); |
There was a problem hiding this comment.
I would say it is probably possible to avoid this change in a stable branch.
There was a problem hiding this comment.
php_stream_zip_open has a single caller in php_zip.c, the symbol isn't exported (no PHPAPI, hidden visibility) and php_zip.h isn't installed, so the prototype change isn't visible outside the tree. Can keep the old signature and add a second function that takes the object if you'd rather avoid it on 8.4.
There was a problem hiding this comment.
No you re right.
Would it be possible however to update the test as follow ?
diff --git a/ext/zip/tests/gh17787.phpt b/ext/zip/tests/gh17787.phpt
index c05558191cf..f82cfa1145b 100644
--- a/ext/zip/tests/gh17787.phpt
+++ b/ext/zip/tests/gh17787.phpt
@@ -30,11 +30,51 @@
var_dump(stream_get_contents($stream) === $data);
fclose($stream);
+
+// Same with getStream()
+$zip = new ZipArchive;
+$zip->open($name, ZipArchive::RDONLY);
+$stream = $zip->getStream('entry.txt');
+$zip = null;
+
+var_dump(stream_get_contents($stream) === $data);
+fclose($stream);
+
+// Pending changes are still committed once the last stream is closed
+$name = __DIR__ . '/gh17787_write.zip';
+
+$zip = new ZipArchive;
+var_dump($zip->open($name, ZipArchive::CREATE | ZipArchive::OVERWRITE));
+$zip->addFromString('first.txt', 'first');
+$zip->close();
+
+$zip = new ZipArchive;
+var_dump($zip->open($name));
+$zip->addFromString('second.txt', 'second');
+$stream = $zip->getStreamName('first.txt', ZipArchive::FL_UNCHANGED);
+$zip = null;
+
+var_dump(stream_get_contents($stream));
+fclose($stream);
+
+$zip = new ZipArchive;
+var_dump($zip->open($name, ZipArchive::RDONLY));
+var_dump($zip->numFiles);
+var_dump($zip->getFromName('second.txt'));
+$zip->close();
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/gh17787.zip');
+@unlink(__DIR__ . '/gh17787_write.zip');
?>
--EXPECT--
bool(true)
bool(true)
+bool(true)
+bool(true)
+bool(true)
+string(5) "first"
+bool(true)
+int(2)
+string(6) "second"|
Added a |
ZipArchive::getStreamIndex()(andgetStream/getStreamName) hands back a stream that reads from the archive's underlyingzip_twithout keeping a reference to it. When theZipArchiveobject is freed while the stream is still open, as in the report where$zgets reassigned inside the read loop, the object destructor closes thezip_tand the stream stops reading early. cmb69 diagnosed this on the issue. The fix keeps the archive object alive for the stream's lifetime, so the borrowed handle stays valid until the stream is closed.One case this does not cover is calling
$zip->close()or reopening the same object while a stream is open, which hits the same truncation by a different route. I can extend the fix to those paths if you prefer.