php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #65137 stream_select misleads when TLS socket is used
Submitted: 2013-06-26 19:01 UTC Modified: 2015-03-11 15:51 UTC
Votes:2
Avg. Score:5.0 ± 0.0
Reproduced:2 of 2 (100.0%)
Same Version:1 (50.0%)
Same OS:0 (0.0%)
From: boen dot robot at gmail dot com Assigned: rdlowrey (profile)
Status: Closed Package: Streams related
PHP Version: 5.5.0 OS: Windows Server 2008 R2
Private report: No CVE-ID: None
 [2013-06-26 19:01 UTC] boen dot robot at gmail dot com
Description:
------------
When stream_select() is called on a socket stream created by stream_socket_client() which uses TLS (with ADH), it seems to report too early. That is, it would report that that the stream can be read, but then when you try to read it, it would block, and potentially fail.

The exact same code, if called without encryption, would always work just fine. Also, if right before receiving, the script is made to sleep for a while (in my case, about 100 miliseconds did the trick), the script is also fine.

Sample code is hard to give, as this occurs only when the server sends large enough data at once over the encrypted connection, and I seem to be unable to mock a TLS+ADH server with PHP (which is a separate issue that, at this point, could be just me being stupid).

I *think* the issue might be that stream_select() reports on the underlying socket (i.e. the data being received before decryption), rather than the *readable data* from the socket, while fread() blocks until there's at least 1 byte of readable data to be read. But this shouldn't be so IMHO.

Note that this occurs not just with PHP 5.5.0, but also the latest releases of 5.4.16 and 5.3.26, and at least as early as 5.3.8.

Test script:
---------------
NOTE: This script is an "approximation" so to speak, in that it demonstrates the exact settings for the client where this occurs, but due to the lack of a mock server, it's not confirmed to always fail.

<?php
$socket = stream_socket_client(
	'tls://example.com:8729',
	$errno,
	$errstr,
	2,
	STREAM_CLIENT_CONNECT,
	stream_context_create(
		array('ssl' => array('ciphers' => 'ADH'))
	)
);

fwrite($socket, chr(6) . '/login' . chr(0));

$r = array($socket);
$w = $e = null;

if (1 === stream_select($r, $w, $e, null)) {
    echo fread($socket, 1);
} else {
    echo 'stream_select() returned with errors';
}


Expected result:
----------------
stream_select() should only report the encrypted socket as readable if there's at least 1 byte of readable data in the buffer that can be read by fread().

