Skip to content

Commit 2e0fa0a

Browse files
bukkailuuu1994
authored andcommitted
Fix GHSA-vc5h-9ppw-p5f3: phar circular symlink crash
Prevents infinite recursion in phar_get_link_source.
1 parent ab048bd commit 2e0fa0a

5 files changed

Lines changed: 67 additions & 11 deletions

File tree

10 KB
Binary file not shown.
210 KB
Binary file not shown.
10 KB
Binary file not shown.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
GHSA-vc5h-9ppw-p5f3 (circular symlinks in tar should not cause stack overflow)
3+
--CREDITS--
4+
Calvin Young - eWalker Consulting (HK) Limited
5+
Enoch Chow - Isomorph Cyber
6+
--EXTENSIONS--
7+
phar
8+
--FILE--
9+
<?php
10+
$base = dirname(__FILE__);
11+
12+
// simple 2-cycle
13+
$phar = new PharData($base . '/files/circular_symlinks.tar');
14+
var_dump($phar['file_a']->getContent() === '');
15+
16+
// rho-shaped cycle (tail leading into a loop)
17+
$phar = new PharData($base . '/files/circular_symlinks_rho.tar');
18+
var_dump($phar['file_a']->getContent() === '');
19+
20+
// long cycle (400 entries)
21+
$phar = new PharData($base . '/files/circular_symlinks_long.tar');
22+
var_dump($phar['link_0']->getContent() === '');
23+
?>
24+
--EXPECT--
25+
bool(true)
26+
bool(true)
27+
bool(true)

ext/phar/util.c

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,30 +57,59 @@ static char *phar_get_link_location(phar_entry_info *entry) /* {{{ */
5757
}
5858
/* }}} */
5959

60-
phar_entry_info *phar_get_link_source(phar_entry_info *entry) /* {{{ */
60+
static phar_entry_info *phar_follow_one_link(phar_entry_info *entry)
6161
{
6262
phar_entry_info *link_entry;
6363
char *link;
6464

65-
if (!entry->link) {
66-
return entry;
67-
}
68-
6965
link = phar_get_link_location(entry);
7066
if (NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), entry->link, strlen(entry->link))) ||
7167
NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), link, strlen(link)))) {
7268
if (link != entry->link) {
7369
efree(link);
7470
}
75-
return phar_get_link_source(link_entry);
76-
} else {
77-
if (link != entry->link) {
78-
efree(link);
71+
return link_entry;
72+
}
73+
74+
if (link != entry->link) {
75+
efree(link);
76+
}
77+
return NULL;
78+
}
79+
80+
phar_entry_info *phar_get_link_source(phar_entry_info *entry)
81+
{
82+
phar_entry_info *slow, *fast;
83+
84+
if (!entry->link) {
85+
return entry;
86+
}
87+
88+
/*
89+
* Use Floyd's cycle detection algorithm to follow the symlink chain without unbounded
90+
* recursion. Each entry has at most one outgoing link, so if a cycle exists the fast pointer
91+
* will eventually meet the slow one. Otherwise the fast pointer reaches the end first.
92+
*/
93+
slow = fast = entry;
94+
while (1) {
95+
fast = phar_follow_one_link(fast);
96+
if (!fast || !fast->link) {
97+
return fast;
98+
}
99+
fast = phar_follow_one_link(fast);
100+
if (!fast || !fast->link) {
101+
return fast;
102+
}
103+
104+
/* no need to check slow as it's always behind */
105+
slow = phar_follow_one_link(slow);
106+
107+
if (slow == fast) {
108+
/* circular symlink chain */
109+
return NULL;
79110
}
80-
return NULL;
81111
}
82112
}
83-
/* }}} */
84113

85114
/* retrieve a phar_entry_info's current file pointer for reading contents */
86115
php_stream *phar_get_efp(phar_entry_info *entry, int follow_links) /* {{{ */

0 commit comments

Comments
 (0)