[php-src] PR #24168: Use-after-free in stream_filter_remove() called from a stream callback
| From: | EdmondDantes | Date: | Tue, 06 Oct 2026 18:40:39 +0000 |
| Subject: | [php-src] PR #24168: Use-after-free in stream_filter_remove() called from a stream callback | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-39220@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24168
Author: EdmondDantes
stream_filter_remove() flushes the filter, then unlinks and frees it. The flush can run
PHP code: a user filter's filter() method, or a user wrapper's
stream_write() for the flushed data. If that code removes the same filter, the outer
call continues on freed memory. A user filter can also remove itself (or another filter of its
stream) from filter(), or from other code while filter() is suspended in a
Fiber; userfilter_filter() and the filter chain walks then use the freed filter after
the callback returns. Valgrind shows invalid reads in userfilter_filter() and
php_stream_filter_remove().
The fix:
- userfilter_filter() sets a new stream flag,
PHP_STREAM_FLAG_USER_FILTER_RUNNING, around the callback, next to the existing
PHP_STREAM_FLAG_NO_FCLOSE. stream_filter_remove() refuses with a warning
while it is set, as fclose() already does.
- After the flush, stream_filter_remove() checks that the filter resource is still
alive, for the case where a user wrapper removed it from stream_write().
Tests: ext/standard/tests/filters/stream_filter_remove_in_filter.phpt (removal from
filter() and while filter() is suspended in a Fiber) and
stream_filter_remove_during_flush.phpt (removal from a user wrapper's
stream_write() during the flush). Both fail without the fix, with invalid reads under
valgrind.
Known limits, left for separate changes: the flag is saved and restored per call, so Fibers resumed
out of order can clear it early, as with PHP_STREAM_FLAG_NO_FCLOSE today;
pclose() and closedir() free a stream without checking
PHP_STREAM_FLAG_NO_FCLOSE.