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
23 changes: 23 additions & 0 deletions ext/bz2/bz2_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ typedef struct _php_bz2_filter_data {
char *outbuf;
size_t inbuf_len;
size_t outbuf_len;
size_t max_output;
size_t total_output;

enum strm_status status; /* Decompress option */
unsigned int small_footprint : 1; /* Decompress option */
Expand Down Expand Up @@ -137,6 +139,12 @@ static php_stream_filter_status_t php_bz2_decompress_filter(
if (data->strm.avail_out < data->outbuf_len) {
php_stream_bucket *out_bucket;
size_t bucketlen = data->outbuf_len - data->strm.avail_out;
data->total_output += bucketlen;
if (data->max_output && data->total_output > data->max_output) {
php_error_docref(NULL, E_NOTICE, "bzip2.decompress: decompressed output exceeded max_output");
php_stream_bucket_delref(bucket);
return PSFS_ERR_FATAL;
}
out_bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0);
php_stream_bucket_append(buckets_out, out_bucket);
data->strm.avail_out = data->outbuf_len;
Expand All @@ -160,6 +168,11 @@ static php_stream_filter_status_t php_bz2_decompress_filter(
if (data->strm.avail_out < data->outbuf_len) {
size_t bucketlen = data->outbuf_len - data->strm.avail_out;

data->total_output += bucketlen;
if (data->max_output && data->total_output > data->max_output) {
php_error_docref(NULL, E_NOTICE, "bzip2.decompress: decompressed output exceeded max_output");
return PSFS_ERR_FATAL;
}
bucket = php_stream_bucket_new(stream, estrndup(data->outbuf, bucketlen), bucketlen, 1, 0);
php_stream_bucket_append(buckets_out, bucket);
data->strm.avail_out = data->outbuf_len;
Expand Down Expand Up @@ -344,6 +357,16 @@ static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *fi
tmpzval = NULL;
}

if ((tmpzval = zend_hash_str_find_ind(ht, "max_output", sizeof("max_output")-1))) {
zend_long tmp = zval_get_long(tmpzval);
if (tmp <= 0) {
php_error_docref(NULL, E_WARNING, "Invalid parameter given for max_output (" ZEND_LONG_FMT ")", tmp);
} else {
data->max_output = (size_t)tmp;
}
tmpzval = NULL;
}

tmpzval = zend_hash_str_find_ind(ht, "small", sizeof("small")-1);
} else {
tmpzval = filterparams;
Expand Down
58 changes: 58 additions & 0 deletions ext/bz2/tests/bz2_filter_decompress_max_output.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
--TEST--
bzip2.decompress: max_output filter parameter
--EXTENSIONS--
bz2
--FILE--
<?php
$original = str_repeat('abcdefgh', 128); // 1024 bytes
$compressed = bzcompress($original);

echo "--- unbounded (no max_output) ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_WRITE);
fwrite($fp, $compressed);
rewind($fp);
var_dump(strlen(stream_get_contents($fp)));
fclose($fp);

echo "--- max_output above actual size ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_WRITE, ['max_output' => 2048]);
fwrite($fp, $compressed);
rewind($fp);
var_dump(strlen(stream_get_contents($fp)));
fclose($fp);

echo "--- max_output below actual size ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_WRITE, ['max_output' => 100]);
fwrite($fp, $compressed);
rewind($fp);
var_dump(strlen(stream_get_contents($fp)) <= 100);
fclose($fp);

echo "--- max_output = 0 (invalid) ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_WRITE, ['max_output' => 0]);
fclose($fp);

echo "--- max_output = -1 (invalid) ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_WRITE, ['max_output' => -1]);
fclose($fp);
?>
--EXPECTF--
--- unbounded (no max_output) ---
int(1024)
--- max_output above actual size ---
int(1024)
--- max_output below actual size ---

Notice: fwrite(): bzip2.decompress: decompressed output exceeded max_output in %s on line %d
bool(true)
--- max_output = 0 (invalid) ---

Warning: stream_filter_append(): Invalid parameter given for max_output (0) in %s on line %d
--- max_output = -1 (invalid) ---

Warning: stream_filter_append(): Invalid parameter given for max_output (-1) in %s on line %d
6 changes: 6 additions & 0 deletions ext/phar/tests/016.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,18 @@ var_dump(file_get_contents($pname . '/d'));
--CLEAN--
<?php unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar.php'); ?>
--EXPECTF--
Notice: file_get_contents(): zlib.inflate: decompressed output exceeded max_output in %s on line %d

Warning: file_get_contents(phar://%s/a): Failed to open stream: phar error: internal corruption of phar "%s" (actual filesize mismatch on file "a") in %s on line %d
bool(false)

Notice: file_get_contents(): zlib.inflate: decompressed output exceeded max_output in %s on line %d

Warning: file_get_contents(phar://%s/b): Failed to open stream: phar error: internal corruption of phar "%s" (actual filesize mismatch on file "b") in %s on line %d
bool(false)
string(1) "*"

Notice: file_get_contents(): zlib.inflate: decompressed output exceeded max_output in %s on line %d

Warning: file_get_contents(phar://%s/d): Failed to open stream: phar error: internal corruption of phar "%s" (actual filesize mismatch on file "d") in %s on line %d
bool(false)
26 changes: 26 additions & 0 deletions ext/phar/tests/gh-ss012-decomp-bounds.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
phar decompression: compressed entry round-trips correctly
--EXTENSIONS--
phar
zlib
--INI--
phar.readonly=0
--FILE--
<?php
$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar';
$pname = 'phar://' . $fname;

$phar = new Phar($fname);
$phar['entry.txt'] = 'hello world';
$phar['entry.txt']->compress(Phar::GZ);

echo file_get_contents($pname . '/entry.txt') . "\n";
echo "no crash";
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar');
?>
--EXPECT--
hello world
no crash
10 changes: 9 additions & 1 deletion ext/phar/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,15 @@ zend_result phar_open_entry_fp(phar_entry_info *entry, char **error, int follow_
ufp = phar_get_entrypufp(entry);

if ((filtername = phar_decompress_filter(entry, 0)) != NULL) {
filter = php_stream_filter_create(filtername, NULL, 0);
if (entry->uncompressed_filesize) {
zval filterparams;
array_init(&filterparams);
add_assoc_long(&filterparams, "max_output", (zend_long) entry->uncompressed_filesize);
filter = php_stream_filter_create(filtername, &filterparams, 0);
zval_ptr_dtor(&filterparams);
} else {
filter = php_stream_filter_create(filtername, NULL, 0);
}
} else {
filter = NULL;
}
Expand Down
58 changes: 58 additions & 0 deletions ext/zlib/tests/zlib_filter_inflate_max_output.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
--TEST--
zlib.inflate: max_output filter parameter
--EXTENSIONS--
zlib
--FILE--
<?php
$original = str_repeat('abcdefgh', 128); // 1024 bytes
$compressed = gzdeflate($original);

echo "--- unbounded (no max_output) ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_WRITE);
fwrite($fp, $compressed);
rewind($fp);
var_dump(strlen(stream_get_contents($fp)));
fclose($fp);

echo "--- max_output above actual size ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_WRITE, ['max_output' => 2048]);
fwrite($fp, $compressed);
rewind($fp);
var_dump(strlen(stream_get_contents($fp)));
fclose($fp);

echo "--- max_output below actual size ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_WRITE, ['max_output' => 100]);
fwrite($fp, $compressed);
rewind($fp);
var_dump(strlen(stream_get_contents($fp)) <= 100);
fclose($fp);

echo "--- max_output = 0 (invalid) ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_WRITE, ['max_output' => 0]);
fclose($fp);

echo "--- max_output = -1 (invalid) ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_WRITE, ['max_output' => -1]);
fclose($fp);
?>
--EXPECTF--
--- unbounded (no max_output) ---
int(1024)
--- max_output above actual size ---
int(1024)
--- max_output below actual size ---

Notice: fwrite(): zlib.inflate: decompressed output exceeded max_output in %s on line %d
bool(true)
--- max_output = 0 (invalid) ---

Warning: stream_filter_append(): Invalid parameter given for max_output (0) in %s on line %d
--- max_output = -1 (invalid) ---

Warning: stream_filter_append(): Invalid parameter given for max_output (-1) in %s on line %d
28 changes: 25 additions & 3 deletions ext/zlib/zlib_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ typedef struct _php_zlib_filter_data {
size_t inbuf_len;
unsigned char *outbuf;
size_t outbuf_len;
size_t max_output;
size_t total_output;
int persistent;
bool finished; /* for zlib.deflate: signals that no flush is pending */
} php_zlib_filter_data;
Expand Down Expand Up @@ -105,6 +107,12 @@ static php_stream_filter_status_t php_zlib_inflate_filter(
if (data->strm.avail_out < data->outbuf_len) {
php_stream_bucket *out_bucket;
size_t bucketlen = data->outbuf_len - data->strm.avail_out;
data->total_output += bucketlen;
if (data->max_output && data->total_output > data->max_output) {
php_error_docref(NULL, E_NOTICE, "zlib.inflate: decompressed output exceeded max_output");
php_stream_bucket_delref(bucket);
return PSFS_ERR_FATAL;
}
out_bucket = php_stream_bucket_new(
stream, estrndup((char *) data->outbuf, bucketlen), bucketlen, 1, 0);
php_stream_bucket_append(buckets_out, out_bucket);
Expand All @@ -126,6 +134,11 @@ static php_stream_filter_status_t php_zlib_inflate_filter(
if (data->strm.avail_out < data->outbuf_len) {
size_t bucketlen = data->outbuf_len - data->strm.avail_out;

data->total_output += bucketlen;
if (data->max_output && data->total_output > data->max_output) {
php_error_docref(NULL, E_NOTICE, "zlib.inflate: decompressed output exceeded max_output");
return PSFS_ERR_FATAL;
}
bucket = php_stream_bucket_new(
stream, estrndup((char *) data->outbuf, bucketlen), bucketlen, 1, 0);
php_stream_bucket_append(buckets_out, bucket);
Expand Down Expand Up @@ -319,11 +332,11 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f
if (strcasecmp(filtername, "zlib.inflate") == 0) {
int windowBits = -MAX_WBITS;

if (filterparams) {
if (filterparams && (Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT)) {
HashTable *ht = HASH_OF(filterparams);
zval *tmpzval;

if ((Z_TYPE_P(filterparams) == IS_ARRAY || Z_TYPE_P(filterparams) == IS_OBJECT) &&
(tmpzval = zend_hash_str_find_ind(HASH_OF(filterparams), "window", sizeof("window") - 1))) {
if ((tmpzval = zend_hash_str_find_ind(ht, "window", sizeof("window") - 1))) {
/* log-2 base of history window (9 - 15) */
zend_long tmp = zval_get_long(tmpzval);
if (tmp < -MAX_WBITS || tmp > MAX_WBITS + 32) {
Expand All @@ -332,6 +345,15 @@ static php_stream_filter *php_zlib_filter_create(const char *filtername, zval *f
windowBits = tmp;
}
}

if ((tmpzval = zend_hash_str_find_ind(ht, "max_output", sizeof("max_output") - 1))) {
zend_long tmp = zval_get_long(tmpzval);
if (tmp <= 0) {
php_error_docref(NULL, E_WARNING, "Invalid parameter given for max_output (" ZEND_LONG_FMT ")", tmp);
} else {
data->max_output = (size_t)tmp;
}
}
}

/* RFC 1951 Inflate */
Expand Down
Loading