Skip to content

Commit facb875

Browse files
committed
phar: cap decompression output against declared uncompressed_filesize
phar_open_entry_fp() decompressed entries by streaming compressed_filesize bytes through a filter into a tmpfile, then checked output size after the full copy. A crafted phar whose compressed data expanded beyond uncompressed_filesize could write large amounts to disk before the check ran. Replace the single php_stream_copy_to_stream_ex() call with an 8 KiB chunked loop that flushes the decompression filter and checks running output size after each chunk. Abort when output exceeds uncompressed_filesize.
1 parent f7eb5ef commit facb875

File tree

2 files changed

+46
-4
lines changed

2 files changed

+46
-4
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
phar decompression: compressed entry round-trips correctly
3+
--EXTENSIONS--
4+
phar
5+
zlib
6+
--INI--
7+
phar.readonly=0
8+
--FILE--
9+
<?php
10+
$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar';
11+
$pname = 'phar://' . $fname;
12+
13+
$phar = new Phar($fname);
14+
$phar['entry.txt'] = 'hello world';
15+
$phar['entry.txt']->compress(Phar::GZ);
16+
17+
echo file_get_contents($pname . '/entry.txt') . "\n";
18+
echo "no crash";
19+
?>
20+
--CLEAN--
21+
<?php
22+
@unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar');
23+
?>
24+
--EXPECT--
25+
hello world
26+
no crash

ext/phar/util.c

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -926,10 +926,21 @@ zend_result phar_open_entry_fp(phar_entry_info *entry, char **error, int follow_
926926
php_stream_seek(phar_get_entrypfp(entry), phar_get_fp_offset(entry), SEEK_SET);
927927

928928
if (entry->uncompressed_filesize) {
929-
if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_entrypfp(entry), ufp, entry->compressed_filesize, NULL)) {
930-
spprintf(error, 4096, "phar error: internal corruption of phar \"%s\" (actual filesize mismatch on file \"%s\")", phar->fname, entry->filename);
931-
php_stream_filter_remove(filter, 1);
932-
return FAILURE;
929+
size_t remaining = entry->compressed_filesize;
930+
while (remaining > 0) {
931+
size_t chunk = remaining < 8192 ? remaining : 8192;
932+
size_t n = 0;
933+
if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_entrypfp(entry), ufp, chunk, &n)) {
934+
goto decompression_error;
935+
}
936+
if (n == 0) {
937+
break;
938+
}
939+
remaining -= n;
940+
php_stream_filter_flush(filter, 0);
941+
if (php_stream_tell(ufp) - loc > (zend_off_t) entry->uncompressed_filesize) {
942+
goto decompression_error;
943+
}
933944
}
934945
}
935946

@@ -952,6 +963,11 @@ zend_result phar_open_entry_fp(phar_entry_info *entry, char **error, int follow_
952963
return FAILURE;
953964
}
954965
return SUCCESS;
966+
967+
decompression_error:
968+
spprintf(error, 4096, "phar error: internal corruption of phar \"%s\" (actual filesize mismatch on file \"%s\")", phar->fname, entry->filename);
969+
php_stream_filter_remove(filter, 1);
970+
return FAILURE;
955971
}
956972
/* }}} */
957973

0 commit comments

Comments
 (0)