Actual result:
--------------
stream_select() reports the encrypted socket as readable, but fread() blocks, and may even time out.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2013-06-26 23:59 UTC] boen dot robot at gmail dot com
On a quick inspection of the relevant source (and I must note I know nothing about PHP's internals, nor have I compiled PHP, so take this with a grain of salt), this seems to be the problematic part:

https://github.com/php/php-src/blob/642721b38a9c5ebf336c81027c0dafd6f9246bd6/ext/openssl/xp_ssl.c#L814

For some reason, when doing a cast for stream_select(), the actual socket is returned directly, as opposed to this happening after an "sslsock->ssl_active" check, like the other casts, which I'm guessing is causing the gap between what stream_select() says, and what fread() does. What's the reason for it being that way anyway?
 [2014-01-02 13:28 UTC] ab@php.net
-Status: Open +Status: Feedback
 [2014-01-02 13:28 UTC] ab@php.net
Are you using x64 bins? There was a fix related to another bug, but the exact place you've pointed to was touched

http://git.php.net/?p=php-src.git;a=commitdiff;h=da62fd5ed824bafc4dc3e90278c3d57d8e74cbe1

That's most likely a fix on win64, not sure if it'll affect a 32 bit builds in your case. Anyway, I'd say it makes sense you to test again with a snap from here http://windows.php.net/downloads/snaps/php-5.5/ (please pick the latest). With those snaps and your snippet I get "stream_select() returned with errors", but also a lot of warnings with display_errors=1. Used some public sites like SSL'd google, etc.

Thanks.
 [2014-01-02 13:53 UTC] ab@php.net
-Assigned To: +Assigned To: ab
 [2014-04-03 14:27 UTC] heruan at aldu dot net
This is also happening when a stream_socket_server() accepts a connection and stream_socket_enable_crypto() on it; then, stream_select() reports data on a read socket but an immediate fread() returns empty, while usleep() a bit before fread() succeeds. This is happening on Linux with PHP 5.5.9.
 [2014-04-03 14:49 UTC] boen dot robot at gmail dot com
To ab@php.net:

Sorry for not responding earlier - and no, I'm not using x64 bins. All bins are the x86 NTS bins from windows.php.net.
 [2014-08-23 01:04 UTC] daverandom@php.net
This occurs because OpenSSL has an internal buffer, which stream_select() does not inspect.

I've just thrown a patch together [1] which fixes the issue for me, I'm going to try and squeeze it into 5.4.32, not sure if that ship has sailed yet, it may have to be 5.5+ only as it's not really a security fix in the strictest sense.

[1] https://github.com/DaveRandom/php-src/compare/fix/ssl-stream-select-buffer
 [2014-08-27 15:51 UTC] daverandom@php.net
-Status: Feedback +Status: Closed
 [2014-08-27 15:51 UTC] daverandom@php.net
The fix for this bug has been committed.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.

 For Windows:

http://windows.php.net/snapshots/
 
Thank you for the report, and for helping us make PHP better.


 [2014-12-22 14:00 UTC] boen dot robot at gmail dot com
I'd like to point out that this issue is still not resolved, as daverandom's fix was reverted due to the related issue askalski points to.

(So... the status shouldn't really be "Closed"...)

IMHO, that fix, while POTENTIALLY bad for performance (in SOME cases...), is better than no fix at all, because encrypted sockets are almost impossible to work with otherwise. When people use an encrypted connection, they are ready to sacrifice a little performance anyway.

A proper fix, I think, would involve a modification in PHP's stream API, where instead of just PHP_STREAM_AS_FD_FOR_SELECT, there would be PHP_STREAM_AS_FD_FOR_SELECT_READ, PHP_STREAM_AS_FD_FOR_SELECT_WRITE and PHP_STREAM_AS_FD_FOR_SELECT_OOB, allowing the OpenSSL extension (and potentially other streams) to flush the appropriate buffers, as opposed to "all" buffers, which is what the current fix is doing. The PHP_STREAM_AS_FD_FOR_SELECT constant could be modified to be a bitmask that OR's all those new ones, thus minimizing migration problems.
 [2015-02-09 13:38 UTC] sjaillet at gmail dot com
Is it possible have the status of this bug updated ? Thanks !
 [2015-02-09 19:28 UTC] requinix@php.net
-Status: Closed +Status: Re-Opened -Assigned To: ab +Assigned To:
 [2015-03-05 23:53 UTC] rdlowrey@php.net
-Assigned To: +Assigned To: rdlowrey
 [2015-03-06 01:02 UTC] rdlowrey@php.net
I have cherry-picked @DaveRandom's original SSL_pending() solution back into 5.6 and master. I'm fairly certain this commit solves the problem fully. It was unfortunately reverted due to its close proximity to some other buggy shenanigans.

This change will *not* appear in the forthcoming 5.6.7 release so that we have time to test, get feedback and verify that it works everywhere. If you're interested in this bug's resolution please build the current PHP-5.6 or master branch to verify that the issue is resolved.
 [2015-03-06 01:15 UTC] boen dot robot at gmail dot com
Thank you.

I don't have the setup (or know-how) to compile PHP myself, especially on Windows. If there were snapshot builds, I'd gladly test this, as well as the related #68853.

I guess I'll just have to wait for... 5.6.8 or whenever a release with that fix in it emerges, and report back then, if there are new issues.
(Considering how long this has been an issue, I don't mind...)
 [2015-03-06 17:35 UTC] rdlowrey@php.net
Windows snapshots should be available sometime in the next few hours here:

http://windows.php.net/downloads/snaps/php-5.6/

The commit to fix this was added after the final March 05 snapshot. As soon as a newer snapshot is available you should be able to download a usable windows binary to test. In my own tests this latest commit resolves the issue from the client/server test case in the related bug #68853.
 [2015-03-08 06:56 UTC] boen dot robot at gmail dot com
The snapshot build seems to have fixed the original issue I was encountering, but it seems it might have created a new one... Or maybe this one existed already, and I just hadn't managed witness it before.

Again only on an encrypted connection... If the server waits for more than default_socket_timeout before starting to receiving data, while in the mean time the client does a stream_select() with the connection in $w (i.e. it waits for the server to be ready to receive... supposedly), and $tv_sec being NULL, stream_select() just hangs at the client, and thus no sending is ever done.

Though for some reason, I can't duplicate it by modifying the echo client/server pair from bug #68853 accordingly... Feel free to close this particular issue. I'll create a new one if I can produce a more reliable test case than my current (somewhat large) original code that exhibits this new issue.
 [2015-03-08 07:10 UTC] rdlowrey@php.net
-Status: Re-Opened +Status: Closed
 [2015-03-11 12:35 UTC] arjen at react dot com
This patch triggers this bug again: https://bugs.php.net/bug.php?id=67965
 [2015-03-11 14:13 UTC] rdlowrey@php.net
-Status: Closed +Status: Re-Opened
 [2015-03-11 14:13 UTC] rdlowrey@php.net
This bug is driving me nuts -- the inherently blocking nature of the existing implementation makes this difficult to address without rewriting *a lot* of code. Thanks arjen ... Working on it ...
 [2015-03-11 15:51 UTC] rdlowrey@php.net
-Status: Re-Opened +Status: Closed
 [2015-03-11 15:51 UTC] rdlowrey@php.net
@arjen The blocking behavior inside stream_select() should be corrected now in upstream 5.5, 5.6 and master. Closing again ... let me know if you observe further issues.
 [2015-05-15 08:52 UTC] edhelas at movim dot eu
I still experience this issue that occurs when big chunk of data are received on the socket (and that are bigger than the defined buffer).

PHP-Version : 5.6.8+dfsg-1
OS : Debian Testing
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sun Nov 19 01:31:42 2017 UTC