Skip to content

Commit af279cb

Browse files
author
Sjoerd Langkemper
committed
Error when max_filter_count is set and exceeded
Read the maximum number of filters from context stream. If it is set, it doesn't make sense to raise a deprecation warning, because this stream context option didn't exist earlier. We raise an error consistent with current stream handling, as long as rfc/stream_errors has not landed. If it is not set, we give a deprecation warning when the 17th filter is added. This is unfortunately done in another place; to prevent showing warnings multiple times, the counting is done within the loop and not at the end.
1 parent a315900 commit af279cb

2 files changed

Lines changed: 76 additions & 20 deletions

File tree

ext/standard/php_fopen_wrapper.c

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -144,30 +144,54 @@ static const php_stream_ops php_stream_input_ops = {
144144
NULL /* set_option */
145145
};
146146

147-
static const int max_stream_filters = 16;
147+
static const int max_filter_count_default = 16;
148+
149+
static int php_get_max_filter_count(php_stream_context *context) {
150+
if (context != NULL) {
151+
zval *option_val = php_stream_context_get_option(context, "filter", "max_filter_count");
152+
if (option_val) {
153+
zend_long custom_limit = zval_get_long(option_val);
154+
if (custom_limit >= 0) {
155+
return (int)custom_limit;
156+
}
157+
}
158+
}
159+
return -1;
160+
}
161+
162+
static bool php_stream_has_too_many_filters(php_stream *stream, php_stream_context *context) {
163+
int max_filter_count = php_get_max_filter_count(context);
164+
if (max_filter_count == -1) {
165+
// If not explicitly configured we don't throw an error yet.
166+
return false;
167+
}
148168

149-
static void php_stream_apply_filter_list(php_stream *stream, char *filterlist, int read_chain, int write_chain) /* {{{ */
169+
int count = MAX(php_stream_filter_count(&stream->readfilters), php_stream_filter_count(&stream->writefilters));
170+
return count > max_filter_count;
171+
}
172+
173+
static void php_stream_apply_filter_list(php_stream *stream, char *filterlist, int read_chain, int write_chain, bool warn_filter_count) /* {{{ */
150174
{
151175
char *p, *token = NULL;
152176
php_stream_filter *temp_filter;
153177

154178
p = php_strtok_r(filterlist, "|", &token);
155179
while (p) {
180+
int count = read_chain ? php_stream_filter_count(&stream->readfilters) : write_chain ? php_stream_filter_count(&stream->writefilters) : 0;
181+
if (warn_filter_count && count == max_filter_count_default) {
182+
zend_error(E_DEPRECATED, "Using more than %d filters in a php://filter URL is deprecated, "
183+
"set this limit using the stream context option max_filter_count, or use stream_filter_append", max_filter_count_default);
184+
}
185+
156186
php_url_decode(p, strlen(p));
157187
if (read_chain) {
158-
if (php_stream_filter_count(&stream->readfilters) == max_stream_filters) {
159-
zend_error(E_DEPRECATED, "Using more than %d filters in a php://filter URL is deprecated, use stream_filter_append to chain more than %d filters", max_stream_filters, max_stream_filters);
160-
}
161188
if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
162189
php_stream_filter_append(&stream->readfilters, temp_filter);
163190
} else {
164191
php_error_docref(NULL, E_WARNING, "Unable to create filter (%s)", p);
165192
}
166193
}
167194
if (write_chain) {
168-
if (php_stream_filter_count(&stream->writefilters) == max_stream_filters) {
169-
zend_error(E_DEPRECATED, "Using more than %d filters in a php://filter URL is deprecated, use stream_filter_append to chain more than %d filters", max_stream_filters, max_stream_filters);
170-
}
171195
if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
172196
php_stream_filter_append(&stream->writefilters, temp_filter);
173197
} else {
@@ -363,16 +387,18 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c
363387
return NULL;
364388
}
365389

390+
bool max_filter_count_not_set = php_get_max_filter_count(context) == -1;
391+
366392
*p = '\0';
367393

368394
p = php_strtok_r(pathdup + 1, "/", &token);
369395
while (p) {
370396
if (!strncasecmp(p, "read=", 5)) {
371-
php_stream_apply_filter_list(stream, p + 5, 1, 0);
397+
php_stream_apply_filter_list(stream, p + 5, 1, 0, max_filter_count_not_set);
372398
} else if (!strncasecmp(p, "write=", 6)) {
373-
php_stream_apply_filter_list(stream, p + 6, 0, 1);
399+
php_stream_apply_filter_list(stream, p + 6, 0, 1, max_filter_count_not_set);
374400
} else {
375-
php_stream_apply_filter_list(stream, p, mode_rw & PHP_STREAM_FILTER_READ, mode_rw & PHP_STREAM_FILTER_WRITE);
401+
php_stream_apply_filter_list(stream, p, mode_rw & PHP_STREAM_FILTER_READ, mode_rw & PHP_STREAM_FILTER_WRITE, max_filter_count_not_set);
376402
}
377403
p = php_strtok_r(NULL, "/", &token);
378404
}
@@ -383,6 +409,12 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c
383409
return NULL;
384410
}
385411

