Skip to content

Commit 142f85e

Browse files
committed
Fix phpGH-17137: Segmentation fault ext/phar/phar.c
Commit edae243 attempted to fix a leak and double free, but didn't properly understand what was going on, causing a reference count mistake and subsequent segfault in this case. The first mistake of that commit is that the reference count should've been increased because we're reusing a phar object. The error handling path should've gotten changed instead to undo this refcount increase instead of not refcounting at all (root cause of this bug). The second mistake is that the alias isn't supposed to be transferred or whatever, that just doesn't make sense. The reason the test bug69958.phpt originally leaked is because in the non-reuse case we borrowed the alias and otherwise we own the alias. If we own the alias the alias information shouldn't get deleted anyway as that would desync the alias map. Fixing these will reveal a third issue in which the alias memory is not always properly in sync with the persistence-ness of the phar, fix this as well. Closes phpGH-17150.
1 parent aafa6ea commit 142f85e

File tree

4 files changed

+67
-22
lines changed

4 files changed

+67
-22
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ PHP NEWS
2525
. Fixed bug GH-17158 (pg_fetch_result Shows Incorrect ArgumentCountError
2626
Message when Called With 1 Argument). (nielsdos)
2727

28+
- Phar:
29+
. Fixed bug GH-17137 (Segmentation fault ext/phar/phar.c). (nielsdos)
30+
2831
- SimpleXML:
2932
. Fixed bug GH-17040 (SimpleXML's unset can break DOM objects). (nielsdos)
3033
. Fixed bug GH-17153 (SimpleXML crash when using autovivification on

ext/phar/phar.c

+1
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,7 @@ int phar_create_or_parse_filename(char *fname, size_t fname_len, char *alias, si
15041504
}
15051505
}
15061506

1507+
ZEND_ASSERT(!mydata->is_persistent);
15071508
mydata->alias = alias ? estrndup(alias, alias_len) : estrndup(mydata->fname, fname_len);
15081509
mydata->alias_len = alias ? alias_len : fname_len;
15091510
}

ext/phar/phar_object.c

+32-22
Original file line numberDiff line numberDiff line change
@@ -2112,9 +2112,8 @@ static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext) /*
21122112
efree(newname);
21132113

21142114
if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, newpath, phar->fname_len))) {
2115-
efree(oldpath);
21162115
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to add newly converted phar \"%s\" to the list of phars, new phar name is in phar.cache_list", phar->fname);
2117-
return NULL;
2116+
goto err_oldpath;
21182117
}
21192118

21202119
if (NULL != (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), newpath, phar->fname_len))) {
@@ -2126,41 +2125,42 @@ static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext) /*
21262125
pphar->flags = phar->flags;
21272126
pphar->fp = phar->fp;
21282127
phar->fp = NULL;
2129-
/* FIX: GH-10755 Double-free issue caught by ASAN check */
2130-
pphar->alias = phar->alias; /* Transfer alias to pphar to */
2131-
phar->alias = NULL; /* avoid being free'd twice */
2128+
/* The alias is not owned by the phar, so set it to NULL to avoid freeing it. */
2129+
phar->alias = NULL;
21322130
phar_destroy_phar_data(phar);
21332131
*sphar = NULL;
21342132
phar = pphar;
2133+
/* NOTE: this phar is now reused, so the refcount must be increased. */
2134+
phar->refcount++;
21352135
newpath = oldpath;
21362136
goto its_ok;
21372137
}
21382138
}
21392139

2140-
efree(oldpath);
21412140
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to add newly converted phar \"%s\" to the list of phars, a phar with that name already exists", phar->fname);
2142-
return NULL;
2141+
goto err_oldpath;
21432142
}
21442143
its_ok:
21452144
if (SUCCESS == php_stream_stat_path(newpath, &ssb)) {
21462145
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "phar \"%s\" exists and must be unlinked prior to conversion", newpath);
2147-
efree(oldpath);
2148-
return NULL;
2146+
goto err_reused_oldpath;
21492147
}
21502148
if (!phar->is_data) {
21512149
if (SUCCESS != phar_detect_phar_fname_ext(newpath, phar->fname_len, (const char **) &(phar->ext), &ext_len, 1, 1, 1)) {
2152-
efree(oldpath);
21532150
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "phar \"%s\" has invalid extension %s", phar->fname, ext);
2154-
return NULL;
2151+
goto err_reused_oldpath;
21552152
}
21562153
phar->ext_len = ext_len;
21572154

