Skip to content

Fix GH-17137: Segmentation fault ext/phar/phar.c #17150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ext/phar/phar.c
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,7 @@ int phar_create_or_parse_filename(char *fname, size_t fname_len, char *alias, si
}
}

ZEND_ASSERT(!mydata->is_persistent);
mydata->alias = alias ? estrndup(alias, alias_len) : estrndup(mydata->fname, fname_len);
mydata->alias_len = alias ? alias_len : fname_len;
}
Expand Down
53 changes: 31 additions & 22 deletions ext/phar/phar_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2112,9 +2112,8 @@ static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext) /*
efree(newname);

if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, newpath, phar->fname_len))) {
efree(oldpath);
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);
return NULL;
goto err_oldpath;
}

if (NULL != (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), newpath, phar->fname_len))) {
Expand All @@ -2126,41 +2125,42 @@ static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext) /*
pphar->flags = phar->flags;
pphar->fp = phar->fp;
phar->fp = NULL;
/* FIX: GH-10755 Double-free issue caught by ASAN check */
pphar->alias = phar->alias; /* Transfer alias to pphar to */
phar->alias = NULL; /* avoid being free'd twice */
/* The alias is not owned by the phar, so set it to NULL to avoid freeing it. */
phar->alias = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also need phar->alias_len = 0 here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary because the phar will be freed due to it being the temporary object created by the caller. The free code does not care about alias_len

phar_destroy_phar_data(phar);
*sphar = NULL;
phar = pphar;
/* NOTE: this phar is now reused, so the refcount must be increased. */
phar->refcount++;
newpath = oldpath;
goto its_ok;
}
}

efree(oldpath);
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);
return NULL;
goto err_oldpath;
}
its_ok:
if (SUCCESS == php_stream_stat_path(newpath, &ssb)) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "phar \"%s\" exists and must be unlinked prior to conversion", newpath);
efree(oldpath);
return NULL;
goto err_reused_oldpath;
}
if (!phar->is_data) {
if (SUCCESS != phar_detect_phar_fname_ext(newpath, phar->fname_len, (const char **) &(phar->ext), &ext_len, 1, 1, 1)) {
efree(oldpath);
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "phar \"%s\" has invalid extension %s", phar->fname, ext);
return NULL;
goto err_reused_oldpath;
}
phar->ext_len = ext_len;

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

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

phar->alias = NULL;
phar->alias_len = 0;
/* See comment in other branch. */
if (phar != pphar) {
phar->alias = NULL;
phar->alias_len = 0;
}
}

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

phar_flush(phar, 0, 0, 1, &error);
Expand All @@ -2193,8 +2194,7 @@ static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext) /*
*sphar = NULL;
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "%s", error);
efree(error);
efree(oldpath);
return NULL;
goto err_oldpath;
}

efree(oldpath);
Expand All @@ -2217,6 +2217,15 @@ static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext) /*
zend_call_known_instance_method_with_1_params(ce->constructor, Z_OBJ(ret), NULL, &arg1);
zval_ptr_dtor(&arg1);
return Z_OBJ(ret);

err_reused_oldpath:
if (pphar == phar) {
/* NOTE: we know it's not the last reference because the phar is reused. */
phar->refcount--;
}
err_oldpath:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment that the fallthrough is intentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually don't do this when I write goto-style error-handling code, but perhaps I should. I'll add it here.

efree(oldpath);
return NULL;
}
/* }}} */

Expand Down Expand Up @@ -2747,7 +2756,7 @@ PHP_METHOD(Phar, setAlias)
old_temp = phar_obj->archive->is_temporary_alias;

if (alias_len) {
phar_obj->archive->alias = estrndup(alias, alias_len);
phar_obj->archive->alias = pestrndup(alias, alias_len, phar_obj->archive->is_persistent);
} else {
phar_obj->archive->alias = NULL;
}
Expand Down
31 changes: 31 additions & 0 deletions ext/phar/tests/gh17137.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
GH-17137 (Segmentation fault ext/phar/phar.c)
--EXTENSIONS--
phar
--INI--
phar.readonly=0
--FILE--
<?php
$file = __DIR__ . DIRECTORY_SEPARATOR . 'gh17137.phar';
$phar = new Phar($file);
var_dump($phar, $phar->decompress());
echo "OK\n";
?>
--EXPECT--
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expecting certain object ID might be too specific; maybe use EXPECTF and %d instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair

object(Phar)#1 (3) {
["pathName":"SplFileInfo":private]=>
string(0) ""
["glob":"DirectoryIterator":private]=>
bool(false)
["subPathName":"RecursiveDirectoryIterator":private]=>
string(0) ""
}
object(Phar)#2 (3) {
["pathName":"SplFileInfo":private]=>
string(0) ""
["glob":"DirectoryIterator":private]=>
bool(false)
["subPathName":"RecursiveDirectoryIterator":private]=>
string(0) ""
}
OK
Loading