Skip to content
Open
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
111 changes: 96 additions & 15 deletions ext/phar/phar_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1640,6 +1640,34 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */
return ZEND_HASH_APPLY_KEEP;
}

/* read filters change the length, so filtered streams are only checked after the copy */
if (!fp->readfilters.head && php_stream_stat(fp, &ssb) != -1
&& (int64_t) ssb.sb.st_size - php_stream_tell(fp) > (int64_t) UINT32_MAX) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s is too large to be stored in a phar archive", str_key);

if (save) {
efree(save);
}

if (opened) {
zend_string_release_ex(opened, 0);
}

if (temp) {
efree(temp);
}

if (close_fp) {
php_stream_close(fp);
}

return ZEND_HASH_APPLY_STOP;
}

if (p_obj->fp == phar_obj->archive->ufp) {
php_stream_seek(p_obj->fp, 0, SEEK_END);
}

if (!(data = phar_get_or_create_entry_data(phar_obj->archive->fname, phar_obj->archive->fname_len, str_key, str_key_len, "w+b", 0, &error, 1))) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s cannot be created: %s", str_key, error);
efree(error);
Expand Down Expand Up @@ -1676,6 +1704,29 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */
data->internal_file->offset_abs = data->internal_file->offset = php_stream_tell(p_obj->fp);
data->fp = NULL;
php_stream_copy_to_stream_ex(fp, p_obj->fp, PHP_STREAM_COPY_ALL, &contents_len);
if (contents_len > UINT32_MAX) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s is too large to be stored in a phar archive", str_key);
data->internal_file->is_deleted = 1;
phar_entry_delref(data);

if (save) {
efree(save);
}

if (opened) {
zend_string_release_ex(opened, 0);
}

if (temp) {
efree(temp);
}

if (close_fp) {
php_stream_close(fp);
}

return ZEND_HASH_APPLY_STOP;
}
data->internal_file->uncompressed_filesize = data->internal_file->compressed_filesize =
php_stream_tell(p_obj->fp) - data->internal_file->offset;
if (php_stream_stat(fp, &ssb) != -1) {
Expand Down Expand Up @@ -1719,7 +1770,7 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */
PHP_METHOD(Phar, buildFromDirectory)
{
char *error;
bool apply_reg = 0;
bool apply_reg = 0, pass_fp_is_new;
zval arg, arg2, iter, iteriter, regexiter;
struct _phar_t pass;
zend_string *dir, *regex = NULL;
Expand Down Expand Up @@ -1793,22 +1844,30 @@ PHP_METHOD(Phar, buildFromDirectory)
pass.base = dir;
pass.count = 0;
pass.ret = return_value;
pass.fp = php_stream_fopen_tmpfile();
if (pass.fp == NULL) {
zval_ptr_dtor(&iteriter);
if (apply_reg) {
zval_ptr_dtor(&regexiter);
pass.fp = phar_obj->archive->ufp;
pass_fp_is_new = pass.fp == NULL;
if (pass_fp_is_new) {
pass.fp = php_stream_fopen_tmpfile();
if (pass.fp == NULL) {
zval_ptr_dtor(&iteriter);
if (apply_reg) {
zval_ptr_dtor(&regexiter);
}
zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" unable to create temporary file", phar_obj->archive->fname);
RETURN_THROWS();
}
zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" unable to create temporary file", phar_obj->archive->fname);
RETURN_THROWS();
} else {
php_stream_seek(pass.fp, 0, SEEK_END);
}

if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) {
zval_ptr_dtor(&iteriter);
if (apply_reg) {
zval_ptr_dtor(&regexiter);
}
php_stream_close(pass.fp);
if (pass_fp_is_new) {
php_stream_close(pass.fp);
}
zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname);
RETURN_THROWS();
}
Expand All @@ -1833,7 +1892,7 @@ PHP_METHOD(Phar, buildFromDirectory)
if (apply_reg) {
zval_ptr_dtor(&regexiter);
}
php_stream_close(pass.fp);
phar_obj->archive->ufp = pass.fp;
}
}
/* }}} */
Expand Down Expand Up @@ -1878,10 +1937,15 @@ PHP_METHOD(Phar, buildFromIterator)
pass.base = base;
pass.ret = return_value;
pass.count = 0;
pass.fp = php_stream_fopen_tmpfile();
pass.fp = phar_obj->archive->ufp;
if (pass.fp == NULL) {
zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\": unable to create temporary file", phar_obj->archive->fname);
RETURN_THROWS();
pass.fp = php_stream_fopen_tmpfile();
if (pass.fp == NULL) {
zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\": unable to create temporary file", phar_obj->archive->fname);
RETURN_THROWS();
}
} else {
php_stream_seek(pass.fp, 0, SEEK_END);
}

