[php-src] PR #23804: Fix use-after-free in StreamPollHandle when its stream is closed
| From: | nicolas-grekas | Date: | Sun, 20 Sep 2026 19:50:54 +0000 |
| Subject: | [php-src] PR #23804: Fix use-after-free in StreamPollHandle when its stream is closed | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-38642@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/23804
Author: nicolas-grekas
StreamPollHandle takes a reference on the resource, but it caches the php_stream
* it was given at construction. fclose() frees that stream while the resource
stays alive, so anything that reaches the handle afterwards works on freed memory:
```php
$ctx = new Io\Poll\Context();
[$a, $b] = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
$w = $ctx->add(new StreamPollHandle($a), [Io\Poll\Event::Read]);
fclose($a);
$w->remove(); // segfault, same with modifyEvents() and isValid()
```
Resolving the stream from the resource is enough: res->ptr is NULL once it is
closed, and the SOCK_ERR paths that are already there then do the right thing -
remove() skips the poll context, modifyEvents() throws
InvalidHandleException, isValid() returns false, and wait()
ignores the watcher. getStream() returns the closed resource, which is what the
polyfill does too.
I hit this while writing an event loop driver on top of the API. Closing a socket before cancelling
its watcher is a user mistake, but it should not crash.
BTW StreamPollHandle::getFileDescriptor() is implemented but not in the stub, so it is
dead code at the moment - remove it or expose it?
/cc @bukka @iluuu1994