Fix TLS stream EOF detection after close_notify with stale errno - #24132
Merged
Merged
Conversation
php_openssl_handle_ssl_error() sets errno to EAGAIN on SSL_ERROR_WANT_READ and SSL_ERROR_WANT_WRITE, and php_openssl_sockop_io() reads it back to avoid marking a non-blocking read that needs to wait as EOF. OpenSSL resets errno before every recv() on POSIX systems, but on Windows it uses the Winsock error state instead and never touches errno, so the EAGAIN stays set across every later successful read. A close_notify received afterwards returned SSL_ERROR_ZERO_RETURN but did not set stream->eof, and feof() stayed false while the TCP connection was still open. Decide EOF from the SSL error code, which already says whether the operation just needs to wait, instead of from errno.
bukka
referenced
this pull request
Oct 5, 2026
StreamPollHandle took the descriptor through the select cast. On a TLS stream that cast reads: it moves what OpenSSL holds decrypted into the stream buffer so that stream_select() can report it. Adding a watcher to an Io\Poll\Context therefore read from the stream, filled the buffer of a stream set unbuffered with stream_set_read_buffer() and changed unread_bytes. That is not how the poll API should behave. Adding a handle is a registration and must leave the stream as it found it, and the data that moved was not reported by the watcher anyway, as bytes held by the stream layer are not readiness of the descriptor. A new cast PHP_STREAM_AS_FD_FOR_POLL returns the descriptor and nothing else. The socket, TLS, plain and pgsql wrappers answer it, a userspace wrapper sees it as STREAM_CAST_FOR_SELECT and the stream it returns is cast the same way, and a filtered stream allows it like the select cast. StreamPollHandle uses it, so adding a TLS stream leaves its buffer and unread_bytes as they were. The select cast and stream_select() are unchanged. This is fixed in 8.6 because the API is new there and this is the behaviour it should ship with. Keeping a read inside the registration would complicate things later: once reads on a TLS stream can suspend, the cast would become a nested read on a stream with an operation in flight, and changing the cast then would change the visible behaviour of a released API.
bukka
added a commit
that referenced
this pull request
Oct 5, 2026
* PHP-8.4: Fix TLS stream EOF detection after close_notify with stale errno (#24132)
bukka
added a commit
that referenced
this pull request
Oct 5, 2026
* PHP-8.5: Fix TLS stream EOF detection after close_notify with stale errno (#24132)
bukka
added a commit
that referenced
this pull request
Oct 5, 2026
* PHP-8.6: Fix TLS stream EOF detection after close_notify with stale errno (#24132)
arnaud-lb
added a commit
to nicolas-grekas/php-src
that referenced
this pull request
Oct 5, 2026
* up/PHP-8.4: [ci skip] NEWS Rethrow exceptions from destructors called by the GC in a fiber (php#24118) ext/intl: Use byte offsets in IntlDateFormatter parsing ext/dom: Restore the XPath context after a reentrant evaluation Fix TLS stream EOF detection after close_notify with stale errno (php#24132) Re-generate outdated parse_date.c file ext/standard: Close owned proc_open descriptors on setup failure ext/standard: Keep IPTC headers local to each call ext/standard: Reject incomplete sha1_file reads
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
php_openssl_handle_ssl_error() sets errno to EAGAIN on SSL_ERROR_WANT_READ and SSL_ERROR_WANT_WRITE, and php_openssl_sockop_io() reads it back to avoid marking a non-blocking read that needs to wait as EOF. OpenSSL resets errno before every recv() on POSIX systems, but on Windows it uses the Winsock error state instead and never touches errno, so the EAGAIN stays set across every later successful read. A close_notify received afterwards returned SSL_ERROR_ZERO_RETURN but did not set stream->eof, and feof() stayed false while the TCP connection was still open.
Decide EOF from the SSL error code, which already says whether the operation just needs to wait, instead of from errno.