2158-
if (phar->alias) {
2155+
/* If we are reusing a phar, then the aliases should be already set up correctly,
2156+
* and so we should not clear out the alias information.
2157+
* This would also leak memory because, unlike the non-reuse path, we actually own the alias memory. */
2158+
if (phar->alias && phar != pphar) {
21592159
if (phar->is_temporary_alias) {
21602160
phar->alias = NULL;
21612161
phar->alias_len = 0;
21622162
} else {
2163-
phar->alias = estrndup(newpath, strlen(newpath));
2163+
phar->alias = pestrndup(newpath, strlen(newpath), phar->is_persistent);
21642164
phar->alias_len = strlen(newpath);
21652165
phar->is_temporary_alias = 1;
21662166
zend_hash_str_update_ptr(&(PHAR_G(phar_alias_map)), newpath, phar->fname_len, phar);
@@ -2170,20 +2170,21 @@ static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext) /*
21702170
} else {
21712171

21722172
if (SUCCESS != phar_detect_phar_fname_ext(newpath, phar->fname_len, (const char **) &(phar->ext), &ext_len, 0, 1, 1)) {
2173-
efree(oldpath);
21742173
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "data phar \"%s\" has invalid extension %s", phar->fname, ext);
2175-
return NULL;
2174+
goto err_reused_oldpath;
21762175
}
21772176
phar->ext_len = ext_len;
21782177

2179-
phar->alias = NULL;
2180-
phar->alias_len = 0;
2178+
/* See comment in other branch. */
2179+
if (phar != pphar) {
2180+
phar->alias = NULL;
2181+
phar->alias_len = 0;
2182+
}
21812183
}
21822184

21832185
if ((!pphar || phar == pphar) && NULL == zend_hash_str_update_ptr(&(PHAR_G(phar_fname_map)), newpath, phar->fname_len, phar)) {
2184-
efree(oldpath);
21852186
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to add newly converted phar \"%s\" to the list of phars", phar->fname);
2186-
return NULL;
2187+
goto err_oldpath;
21872188
}
21882189

21892190
phar_flush(phar, 0, 0, 1, &error);
@@ -2193,8 +2194,7 @@ static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext) /*
21932194
*sphar = NULL;
21942195
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "%s", error);
21952196
efree(error);
2196-
efree(oldpath);
2197-
return NULL;
2197+
goto err_oldpath;
21982198
}
21992199

22002200
efree(oldpath);
@@ -2217,6 +2217,16 @@ static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext) /*
22172217
zend_call_known_instance_method_with_1_params(ce->constructor, Z_OBJ(ret), NULL, &arg1);
22182218
zval_ptr_dtor(&arg1);
22192219
return Z_OBJ(ret);
2220+
2221+
err_reused_oldpath:
2222+
if (pphar == phar) {
2223+
/* NOTE: we know it's not the last reference because the phar is reused. */
2224+
phar->refcount--;
2225+
}
2226+
/* fallthrough */
2227+
err_oldpath:
2228+
efree(oldpath);
2229+
return NULL;
22202230
}
22212231
/* }}} */
22222232

@@ -2747,7 +2757,7 @@ PHP_METHOD(Phar, setAlias)
27472757
old_temp = phar_obj->archive->is_temporary_alias;
27482758

27492759
if (alias_len) {
2750-
phar_obj->archive->alias = estrndup(alias, alias_len);
2760+
phar_obj->archive->alias = pestrndup(alias, alias_len, phar_obj->archive->is_persistent);
27512761
} else {
27522762
phar_obj->archive->alias = NULL;
27532763
}

ext/phar/tests/gh17137.phpt

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
GH-17137 (Segmentation fault ext/phar/phar.c)
3+
--EXTENSIONS--
4+
phar
5+
--INI--
6+
phar.readonly=0
7+
--FILE--
8+
<?php
9+
$file = __DIR__ . DIRECTORY_SEPARATOR . 'gh17137.phar';
10+
$phar = new Phar($file);
11+
var_dump($phar, $phar->decompress());
12+
echo "OK\n";
13+
?>
14+
--EXPECTF--
15+
object(Phar)#%d (3) {
16+
["pathName":"SplFileInfo":private]=>
17+
string(0) ""
18+
["glob":"DirectoryIterator":private]=>
19+
bool(false)
20+
["subPathName":"RecursiveDirectoryIterator":private]=>
21+
string(0) ""
22+
}
23+
object(Phar)#%d (3) {
24+
["pathName":"SplFileInfo":private]=>
25+
string(0) ""
26+
["glob":"DirectoryIterator":private]=>
27+
bool(false)
28+
["subPathName":"RecursiveDirectoryIterator":private]=>
29+
string(0) ""
30+
}
31+
OK

0 commit comments

Comments
 (0)