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