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;
 }
 /* }}} */