Skip to content

Commit 6b8839b

Browse files
committed
Fix stream filter flush corrupting a partially-read buffer
When a read filter is flushed (for example via stream_filter_remove()) while the stream has already been partially read, _php_stream_filter_flush() backs the unconsumed tail of the read buffer up to offset 0. Two defects in that block diverged from the equivalent code in streams.c. The copy used memcpy() on source and destination ranges that overlap whenever writepos - readpos exceeds readpos, which is undefined behavior (ASAN reports memcpy-param-overlap). And writepos was shrunk with writepos -= readpos after readpos had already been zeroed, making the subtraction a no-op, so writepos stayed inflated by readpos bytes: the stale tail was kept as live data and later flushed buckets were appended past the real end, duplicating bytes and risking an out-of-bounds write. Use memmove() and subtract before zeroing, matching streams.c.
1 parent 19f595f commit 6b8839b

2 files changed

Lines changed: 35 additions & 2 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Flushing a read filter on a partially-read stream must not duplicate buffered bytes
3+
--FILE--
4+
<?php
5+
class FlushEmitFilter extends php_user_filter
6+
{
7+
public function filter($in, $out, &$consumed, $closing): int
8+
{
9+
while ($bucket = stream_bucket_make_writeable($in)) {
10+
$consumed += $bucket->datalen;
11+
stream_bucket_append($out, $bucket);
12+
}
13+
if ($closing) {
14+
stream_bucket_append($out, stream_bucket_new($this->stream, "<FLUSH>"));
15+
}
16+
return PSFS_PASS_ON;
17+
}
18+
}
19+
20+
stream_filter_register("flushemit", "FlushEmitFilter");
21+
22+
$fp = fopen("php://memory", "r+");
23+
fwrite($fp, "ABCDEFGHIJKLMNOP");
24+
rewind($fp);
25+
26+
$filter = stream_filter_append($fp, "flushemit", STREAM_FILTER_READ);
27+
var_dump(fread($fp, 5));
28+
stream_filter_remove($filter);
29+
var_dump(fread($fp, 100));
30+
?>
31+
--EXPECT--
32+
string(5) "ABCDE"
33+
string(18) "FGHIJKLMNOP<FLUSH>"

main/streams/filter.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,9 @@ PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish)
459459
/* Dump any newly flushed data to the read buffer */
460460
if (stream->readpos > 0) {
461461
/* Back the buffer up */
462-
memcpy(stream->readbuf, stream->readbuf + stream->readpos, stream->writepos - stream->readpos);
463-
stream->readpos = 0;
462+
memmove(stream->readbuf, stream->readbuf + stream->readpos, stream->writepos - stream->readpos);
464463
stream->writepos -= stream->readpos;
464+
stream->readpos = 0;
465465
}
466466
if (flushed_size > (stream->readbuflen - stream->writepos)) {
467467
/* Grow the buffer */

0 commit comments

Comments
 (0)