From e0643a1fb0054a2f5095ee5c3c85940dea10fa49 Mon Sep 17 00:00:00 2001 From: Edmond <1571649+edmonddantes@users.noreply.github.com> Date: Tue, 6 Oct 2026 23:46:41 +0000 Subject: [PATCH] Fix use-after-free when pclose() closes a stream from its user filter fclose() refuses a stream marked PHP_STREAM_FLAG_NO_FCLOSE, which a user filter's callback sets on its stream; pclose() closed it anyway, freeing the stream and the filter under the running callback. --- ext/standard/file.c | 5 ++++ .../tests/filters/pclose_in_filter.phpt | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 ext/standard/tests/filters/pclose_in_filter.phpt diff --git a/ext/standard/file.c b/ext/standard/file.c index 2a84a9849a95..86337a3452f5 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -845,6 +845,11 @@ PHP_FUNCTION(pclose) PHP_STREAM_FROM_ZVAL(stream, res); + if ((stream->flags & PHP_STREAM_FLAG_NO_FCLOSE) != 0) { + php_error_docref(NULL, E_WARNING, ZEND_LONG_FMT " is not a valid stream resource", stream->res->handle); + RETURN_LONG(-1); + } + FG(pclose_wait) = 1; zend_list_close(stream->res); FG(pclose_wait) = 0; diff --git a/ext/standard/tests/filters/pclose_in_filter.phpt b/ext/standard/tests/filters/pclose_in_filter.phpt new file mode 100644 index 000000000000..32c6183db3cd --- /dev/null +++ b/ext/standard/tests/filters/pclose_in_filter.phpt @@ -0,0 +1,28 @@ +--TEST-- +pclose() of the stream from its user filter's callback +--FILE-- +datalen; + stream_bucket_append($out, $bucket); + } + return PSFS_PASS_ON; + } +} + +stream_filter_register('closer', 'Closer'); +$fp = fopen('php://memory', 'w+'); +stream_filter_append($fp, 'closer', STREAM_FILTER_WRITE); +fwrite($fp, 'abc'); +var_dump(pclose($fp)); +?> +--EXPECTF-- +Warning: pclose(): %d is not a valid stream resource in %s on line %d +int(-1) +int(0)