if (SUCCESS == spl_iterator_apply(obj, (spl_iterator_apply_func_t) phar_build, (void *) &pass)) {
Expand All @@ -1892,7 +1956,7 @@ PHP_METHOD(Phar, buildFromIterator)
efree(error);
}
} else {
php_stream_close(pass.fp);
phar_obj->archive->ufp = pass.fp;
}
}
/* }}} */
Expand Down Expand Up @@ -3654,6 +3718,17 @@ static void phar_add_file(phar_archive_data **pphar, zend_string *file_name, con
}
#endif

if (zresource) {
php_stream_from_zval_no_verify(contents_file, zresource);
}

if ((content && ZSTR_LEN(content) > UINT32_MAX)
|| (contents_file && !contents_file->readfilters.head && php_stream_stat(contents_file, &ssb) != -1
&& (int64_t) ssb.sb.st_size - php_stream_tell(contents_file) > (int64_t) UINT32_MAX)) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s is too large to be stored in a phar archive", filename);
goto finish;
}

if (!(data = phar_get_or_create_entry_data((*pphar)->fname, (*pphar)->fname_len, filename, filename_len, "w+b", 0, &error, 1))) {
if (error) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist and cannot be created: %s", filename, error);
Expand All @@ -3678,13 +3753,19 @@ static void phar_add_file(phar_archive_data **pphar, zend_string *file_name, con
goto finish;
}
} else {
if (!(php_stream_from_zval_no_verify(contents_file, zresource))) {
if (!contents_file) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s could not be written to", filename);
phar_entry_delref(data);
goto finish;
}
php_stream_copy_to_stream_ex(contents_file, data->fp, PHP_STREAM_COPY_ALL, &contents_len);
}
if (contents_len > UINT32_MAX) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s is too large to be stored in a phar archive", filename);
data->internal_file->is_deleted = 1;
phar_entry_delref(data);
goto finish;
}
data->internal_file->compressed_filesize = data->internal_file->uncompressed_filesize = contents_len;
}

Expand Down
5 changes: 5 additions & 0 deletions ext/phar/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,11 @@ static ssize_t phar_stream_write(php_stream *stream, const char *buf, size_t cou
{
phar_entry_data *data = (phar_entry_data *) stream->abstract;

if ((uint64_t) data->position + count > UINT32_MAX) {
php_error_docref(NULL, E_WARNING, "phar error: Could not write %d characters to \"%s\" in phar \"%s\", entry is too large", (int) count, data->internal_file->filename, data->phar->fname);
return -1;
}

php_stream_seek(data->fp, data->position + data->zero, SEEK_SET);
if (count != php_stream_write(data->fp, buf, count)) {
php_stream_wrapper_log_error(stream->wrapper, stream->flags, "phar error: Could not write %d characters to \"%s\" in phar \"%s\"", (int) count, data->internal_file->filename, data->phar->fname);
Expand Down
127 changes: 127 additions & 0 deletions ext/phar/tests/gh25053.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
--TEST--
GH-25053 (Phar truncates entries of 4 GiB or more without error/warning)
--EXTENSIONS--
phar
--SKIPIF--
<?php
if (!getenv('RUN_RESOURCE_HEAVY_TESTS')) die('skip resource-heavy test');
if (PHP_INT_SIZE != 8) die('skip this test is for 64bit platforms only');
if (getenv('SKIP_SLOW_TESTS')) die('skip slow tests excluded by request');
?>
--ENV--
TEST_TIMEOUT=600
--INI--
phar.readonly=0
--FILE--
<?php
$src = __DIR__ . '/gh25053.big';
$f = fopen($src, 'w');
ftruncate($f, 2**32 + 1);
fclose($f);

$tar = new PharData(__DIR__ . '/gh25053-1.tar');
try {
$tar->addFile($src, 'big');
} catch (BadMethodCallException $e) {
echo $e->getMessage(), "\n";
}
var_dump(isset($tar['big']));

$tar = new PharData(__DIR__ . '/gh25053-2.tar');
try {
$tar->buildFromIterator(new ArrayIterator(['big' => $src]));
} catch (BadMethodCallException $e) {
echo $e->getMessage(), "\n";
}
var_dump(isset($tar['big']));

$tar = new PharData(__DIR__ . '/gh25053-3.tar');
try {
$tar->buildFromIterator(new ArrayIterator(['big' => fopen($src, 'rb')]));
} catch (BadMethodCallException $e) {
echo $e->getMessage(), "\n";
}
var_dump(isset($tar['big']));

$tar = new PharData(__DIR__ . '/gh25053-iterator-retry.tar');
$small = fopen('php://temp', 'w+b');
fwrite($small, 'small');
rewind($small);
try {
$tar->buildFromIterator(new ArrayIterator(['small' => $small, 'big' => $src]));
} catch (BadMethodCallException $e) {
echo $e->getMessage(), "\n";
}
var_dump($tar['small']->getContent());
$next = fopen('php://temp', 'w+b');
fwrite($next, 'next');
rewind($next);
$tar->buildFromIterator(new ArrayIterator(['next' => $next]));
var_dump($tar['small']->getContent());
var_dump($tar['next']->getContent());

$tar = new PharData(__DIR__ . '/gh25053-iterator-read-retry.tar');
$first = fopen('php://temp', 'w+b');
fwrite($first, 'first');
rewind($first);
$second = fopen('php://temp', 'w+b');
fwrite($second, 'second');
rewind($second);
try {
$tar->buildFromIterator(new ArrayIterator(['first' => $first, 'second' => $second, 'big' => $src]));
} catch (BadMethodCallException $e) {
echo $e->getMessage(), "\n";
}
$third = fopen('php://temp', 'w+b');
fwrite($third, 'third');
rewind($third);
$iterator = (function () use ($tar, $third) {
var_dump($tar['first']->getContent());
yield 'third' => $third;
})();
$tar->buildFromIterator($iterator);
var_dump($tar['first']->getContent());
var_dump($tar['second']->getContent());
var_dump($tar['third']->getContent());

$phar = new Phar(__DIR__ . '/gh25053-4.phar');
$phar->addFromString('small', 'x');
var_dump(copy($src, 'phar://' . __DIR__ . '/gh25053-4.phar/big'));

$tar = new PharData(__DIR__ . '/gh25053-5.tar');
$fp = fopen($src, 'rb');
fseek($fp, 2**32);
$tar->buildFromIterator(new ArrayIterator(['tail' => $fp]));
var_dump($tar['tail']->getSize());
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/gh25053.big');
@unlink(__DIR__ . '/gh25053-1.tar');
@unlink(__DIR__ . '/gh25053-2.tar');
@unlink(__DIR__ . '/gh25053-3.tar');
@unlink(__DIR__ . '/gh25053-iterator-retry.tar');
@unlink(__DIR__ . '/gh25053-iterator-read-retry.tar');
@unlink(__DIR__ . '/gh25053-4.phar');
@unlink(__DIR__ . '/gh25053-5.tar');
?>
--EXPECTF--
Entry big is too large to be stored in a phar archive
bool(false)
Entry big is too large to be stored in a phar archive
bool(false)
Entry big is too large to be stored in a phar archive
bool(false)
Entry big is too large to be stored in a phar archive
string(5) "small"
string(5) "small"
string(4) "next"
Entry big is too large to be stored in a phar archive
string(5) "first"
string(5) "first"
string(6) "second"
string(5) "third"

Warning: copy(): phar error: Could not write %d characters to "big" in phar "%sgh25053-4.phar", entry is too large in %s on line %d
bool(false)
int(1)
68 changes: 68 additions & 0 deletions ext/phar/tests/gh25053_filtered_stream.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
--TEST--
GH-25053: Phar size checks use filtered content rather than the underlying file size
--EXTENSIONS--
phar
zlib
--SKIPIF--
<?php
if (!getenv('RUN_RESOURCE_HEAVY_TESTS')) die('skip resource-heavy test');
if (PHP_INT_SIZE != 8) die('skip this test is for 64bit platforms only');
if (getenv('SKIP_SLOW_TESTS')) die('skip slow tests excluded by request');
?>
--ENV--
TEST_TIMEOUT=600
--INI--
phar.readonly=0
--FILE--
<?php
$src = __DIR__ . '/gh25053_filtered_stream.bin';
$size = 2**32 + 1;
$fp = fopen($src, 'w+b');
ftruncate($fp, $size);
fclose($fp);

foreach (['buildFromIterator', 'addFile'] as $method) {
echo $method, "\n";
$path = __DIR__ . '/gh25053_filtered_stream-' . $method . '.tar';
$phar = new PharData($path);
try {
if ($method === 'buildFromIterator') {
$fp = fopen($src, 'rb');
stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_READ);
try {
$phar->buildFromIterator(new ArrayIterator(['data' => $fp]));
} finally {
fclose($fp);
}
} else {
$phar->addFile('php://filter/read=zlib.deflate/resource=' . $src, 'data');
}

// Decode in chunks so the round trip does not allocate a 4 GiB string.
$fp = fopen('phar://' . $path . '/data', 'rb');
stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_READ);
$length = 0;
$allZero = true;
while (!feof($fp)) {
$chunk = fread($fp, 1024 * 1024);
$length += strlen($chunk);
$allZero = $allZero && strspn($chunk, "\0") === strlen($chunk);
}
fclose($fp);
var_dump($length === $size && $allZero);
} catch (BadMethodCallException $e) {
echo $e->getMessage(), "\n";
}
}
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/gh25053_filtered_stream.bin');
@unlink(__DIR__ . '/gh25053_filtered_stream-buildFromIterator.tar');
@unlink(__DIR__ . '/gh25053_filtered_stream-addFile.tar');
?>
--EXPECT--
buildFromIterator
bool(true)
addFile
bool(true)
Loading