[php-src] PR #23791: ext/standard: Fix use-after-free when a polled stream is closed
| From: | nicolas-grekas | Date: | Sun, 20 Sep 2026 14:55:22 +0000 |
| Subject: | [php-src] PR #23791: ext/standard: Fix use-after-free when a polled stream is closed | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-38629@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/23791
Author: nicolas-grekas
Found while building an
Io\Poll driver for revolt/event-loop.
StreamPollHandle caches the php_stream * it is constructed with, while the
reference it takes is on the resource. fclose() frees the stream and leaves the
resource alive as a closed one, so every later use of the handle reads freed memory:
```php
[$r, $w] = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
$ctx = new Io\Poll\Context();
$watcher = $ctx->add(new StreamPollHandle($r), [Io\Poll\Event::Read]);
fclose($r);
$watcher->remove(); // segfault, and so do modifyEvents() and Context::add()
```
isValid() returns true there instead of false, and getStream() goes
through the freed stream to reach the resource the handle already holds.
The stream is now resolved from the resource on each use, which gives NULL once it is
closed. Context::add(), Watcher::modifyEvents() and
Watcher::remove() already handle SOCK_ERR, so they throw
InvalidHandleException and, for remove(), drop the watcher, the way they
were written to.