diff --git a/NEWS b/NEWS index 84f5597d9780..1ac6f93def0f 100644 --- a/NEWS +++ b/NEWS @@ -94,6 +94,8 @@ PHP NEWS . Fixed a memory leak in array_merge_recursive() when the recursive merge of an object converted to an array fails. (David Carlier) . Fixed read buffer compaction in php_stream_filter_flush(). (crystarm) + . Fixed leaked buckets when a stream filter appends output and then returns + PSFS_FEED_ME or PSFS_ERR_FATAL. (Ilia Alshanetsky) - SimpleXML: . Fixed writing to a dimension of the object returned by attributes() not diff --git a/ext/standard/tests/filters/stream_filter_flush_discard_buckets.phpt b/ext/standard/tests/filters/stream_filter_flush_discard_buckets.phpt new file mode 100644 index 000000000000..a3dfa7c8d56f --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_flush_discard_buckets.phpt @@ -0,0 +1,43 @@ +--TEST-- +php_stream_filter_flush() discards buckets a filter left behind on PSFS_FEED_ME / PSFS_ERR_FATAL +--FILE-- +data); + } + stream_bucket_append($out, stream_bucket_new($this->stream, 'abandoned')); + return PSFS_FEED_ME; + } +} + +class fatal_filter extends php_user_filter +{ + public function filter($in, $out, &$consumed, $closing): int + { + stream_bucket_append($out, stream_bucket_new($this->stream, 'abandoned')); + return PSFS_ERR_FATAL; + } +} + +stream_filter_register('feed', feed_filter::class); +stream_filter_register('fatal', fatal_filter::class); + +foreach (['feed', 'fatal'] as $name) { + $fp = fopen('php://memory', 'w+'); + $filter = stream_filter_append($fp, $name, STREAM_FILTER_WRITE); + var_dump(stream_filter_remove($filter)); + fclose($fp); +} + +echo "done\n"; +?> +--EXPECTF-- +bool(true) + +Warning: stream_filter_remove(): Unable to flush filter, not removing in %s on line %d +bool(false) +done diff --git a/ext/standard/tests/filters/stream_filter_write_discard_buckets.phpt b/ext/standard/tests/filters/stream_filter_write_discard_buckets.phpt new file mode 100644 index 000000000000..83d3f102df38 --- /dev/null +++ b/ext/standard/tests/filters/stream_filter_write_discard_buckets.phpt @@ -0,0 +1,28 @@ +--TEST-- +php_stream_write_filtered() discards output buckets a filter left behind on PSFS_FEED_ME +--FILE-- +data); + } + stream_bucket_append($out, stream_bucket_new($this->stream, 'abandoned')); + return PSFS_FEED_ME; + } +} + +stream_filter_register('feed', feed_filter::class); + +$fp = fopen('php://memory', 'w+'); +stream_filter_append($fp, 'feed', STREAM_FILTER_WRITE); +var_dump(fwrite($fp, 'one')); +fclose($fp); + +echo "done\n"; +?> +--EXPECT-- +int(3) +done diff --git a/main/streams/filter.c b/main/streams/filter.c index edf0a01e46fc..ac46a19391aa 100644 --- a/main/streams/filter.c +++ b/main/streams/filter.c @@ -425,6 +425,16 @@ PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish) php_stream_filter_status_t status; status = current->fops->filter(stream, current, inp, outp, NULL, flags); + if (status == PSFS_FEED_ME || status == PSFS_ERR_FATAL) { + while ((bucket = inp->head)) { + php_stream_bucket_unlink(bucket); + php_stream_bucket_delref(bucket); + } + while ((bucket = outp->head)) { + php_stream_bucket_unlink(bucket); + php_stream_bucket_delref(bucket); + } + } if (status == PSFS_FEED_ME) { /* We've flushed the data far enough */ return SUCCESS; diff --git a/main/streams/streams.c b/main/streams/streams.c index 368de1a64774..eec7902c9d68 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -1306,6 +1306,11 @@ static ssize_t _php_stream_write_filtered(php_stream *stream, const char *buf, s php_stream_bucket_unlink(bucket); php_stream_bucket_delref(bucket); } + while (brig_outp->head) { + bucket = brig_outp->head; + php_stream_bucket_unlink(bucket); + php_stream_bucket_delref(bucket); + } break; }