Skip to content

Fix GH-23276: Collect ZipArchive subclasses holding their own streams - #23282

Draft
LamentXU123 wants to merge 9 commits into
php:PHP-8.4from
LamentXU123:zip-fix
Draft

Fix GH-23276: Collect ZipArchive subclasses holding their own streams#23282
LamentXU123 wants to merge 9 commits into
php:PHP-8.4from
LamentXU123:zip-fix

Conversation

@LamentXU123

@LamentXU123 LamentXU123 commented Aug 14, 2026

Copy link
Copy Markdown
Member

I actually doubt this is a bug in the Zend engine. But since I am not familiar with how Zend GC works, I'd fix this is in a completely ext/zip way, to replace the stream's reference to the ZipArchive object with a refcounted archive state, so a stream can keep the underlying zip_t without preventing its owning ZipArchive object from being GC-ed.

There can be better fixes, as I said, to fix in the Zend engine. But I think this is a very rare case happening in the zip extension, so I'd rather re-implement it than touching the Zend core, so we don't bother other code.

Fixes GH-23276

@devnexen

devnexen commented Aug 14, 2026

Copy link
Copy Markdown
Member

There can be better fixes, as I said, to fix in the Zend engine. But I think this is a very rare case happening in the zip extension, so I'd rather re-implement it than touching Zend core, so we don't bother other code.

It is probably best for a stable branch IMO, that being said I ll review it sometime this weekend.

@devnexen

Copy link
Copy Markdown
Member

I would suggest one thing after a quick look. Keep at it, this refcounting approach is likely to cause indirect issues.

@ndossche

Copy link
Copy Markdown
Member

I think this is wrong, I believe you need a get_gc object handler for ZipArchive.

@LamentXU123
LamentXU123 marked this pull request as draft August 14, 2026 18:36
@LamentXU123

Copy link
Copy Markdown
Member Author

Apparently I have to take some time to study Zend GC. It seems like we don't have a reasonable way to bypass this entirely.

@LamentXU123
LamentXU123 marked this pull request as ready for review August 15, 2026 10:41
Comment thread ext/zip/zip_stream.c Outdated
* releasing it again would underflow the refcount. A non-zero
* refcount means that this stream is being closed normally.
*/
if (GC_REFCOUNT(&self->owner->zo) != 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ll see the rest later but what happens if a php globals still holds the resource then we fclose on it for ex ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its fine at the first glance. Let's add a regression test just in case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

came up with this

<?php
class Reader extends ZipArchive {
    public $stream;
    public function __destruct() { $GLOBALS['zombie'] = $this; }
}

$f = __DIR__ . '/n5s.zip';
$z = new ZipArchive;
$z->open($f, ZipArchive::CREATE | ZipArchive::OVERWRITE);
$z->addFromString('entry.txt', 'contents');
$z->close();

$zip = new Reader;
$zip->open($f, ZipArchive::RDONLY);
$zip->stream = $zip->getStream('entry.txt');
unset($zip);
gc_collect_cycles();

var_dump($GLOBALS['zombie'] instanceof ZipArchive);
var_dump($GLOBALS['zombie']->stream);
echo 'done', PHP_EOL;
@unlink($f);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ll see the rest later but what happens if a php globals still holds the resource then we fclose on it for ex ?

You are correct. ZipArchive's owner pin doesn't delete the object. Thanks.

@devnexen

devnexen commented Aug 15, 2026

Copy link
Copy Markdown
Member

something tells me it s probably best to keep it as draft for now, I did not review the whole code yet but already unsure everything is fully correct.

@LamentXU123
LamentXU123 marked this pull request as draft August 15, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants