Skip to content
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
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ PHP NEWS
- Phar:
. Fixed bug GH-24740 (Oversized tar metadata exhausts the memory limit).
(Lazizbek Ergashev)
. Fixed bug GH-8469 (Phar Class decompressFiles() bad in PHP 7.4 and 8.x
branches). (Raphael Paez)

- Phpdbg:
. Fix wrong command names. (ndossche, Weilin Du)
Expand Down
47 changes: 44 additions & 3 deletions ext/phar/phar_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -3173,6 +3173,30 @@ static int phar_set_compression(zval *zv, void *argument) /* {{{ */
}
/* }}} */

typedef struct {
uint32_t compress;
char *error;
} phar_compression_args;

static int phar_decompress_for_compression_change(zval *zv, void *argument) /* {{{ */
{
phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv);
phar_compression_args *args = (phar_compression_args *)argument;
uint32_t current = entry->flags & PHAR_ENT_COMPRESSION_MASK;

if (entry->is_deleted || entry->is_dir || !current || current == args->compress) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the fix complete?
It seems that the early return will break the case where recompressing with the same algorithm can still corrupt the phar file?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that the early return will break the case where recompressing with the same algorithm can still corrupt the phar file?

Oh shit I didn't consider this. Nice catch.

I suppose this follow-up diff will work?

--- a/ext/phar/phar_object.c
+++ b/ext/phar/phar_object.c
@@ -3161,7 +3161,7 @@
 	phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv);
 	uint32_t compress = *(uint32_t *)argument;
 
-	if (entry->is_deleted) {
+	if (entry->is_deleted || (entry->flags & PHAR_ENT_COMPRESSION_MASK) == compress) {
 		return ZEND_HASH_APPLY_KEEP;
 	}
 

If you think its fine I will commit this directly. Sorry for missing this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that would work,yes

return ZEND_HASH_APPLY_KEEP;
}

/* phar_flush() would otherwise copy the old compressed bytes as the new contents */
if (FAILURE == phar_open_entry_fp(entry, &args->error, 0)) {
return ZEND_HASH_APPLY_STOP;
}

return ZEND_HASH_APPLY_KEEP;
}
/* }}} */

static int phar_test_compression(zval *zv, void *argument) /* {{{ */
{
phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv);
Expand All @@ -3197,9 +3221,18 @@ static int phar_test_compression(zval *zv, void *argument) /* {{{ */
}
/* }}} */

static void pharobj_set_compression(HashTable *manifest, uint32_t compress) /* {{{ */
static zend_result pharobj_set_compression(HashTable *manifest, uint32_t compress, char **error) /* {{{ */
{
phar_compression_args args = { .compress = compress, .error = NULL };

zend_hash_apply_with_argument(manifest, phar_decompress_for_compression_change, &args);
if (args.error) {
*error = args.error;
return FAILURE;
}

zend_hash_apply_with_argument(manifest, phar_set_compression, &compress);
return SUCCESS;
}
/* }}} */

Expand Down Expand Up @@ -3390,7 +3423,11 @@ PHP_METHOD(Phar, compressFiles)
zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname);
RETURN_THROWS();
}
pharobj_set_compression(&phar_obj->archive->manifest, flags);
if (FAILURE == pharobj_set_compression(&phar_obj->archive->manifest, flags, &error)) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "%s", error);
efree(error);
RETURN_THROWS();
}
phar_obj->archive->is_modified = 1;
phar_flush(phar_obj->archive, &error);

Expand Down Expand Up @@ -3432,7 +3469,11 @@ PHP_METHOD(Phar, decompressFiles)
zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname);
RETURN_THROWS();
}
pharobj_set_compression(&phar_obj->archive->manifest, PHAR_ENT_COMPRESSED_NONE);
if (FAILURE == pharobj_set_compression(&phar_obj->archive->manifest, PHAR_ENT_COMPRESSED_NONE, &error)) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "%s", error);
efree(error);
RETURN_THROWS();
}
}

phar_obj->archive->is_modified = 1;
Expand Down
59 changes: 59 additions & 0 deletions ext/phar/tests/gh8469.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
--TEST--
GH-8469 (Phar::decompressFiles() and Phar::compressFiles() keep the old compressed contents)
--EXTENSIONS--
phar
zlib
bz2
--INI--
phar.require_hash=0
phar.readonly=0
--FILE--
<?php
$contents = str_repeat('four score and seven years ago ', 10);

foreach (['phar', 'phar.zip'] as $ext) {
$fname = __DIR__ . "/gh8469.$ext";
$phar = new Phar($fname);
$phar['a.txt'] = $contents;
$phar['b.txt'] = 'our fathers';
$phar->addEmptyDir('dir');

$phar->compressFiles(Phar::GZ);
$phar->compressFiles(Phar::BZ2);
var_dump($phar['a.txt']->isCompressed(Phar::BZ2));
var_dump($phar['a.txt']->getContent() === $contents);

var_dump($phar->decompressFiles());
var_dump($phar['a.txt']->isCompressed());
var_dump($phar['a.txt']->getContent() === $contents);
var_dump($phar['b.txt']->getContent());
unset($phar);

$phar = new Phar($fname);
var_dump($phar['a.txt']->getContent() === $contents);
var_dump(file_get_contents("phar://$fname/b.txt"));
unset($phar);
}
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/gh8469.phar');
@unlink(__DIR__ . '/gh8469.phar.zip');
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(false)
bool(true)
string(11) "our fathers"
bool(true)
string(11) "our fathers"
bool(true)
bool(true)
bool(true)
bool(false)
bool(true)
string(11) "our fathers"
bool(true)
string(11) "our fathers"
Loading