Commit b9aaa050985 for php.net

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

    phar: free is_temp_dir entry before rejecting .phar/* paths in offsetGet

    Phar::offsetGet() calls phar_get_entry_info_dir with allow_dir=1, which
    may return a heap-allocated temporary directory entry (is_temp_dir=1)
    for paths that resolve to a virtual directory in the manifest. Three
    early-exit paths for .phar/stub.php, .phar/alias.txt, and the generic
    .phar/* prefix all called RETURN_THROWS() before the is_temp_dir cleanup
    block, leaking the entry and its filename buffer on every rejection.

    Move the is_temp_dir cleanup before the .phar/* guards so all exit paths
    release the temporary entry regardless of which rejection fires.

    Closes GH-21798

diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c
index cd888adc41e..e9d49567c82 100644
--- a/ext/phar/phar_object.c
+++ b/ext/phar/phar_object.c
@@ -3594,6 +3594,11 @@ PHP_METHOD(Phar, offsetGet)
 	if (!(entry = phar_get_entry_info_dir(phar_obj->archive, ZSTR_VAL(file_name), ZSTR_LEN(file_name), 1, &error, 0))) {
 		zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist%s%s", ZSTR_VAL(file_name), error?", ":"", error?error:"");
 	} else {
+		if (entry->is_temp_dir) {
+			efree(entry->filename);
+			efree(entry);
+		}
+
 		if (zend_string_equals_literal(file_name, ".phar/stub.php")) {
 			zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot get stub \".phar/stub.php\" directly in phar \"%s\", use getStub", phar_obj->archive->fname);
 			RETURN_THROWS();
@@ -3609,11 +3614,6 @@ PHP_METHOD(Phar, offsetGet)
 			RETURN_THROWS();
 		}

-		if (entry->is_temp_dir) {
-			efree(entry->filename);
-			efree(entry);
-		}
-
 		zend_string *sfname = strpprintf(0, "phar://%s/%s", phar_obj->archive->fname, ZSTR_VAL(file_name));
 		zval zfname;
 		ZVAL_NEW_STR(&zfname, sfname);
diff --git a/ext/phar/tests/gh21798-offsetget-temp-entry.phpt b/ext/phar/tests/gh21798-offsetget-temp-entry.phpt
new file mode 100644
index 00000000000..b5b11ad4363
--- /dev/null
+++ b/ext/phar/tests/gh21798-offsetget-temp-entry.phpt
@@ -0,0 +1,39 @@
+--TEST--
+GH-21798: Phar::offsetGet() must free is_temp_dir entry before rejecting .phar/* paths
+--EXTENSIONS--
+phar
+--INI--
+phar.readonly=0
+phar.require_hash=0
+--FILE--
+<?php
+$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar';
+$phar = new Phar($fname);
+$phar->addFromString('index.php', '<?php echo "ok"; ?>');
+unset($phar);
+
+$phar = new Phar($fname);
+try {
+    $phar->offsetGet('.phar/stub.php');
+} catch (BadMethodCallException $e) {
+    echo $e->getMessage() . "\n";
+}
+try {
+    $phar->offsetGet('.phar/alias.txt');
+} catch (BadMethodCallException $e) {
+    echo $e->getMessage() . "\n";
+}
+try {
+    $phar->offsetGet('.phar/internal');
+} catch (BadMethodCallException $e) {
+    echo $e->getMessage() . "\n";
+}
+echo "no crash\n";
+?>
+--CLEAN--
+<?php @unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar'); ?>
+--EXPECT--
+Entry .phar/stub.php does not exist
+Entry .phar/alias.txt does not exist
+Entry .phar/internal does not exist
+no crash