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
30 changes: 30 additions & 0 deletions ext/bz2/bz2_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,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;

bool persistent;
bool expect_concatenated : 1; /* Decompress option */
Expand Down Expand Up @@ -142,6 +144,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_stream_bucket_delref(bucket);
php_error_docref(NULL, E_NOTICE, "bzip2.decompress: decompressed output exceeded max_output");
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 @@ -165,6 +173,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 @@ -207,6 +220,7 @@ static zend_result php_bz2_decompress_seek(
data->strm.next_out = data->outbuf;
data->strm.avail_out = data->outbuf_len;
data->status = PHP_BZ2_UNINITIALIZED;
data->total_output = 0;

/* Note: We don't reinitialize here - it will be done on first use in the filter function */

Expand Down Expand Up @@ -398,6 +412,7 @@ static php_stream_filter *php_bz2_decompress_filter_create(zval *filter_params,
php_stream_filter_seekable_t write_seekable = PSFS_SEEKABLE_ALWAYS;
bool small_footprint = false;
bool expect_concatenated = false;
size_t max_output = 0;

if (filter_params) {
if (UNEXPECTED(
Expand Down Expand Up @@ -440,13 +455,28 @@ static php_stream_filter *php_bz2_decompress_filter_create(zval *filter_params,
if (small) {
small_footprint = zend_is_true(small);
}

const zval *max_output_zv = zend_hash_str_find_ind(filter_params_ht, ZEND_STRL("max_output"));
if (max_output_zv) {
bool failed = false;
zend_long max_output_long = zval_try_get_long(max_output_zv, &failed);
if (UNEXPECTED(failed)) {
php_error_docref("filters.compression", E_WARNING, "Max output parameter must be of type int, %s given", zend_zval_type_name(max_output_zv));
return NULL;
} else if (max_output_long <= 0) {
php_error_docref("filters.compression", E_WARNING, "Max output must be greater than 0, " ZEND_LONG_FMT " given", max_output_long);
return NULL;
}
max_output = (size_t) max_output_long;
}
}
}

php_bz2_filter_data *data = php_bz2_filter_data_new(persistent);
/* Save configuration for reset */
data->small_footprint = small_footprint;
data->expect_concatenated = expect_concatenated;
data->max_output = max_output;
data->status = PHP_BZ2_UNINITIALIZED;

return php_stream_filter_alloc(&php_bz2_decompress_ops, data, persistent, PSFS_SEEKABLE_START, write_seekable);
Expand Down
88 changes: 88 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,88 @@
--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 survives rewind ---\n";
$fp = fopen('php://temp', 'w+');
fwrite($fp, $compressed);
rewind($fp);
stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_READ, ['max_output' => 1024]);
var_dump(strlen(stream_get_contents($fp)));
rewind($fp);
var_dump(strlen(stream_get_contents($fp)));
fclose($fp);

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

echo "--- max_output = array (invalid type) ---\n";
$fp = fopen('php://temp', 'w+');
var_dump(stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_WRITE, ['max_output' => []]));
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 survives rewind ---
int(1024)
int(1024)
--- max_output = 0 (invalid) ---

Warning: stream_filter_append(): Max output must be greater than 0, 0 given in %s on line %d

Warning: stream_filter_append(): Unable to create or locate filter "bzip2.decompress" in %s on line %d
bool(false)
--- max_output = -1 (invalid) ---

Warning: stream_filter_append(): Max output must be greater than 0, -1 given in %s on line %d

Warning: stream_filter_append(): Unable to create or locate filter "bzip2.decompress" in %s on line %d
bool(false)
--- max_output = array (invalid type) ---

Warning: stream_filter_append(): Max output parameter must be of type int, array given in %s on line %d

Warning: stream_filter_append(): Unable to create or locate filter "bzip2.decompress" in %s on line %d
bool(false)
79 changes: 79 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,79 @@
--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 survives rewind ---\n";
$fp = fopen('php://temp', 'w+');
fwrite($fp, $compressed);
rewind($fp);
stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_READ, ['max_output' => 1024]);
var_dump(strlen(stream_get_contents($fp)));
rewind($fp);
var_dump(strlen(stream_get_contents($fp)));
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);

echo "--- max_output = array (invalid type) ---\n";
$fp = fopen('php://temp', 'w+');
stream_filter_append($fp, 'zlib.inflate', STREAM_FILTER_WRITE, ['max_output' => []]);
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 survives rewind ---
int(1024)
int(1024)
--- 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
--- max_output = array (invalid type) ---

Warning: stream_filter_append(): Invalid type for max_output, expected int in %s on line %d
32 changes: 29 additions & 3 deletions ext/zlib/zlib_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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 windowBits;
bool persistent;
bool finished; /* for zlib.deflate: signals that no flush is pending */
Expand Down Expand Up @@ -109,6 +111,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_stream_bucket_delref(bucket);
php_error_docref(NULL, E_NOTICE, "zlib.inflate: decompressed output exceeded max_output");
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 @@ -130,6 +138,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 @@ -184,6 +197,7 @@ static zend_result php_zlib_inflate_seek(
data->strm.next_out = data->outbuf;
data->strm.avail_out = data->outbuf_len;
data->finished = false;
data->total_output = 0;

return SUCCESS;
}
Expand Down Expand Up @@ -412,11 +426,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 @@ -425,6 +439,18 @@ 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))) {
bool failed;
zend_long tmp = zval_try_get_long(tmpzval, &failed);
if (failed) {
php_error_docref("filters.compression", E_WARNING, "Invalid type for max_output, expected int");
} else if (tmp <= 0) {
php_error_docref("filters.compression", E_WARNING, "Invalid parameter given for max_output (" ZEND_LONG_FMT ")", tmp);
} else {
data->max_output = (size_t)tmp;
}
}
}

/* Save configuration for reset */
Expand Down
Loading