Commit b2de3cf170f for php.net
commit b2de3cf170f81b203dfc2622f2f2dd1572ef95be
Author: David Carlier <devnexen@gmail.com>
Date: Fri May 8 22:33:59 2026 +0100
Fix GH-21986: PharData::getContent() crash on infinite recursion with symlinks.
The entries being unbounded when cycling through them, it blows the
stack.
close GH-21989
diff --git a/NEWS b/NEWS
index 80567e1ff2b..0dd41914ed4 100644
--- a/NEWS
+++ b/NEWS
@@ -31,6 +31,10 @@ PHP NEWS
- OpenSSL:
. Fix compatibility issues with OpenSSL 4.0. (jordikroon, Remi)
+- Phar:
+ . Fixed bug GH-21986 (PharData::getContent() crash on infinite recursion
+ with symlinks). (David Carlier)
+
- SOAP:
. Fixed integer overflow when decoding SOAP array indexes. (Weilin Du)
diff --git a/ext/phar/tests/tar/gh21986.phpt b/ext/phar/tests/tar/gh21986.phpt
new file mode 100644
index 00000000000..13205d40a65
--- /dev/null
+++ b/ext/phar/tests/tar/gh21986.phpt
@@ -0,0 +1,43 @@
+--TEST--
+GH-21986 (PharData::getContent() crash on circular symlink chain in tar)
+--EXTENSIONS--
+phar
+--FILE--
+<?php
+function tar_symlink($name, $target) {
+ $h = str_pad($name, 100, "\0");
+ $h .= "0000777\0";
+ $h .= "0000000\0";
+ $h .= "0000000\0";
+ $h .= "00000000000\0";
+ $h .= "00000000000\0";
+ $h .= str_repeat(' ', 8);
+ $h .= '2';
+ $h .= str_pad($target, 100, "\0");
+ $h .= "ustar\0" . "00";
+ $h = str_pad($h, 512, "\0");
+
+ $sum = 0;
+ for ($i = 0; $i < 512; $i++) {
+ $sum += ord($h[$i]);
+ }
+ return substr_replace($h, sprintf("%06o\0 ", $sum), 148, 8);
+}
+
+$tar = __DIR__ . '/gh21986.tar';
+file_put_contents(
+ $tar,
+ tar_symlink('a.txt', 'b.txt') .
+ tar_symlink('b.txt', 'a.txt') .
+ str_repeat("\0", 1024)
+);
+
+$phar = new PharData($tar);
+var_dump($phar['a.txt']->getContent());
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/gh21986.tar');
+?>
+--EXPECT--
+string(0) ""
diff --git a/ext/phar/util.c b/ext/phar/util.c
index fe177f96444..e2d1076921f 100644
--- a/ext/phar/util.c
+++ b/ext/phar/util.c
@@ -64,24 +64,34 @@ phar_entry_info *phar_get_link_source(phar_entry_info *entry) /* {{{ */
{
phar_entry_info *link_entry;
char *link;
+ uint32_t depth = 0, max_depth;
if (!entry->link) {
return entry;
}
- link = phar_get_link_location(entry);
- if (NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), entry->link, strlen(entry->link))) ||
- NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), link, strlen(link)))) {
- if (link != entry->link) {
- efree(link);
+ max_depth = zend_hash_num_elements(&(entry->phar->manifest));
+
+ while (entry->link) {
+ if (UNEXPECTED(++depth > max_depth)) {
+ return NULL;
}
- return phar_get_link_source(link_entry);
- } else {
- if (link != entry->link) {
- efree(link);
+ link = phar_get_link_location(entry);
+
+ if (NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), entry->link, strlen(entry->link))) ||
+ NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), link, strlen(link)))) {
+ if (link != entry->link) {
+ efree(link);
+ }
+ entry = link_entry;
+ } else {
+ if (link != entry->link) {
+ efree(link);
+ }
+ return NULL;
}
- return NULL;
}
+ return entry;
}
/* }}} */