Skip to content

Commit a315900

Browse files
author
Sjoerd Langkemper
committed
Count number of filters, raise deprecation warning
Limit number of filters that can be chained in a php://filter URL. Count number of filters already on the stream, instead of counting iterations on the loop. When filters are separated by slash instead of pipe, php_stream_apply_filter_list is called muliple times, so counting iterations won't work. Instead, count numbers of filters already on the chain. Add more elaborate test that tests: - file read - file include - no warning on stream_filter_append Related to: #10453 #16699
1 parent 7731252 commit a315900

5 files changed

Lines changed: 107 additions & 29 deletions

File tree

ext/standard/php_fopen_wrapper.c

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
#include "php_memory_streams.h"
2727
#include "php_fopen_wrappers.h"
2828
#include "SAPI.h"
29-
#include "zend_exceptions.h"
3029

3130
static ssize_t php_stream_output_write(php_stream *stream, const char *buf, size_t count) /* {{{ */
3231
{
@@ -145,31 +144,30 @@ static const php_stream_ops php_stream_input_ops = {
145144
NULL /* set_option */
146145
};
147146

148-
static const char max_stream_filters = 5;
147+
static const int max_stream_filters = 16;
149148

150149
static void php_stream_apply_filter_list(php_stream *stream, char *filterlist, int read_chain, int write_chain) /* {{{ */
151150
{
152151
char *p, *token = NULL;
153152
php_stream_filter *temp_filter;
154-
char nb_filters = 0;
155153

156154
p = php_strtok_r(filterlist, "|", &token);
157155
while (p) {
158-
if (nb_filters >= max_stream_filters) {
159-
zend_throw_exception_ex(NULL, 0, "Unable to apply filter, maximum number (%d) reached", max_stream_filters);
160-
return;
161-
}
162-
nb_filters++;
163-
164156
php_url_decode(p, strlen(p));
165157
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+
}
166161
if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
167162
php_stream_filter_append(&stream->readfilters, temp_filter);
168163
} else {
169164
php_error_docref(NULL, E_WARNING, "Unable to create filter (%s)", p);
170165
}
171166
}
172167
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+
}
173171
if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
174172
php_stream_filter_append(&stream->writefilters, temp_filter);
175173
} else {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
--TEST--
2+
At most 16 filters can be chained in one stream
3+
--EXTENSIONS--
4+
filter
5+
--FILE--
6+
<?php
7+
8+
function createFilterChains($n, $resource) {
9+
$filter = 'string.toupper';
10+
$pipes = 'php://filter/' . implode('|', array_fill(0, $n, $filter)) . "/resource=$resource";
11+
$slashes = 'php://filter/' . implode('/', array_fill(0, $n, $filter)) . "/resource=$resource";
12+
$resources = str_repeat("php://filter/$filter/resource=", $n) . $resource;
13+
return [$pipes, $slashes, $resources];
14+
}
15+
16+
$allowed_read = createFilterChains(16, 'data:text/plain,sixteen');
17+
foreach ($allowed_read as $chain) {
18+
var_dump(file_get_contents($chain));
19+
}
20+
21+
$allowed_include = createFilterChains(16, 'php://temp');
22+
foreach ($allowed_include as $chain) {
23+
var_dump(include $chain);
24+
}
25+
26+
$blocked_read = createFilterChains(17, 'data:text/plain,seventeen');
27+
foreach ($blocked_read as $chain) {
28+
var_dump(file_get_contents($chain));
29+
}
30+
31+
$blocked_include = createFilterChains(17, 'php://temp');
32+
foreach ($blocked_include as $chain) {
33+
var_dump(include $chain);
34+
}
35+
36+
// Test that the warning is only given once, even when we add two filters over the limit.
37+
$blocked_read = createFilterChains(18, 'data:text/plain,eighteen');
38+
foreach ($blocked_read as $chain) {
39+
var_dump(file_get_contents($chain));
40+
}
41+
42+
// many filters with stream_filter_append still works
43+
$fp = fopen('data:text/plain,stream_filter_append', 'r');
44+
for ($i = 0; $i < 80; $i++) {
45+
stream_filter_append($fp, 'string.toupper');
46+
}
47+
var_dump(fread($fp, 30));
48+
fclose($fp);
49+
50+
?>
51+
--EXPECTF--
52+
string(7) "SIXTEEN"
53+
string(7) "SIXTEEN"
54+
string(7) "SIXTEEN"
55+
int(1)
56+
int(1)
57+
int(1)
58+
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
60+
string(9) "SEVENTEEN"
61+
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
63+
string(9) "SEVENTEEN"
64+
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
66+
string(9) "SEVENTEEN"
67+
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
69+
int(1)
70+
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
72+
int(1)
73+
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
75+
int(1)
76+
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
78+
string(8) "EIGHTEEN"
79+
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
81+
string(8) "EIGHTEEN"
82+
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
84+
string(8) "EIGHTEEN"
85+
string(20) "STREAM_FILTER_APPEND"

main/streams/filter.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,20 @@ PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream
447447
}
448448
}
449449

450+
PHPAPI int php_stream_filter_count(php_stream_filter_chain *chain) {
451+
if (chain->head == NULL) {
452+
return 0;
453+
}
454+
455+
int count = 1;
456+
php_stream_filter *node = chain->head;
457+
while (node != chain->tail) {
458+
count += 1;
459+
node = node->next;
460+
}
461+
return count;
462+
}
463+
450464
PHPAPI zend_result _php_stream_filter_flush(php_stream_filter *filter, bool finish)
451465
{
452466
php_stream_bucket_brigade brig_a = { NULL, NULL }, brig_b = { NULL, NULL }, *inp = &brig_a, *outp = &brig_b, *brig_temp;

main/streams/php_stream_filter_api.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ PHPAPI void _php_stream_filter_prepend(php_stream_filter_chain *chain, php_strea
138138
PHPAPI void php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
139139
PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter);
140140
PHPAPI zend_result php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
141+
PHPAPI int php_stream_filter_count(php_stream_filter_chain *chain);
141142
PHPAPI zend_result _php_stream_filter_flush(php_stream_filter *filter, bool finish);
142143
PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, bool call_dtor);
143144
PHPAPI void php_stream_filter_free(php_stream_filter *filter);

tests/security/bug10453.phpt

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)