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
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ PHP NEWS
lookup, and an unchecked CreateFileA() failure. (Ilia Alshanetsky)
. Fix persistent stream context lifetime during shutdown (Levi Morrison)

- Streams:
. Fixed read buffer size not being updated when a flushed read filter grows
the buffer in stream_filter_remove(), causing invalid reads. (Jakub Zelenka)

- Tidy:
. Fixed a use-after-free when a tidyNode is used after its document is
reparsed. (Ilia Alshanetsky)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
stream_filter_remove() keeps readbuflen in sync when growing the read buffer
--FILE--
<?php
class ClosingSuffixFilter extends php_user_filter
{
public function filter($in, $out, &$consumed, $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
if ($closing) {
stream_bucket_append($out, stream_bucket_new($this->stream, str_repeat('F', 20000)));
}
return PSFS_PASS_ON;
}
}
stream_filter_register('closing-suffix', ClosingSuffixFilter::class);

/* The file must be larger than one chunk so that the stream is not at EOF
* when the filter is removed and a refill of the read buffer happens later. */
$file = __DIR__ . '/stream_filter_remove_grow_read_buffer.tmp';
$content = str_repeat('a', 100) . str_repeat('b', 100000);
file_put_contents($file, $content);

$stream = fopen($file, 'r');
$filter = stream_filter_append($stream, 'closing-suffix', STREAM_FILTER_READ);
var_dump(fread($stream, 100) === str_repeat('a', 100));
/* The flushed 20000 bytes exceed the remaining read buffer capacity, so the buffer is grown */
var_dump(stream_filter_remove($filter));
$rest = stream_get_contents($stream);
var_dump(strlen($rest));
/* Data already buffered before the flush comes first, then the flushed data, then the rest of the file */
var_dump(preg_match('/^b*F{20000}b*$/', $rest));
var_dump(substr_count($rest, 'b'));
var_dump(feof($stream));
fclose($stream);
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/stream_filter_remove_grow_read_buffer.tmp');
?>
--EXPECT--
bool(true)
bool(true)
int(120000)
int(1)
int(100000)
bool(true)
3 changes: 2 additions & 1 deletion main/streams/filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,8 @@ PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish)
}
if (flushed_size > (stream->readbuflen - stream->writepos)) {
/* Grow the buffer */
stream->readbuf = perealloc(stream->readbuf, stream->writepos + flushed_size + stream->chunk_size, stream->is_persistent);
stream->readbuflen = stream->writepos + flushed_size + stream->chunk_size;
stream->readbuf = perealloc(stream->readbuf, stream->readbuflen, stream->is_persistent);
}
while ((bucket = inp->head)) {
memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
Expand Down
Loading