412+
if (php_stream_has_too_many_filters(stream, context)) {
413+
php_stream_wrapper_log_error(wrapper, options, "too many filters");
414+
php_stream_close(stream);
415+
return NULL;
416+
}
417+
386418
return stream;
387419
} else {
388420
/* invalid php://thingy */

ext/standard/tests/filters/max_filter_chain.phpt

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ foreach ($blocked_include as $chain) {
3333
var_dump(include $chain);
3434
}
3535

36+
$ctx = stream_context_create(['filter' => ['max_filter_count' => 2]]);
37+
$blocked_read = createFilterChains(3, 'data:text/plain,three');
38+
foreach ($blocked_read as $chain) {
39+
var_dump(file_get_contents($chain, false, $ctx));
40+
}
41+
42+
$ctx = stream_context_create(['filter' => ['max_filter_count' => 20]]);
43+
$allowed_read = createFilterChains(19, 'data:text/plain,nineteen');
44+
foreach ($allowed_read as $chain) {
45+
var_dump(file_get_contents($chain, false, $ctx));
46+
}
47+
3648
// Test that the warning is only given once, even when we add two filters over the limit.
3749
$blocked_read = createFilterChains(18, 'data:text/plain,eighteen');
3850
foreach ($blocked_read as $chain) {
@@ -56,30 +68,42 @@ int(1)
5668
int(1)
5769
int(1)
5870

59-
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, use stream_filter_append to chain more than 16 filters in %smax_filter_chain.php on line %d
71+
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in %smax_filter_chain.php on line %d
6072
string(9) "SEVENTEEN"
6173

62-
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, use stream_filter_append to chain more than 16 filters in %smax_filter_chain.php on line %d
74+
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in %smax_filter_chain.php on line %d
6375
string(9) "SEVENTEEN"
6476

65-
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, use stream_filter_append to chain more than 16 filters in %smax_filter_chain.php on line %d
77+
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in %smax_filter_chain.php on line %d
6678
string(9) "SEVENTEEN"
6779

68-
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, use stream_filter_append to chain more than 16 filters in %smax_filter_chain.php on line %d
80+
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in %smax_filter_chain.php on line %d
6981
int(1)
7082

71-
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, use stream_filter_append to chain more than 16 filters in %smax_filter_chain.php on line %d
83+
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in %smax_filter_chain.php on line %d
7284
int(1)
7385

74-
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, use stream_filter_append to chain more than 16 filters in %smax_filter_chain.php on line %d
86+
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in %smax_filter_chain.php on line %d
7587
int(1)
7688

77-
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, use stream_filter_append to chain more than 16 filters in %smax_filter_chain.php on line %d
89+
Warning: file_get_contents(php://filter/string.toupper|string.toupper|string.toupper/resource=data:text/plain,three): Failed to open stream: too many filters in %s on line %d
90+
bool(false)
91+
92+
Warning: file_get_contents(php://filter/string.toupper/string.toupper/string.toupper/resource=data:text/plain,three): Failed to open stream: too many filters in %s on line %d
93+
bool(false)
94+
95+
Warning: file_get_contents(php://filter/string.toupper/resource=php://filter/string.toupper/resource=php://filter/string.toupper/resource=data:text/plain,three): Failed to open stream: too many filters in %s on line %d
96+
bool(false)
97+
string(8) "NINETEEN"
98+
string(8) "NINETEEN"
99+
string(8) "NINETEEN"
100+
101+
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in %smax_filter_chain.php on line %d
78102
string(8) "EIGHTEEN"
79103

80-
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, use stream_filter_append to chain more than 16 filters in %smax_filter_chain.php on line %d
104+
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in %smax_filter_chain.php on line %d
81105
string(8) "EIGHTEEN"
82106

83-
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, use stream_filter_append to chain more than 16 filters in %smax_filter_chain.php on line %d
107+
Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in %smax_filter_chain.php on line %d
84108
string(8) "EIGHTEEN"
85109
string(20) "STREAM_FILTER_APPEND"

0 commit comments

Comments
 (0)