Commit 0c99bd76b10 for php.net

commit 0c99bd76b10b2d983e2cb801722a5d811eea0d79
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Sat Apr 18 09:17:51 2026 -0400

    phar: call phar_entry_delref before goto finish in phar_add_file error paths

    phar_add_file opens or creates an entry via phar_get_or_create_entry_data_rw,
    which increments the entry's reference count and must be balanced by a
    phar_entry_delref call. Two error paths inside the content-write block
    jumped to finish: with goto, skipping the phar_entry_delref at line 3714.
    The finish: label comes after the delref, so both paths leaked the entry
    reference.

    Add phar_entry_delref(data) before each goto finish in the short-write and
    missing-resource branches.

    Closes GH-21798
    Closes GH-21803

diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c
index e9d49567c82..bb96c783e93 100644
--- a/ext/phar/phar_object.c
+++ b/ext/phar/phar_object.c
@@ -3684,11 +3684,13 @@ static void phar_add_file(phar_archive_data **pphar, zend_string *file_name, con
 				size_t written_len = php_stream_write(data->fp, ZSTR_VAL(content), ZSTR_LEN(content));
 				if (written_len != contents_len) {
 					zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s could not be written to", filename);
+					phar_entry_delref(data);
 					goto finish;
 				}
 			} else {
 				if (!(php_stream_from_zval_no_verify(contents_file, zresource))) {
 					zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s could not be written to", filename);
+					phar_entry_delref(data);
 					goto finish;
 				}
 				php_stream_copy_to_stream_ex(contents_file, data->fp, PHP_STREAM_COPY_ALL, &contents_len);
diff --git a/ext/phar/tests/gh21798-add-file-delref.phpt b/ext/phar/tests/gh21798-add-file-delref.phpt
new file mode 100644
index 00000000000..fa893a1975e
--- /dev/null
+++ b/ext/phar/tests/gh21798-add-file-delref.phpt
@@ -0,0 +1,29 @@
+--TEST--
+GH-21798: phar_add_file must call phar_entry_delref on write error paths
+--EXTENSIONS--
+phar
+--INI--
+phar.readonly=0
+phar.require_hash=0
+--FILE--
+<?php
+// Regression baseline: verify addFile and addFromString succeed and
+// phar_entry_delref is not skipped on the happy path.
+$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar';
+
+$phar = new Phar($fname);
+$phar->addFromString('hello.txt', 'hello world');
+$phar->addFromString('empty.txt', '');
+unset($phar);
+
+$phar = new Phar($fname);
+echo $phar['hello.txt']->getContent() . "\n";
+echo ($phar->offsetExists('empty.txt') ? 'empty exists' : 'missing') . "\n";
+echo "no crash\n";
+?>
+--CLEAN--
+<?php @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar'); ?>
+--EXPECT--
+hello world
+empty exists
+no crash