From 8.2.33, without binary diffs From 92458605f7f88697973e1183b46296d4c9bf9c46 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 3 May 2026 19:26:31 +0200 Subject: [PATCH 3/5] Fix GHSA-vc5h-9ppw-p5f3: phar circular symlink crash Prevents infinite recursion in phar_get_link_source. (cherry picked from commit 2e0fa0a44441d74bf8cc4e1ce1c8af9cd4209f52) (cherry picked from commit c84ffef248fddba29bde47847bd4cd06761e0aab) --- .../tests/tar/files/circular_symlinks.tar | Bin 0 -> 10240 bytes .../tar/files/circular_symlinks_long.tar | Bin 0 -> 215040 bytes .../tests/tar/files/circular_symlinks_rho.tar | Bin 0 -> 10240 bytes .../ghsa-vc5h-9ppw-p5f3-symlink-circular.phpt | 27 ++++++++++ ext/phar/util.c | 51 ++++++++++++++---- 5 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 ext/phar/tests/tar/files/circular_symlinks.tar create mode 100644 ext/phar/tests/tar/files/circular_symlinks_long.tar create mode 100644 ext/phar/tests/tar/files/circular_symlinks_rho.tar create mode 100644 ext/phar/tests/tar/ghsa-vc5h-9ppw-p5f3-symlink-circular.phpt diff --git a/ext/phar/util.c b/ext/phar/util.c index 59362bbc72..d49ea9bfc5 100644 --- a/ext/phar/util.c +++ b/ext/phar/util.c @@ -57,30 +57,59 @@ static char *phar_get_link_location(phar_entry_info *entry) /* {{{ */ } /* }}} */ -phar_entry_info *phar_get_link_source(phar_entry_info *entry) /* {{{ */ +static phar_entry_info *phar_follow_one_link(phar_entry_info *entry) { phar_entry_info *link_entry; char *link; - 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); } - return phar_get_link_source(link_entry); - } else { - if (link != entry->link) { - efree(link); + return link_entry; + } + + if (link != entry->link) { + efree(link); + } + return NULL; +} + +phar_entry_info *phar_get_link_source(phar_entry_info *entry) +{ + phar_entry_info *slow, *fast; + + if (!entry->link) { + return entry; + } + + /* + * Use Floyd's cycle detection algorithm to follow the symlink chain without unbounded + * recursion. Each entry has at most one outgoing link, so if a cycle exists the fast pointer + * will eventually meet the slow one. Otherwise the fast pointer reaches the end first. + */ + slow = fast = entry; + while (1) { + fast = phar_follow_one_link(fast); + if (!fast || !fast->link) { + return fast; + } + fast = phar_follow_one_link(fast); + if (!fast || !fast->link) { + return fast; + } + + /* no need to check slow as it's always behind */ + slow = phar_follow_one_link(slow); + + if (slow == fast) { + /* circular symlink chain */ + return NULL; } - return NULL; } } -/* }}} */ /* retrieve a phar_entry_info's current file pointer for reading contents */ php_stream *phar_get_efp(phar_entry_info *entry, int follow_links) /* {{{ */ -- 2.55.0