php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #46917 Socket handling completely broken
Submitted: 2008-12-21 16:03 UTC Modified: 2009-10-26 14:15 UTC
Votes:6
Avg. Score:3.8 ± 0.9
Reproduced:6 of 6 (100.0%)
Same Version:5 (83.3%)
Same OS:5 (83.3%)
From: jost_boekemeier at users dot sf dot net Assigned:
Status: Open Package: Streams related
PHP Version: 5.2.8 OS: win32 only
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2008-12-21 16:03 UTC] jost_boekemeier at users dot sf dot net
Description:
------------
PHP cannot handle broken socket connections due to an uninitialized variable.

xp_socket.c and several other places contain the following pattern:

...
} else if (php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) {
 if (0 == recv(sock->socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EAGAIN) {
	alive = 0;
}
...

It is obvious that the above code cannot work on any operating system; checking if the socket doesn't have an error and then asking for its error code is simply nonsense.

The same pattern is used in several other places within PHP.


The above code fails constantly on Windows. On Linux/Unix a workaround is to add the constant 1E512 to the PHP script, which initializes errno with a value != EAGAIN.


Regards,
Jost Bökemeier




Reproduce code:
---------------
pfsockopen() 
...
// restart back end
...
pfsockopen()

=>



Expected result:
----------------
...

poll([{fd=16, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=16, revents=POLLIN}])
recv(16, ""..., 1, MSG_PEEK)            = 0
close(16)    

...

Actual result:
--------------
...

poll([{fd=16, events=POLLIN|POLLERR|POLLHUP}], 1, -1) = 1 ([{fd=16, revents=POLLIN|POLLERR|POLLHUP}])
recv(16, ""..., 8192, 0)                = 0
gettimeofday({1229844164, 46391}, NULL) = 0
write(2, "[Sun Dec 21 08:22:44 2008] [error] [client 127.0.0.1] PHP Notice:  Undefined index:  content_length in 

...

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2008-12-21 16:07 UTC] jost_boekemeier at users dot sf dot net
The relevant part of the bug trace was missing. 

poll([{fd=16, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=16, revents=POLLIN}])
recv(16, ""..., 1, MSG_PEEK)            = 0
send(16, "PUT /JavaBridge/JavaBridge.phpjavabridge HTTP/1.1\r\nHost: localhost\r\nContent-Length: 40\r\nX_JAVABRIDGE_CHANNEL: /dev/shm/.php_java_bridgexN2WsO\r\n\r\n\177C<H p=\"1\" v=\"php.java.bridge.Util\"></H>"..., 185, 0) = 185
poll([{fd=16, events=POLLIN|POLLERR|POLLHUP}], 1, -1) = 1 ([{fd=16, revents=POLLIN|POLLERR|POLLHUP}])
recv(16, ""..., 8192, 0)                = 0
 [2008-12-24 19:48 UTC] felipe@php.net
Thank you for this bug report. To properly diagnose the problem, we
need a short but complete example script to be able to reproduce
this bug ourselves. 

A proper reproducing script starts with <?php and ends with ?>,
is max. 10-20 lines long and does not require any external 
resources such as databases, etc. If the script requires a 
database to demonstrate the issue, please make sure it creates 
all necessary tables, stored procedures etc.

Please avoid embedding huge scripts into the report.


 [2008-12-26 17:28 UTC] jost_boekemeier at users dot sf dot net
Due to its nature (uninitialized variable) you may or may not be able to reproduce this bug. See http://sourceforge.net/mailarchive/forum.php?thread_name=828E3F73B78941EAB5AF3E2E07C37D8D%40IBM1020C944423&forum_name=php-java-bridge-users for details.

However, you should immediately see the bug by looking at the PHP source code: 

  0 == recv(sock->socket, &buf, sizeof(buf), MSG_PEEK

does NOT set errno (the result code is 0, not -1) so that errno contains a bogus value, most likely the error code from a previously failed sys call, so that

  php_socket_errno() != EAGAIN

fails, depending on the application's PHP code, as errno sometimes contains EAGAIN (from a previous poll()).


Just search the PHP sources for the pattern


} else if (php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) >
0) {
 if (0 == recv(sock->socket, &buf, sizeof(buf), MSG_PEEK) &&
php_socket_errno() != EAGAIN) {
	alive = 0;
}


and set the errno or the lastErrorCode (for windows) to zero before calling this pattern. 

After that persistent sockets, soap and a few other places will work reliably.

There are a few caveats, however. First, if you pass the last error number to application-level, you might have to restore the last errno immediately after the recv call. Second, I don't know what the php_socket_errno() != EAGAIN should do, anyway, as EAGAIN is only set when the previous sys call failed (result code -1, not 0!). So I suggest to ask the author to explain his/her code before fixing anything.


Regards,
Jost Bökemeier
 [2009-01-02 21:31 UTC] felipe@php.net
Hi, I've commited a probable fix, I initialized the errno.
http://news.php.net/php.cvs/55296

Can you test it with a cvs version again?
 [2009-01-03 16:45 UTC] jost_boekemeier at users dot sf dot net
Yes, this patch fixes the problem on Linux.

What about Windows?



Regards,
Jost Boekemeier
 [2009-01-03 17:00 UTC] felipe@php.net
This bug has been fixed in CVS.

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/.
 
Thank you for the report, and for helping us make PHP better.

I added the Win part minutes ago:
-#ifndef PHP_WIN32
+/* Reseting/initializing */
+#ifdef PHP_WIN32
+	WSASetLastError(0);
+#else
 	errno = 0;
 #endif

Ok, then, closed. Thanks.
 [2009-01-06 16:15 UTC] jost_boekemeier at users dot sf dot net
Hi,

the code is okay for Linux, but on Windows it fails constantly.

+	WSASetLastError(0);
 	n = select(max_fd + 1, &rset, &wset, &eset, timeout >= 0 ? &tv : NULL);

IMHO this means that select always returns EAGAIN on windows, so that the following test cannot detect a broken connection. As I've said, I am not sure what the EAGAIN test should do, anyway.


To reproduce this: 

1) start a simple socket server on windows >= xp, call pfsockopen() and let the PHP instance return to the HTTP server's pool w/o closing the connection

2) kill the socket server and start it again

3) pull the PHP instance from the pool and call pfsockopen() again. pfsockopen() will use the broken connection until you kill the pool (i.e.: restart apache or IIS).


Regards,
Jost Bökemeier
 [2009-01-06 17:41 UTC] felipe@php.net
Currently the WSASetLastError(0); already exists for Windows in the code.
 [2009-01-06 19:15 UTC] jost_boekemeier at users dot sf dot net
Well, the initialization is okay now.

However, the code still doesn't work on windows. Which means that there's another bug.

The php_socket_errno() != EAGAIN looks 	suspicious.

"Depending on whether your socket is blocking or non-blocking, you 
either get FD_CLOSE notification, or recv() returns 0 (graceful 
disconnection), or recv() returns WSAECONNRESET error."

I don' see how the current code handles these three cases properly.
 [2009-01-06 19:51 UTC] jost_boekemeier at users dot sf dot net
The windows equivalent to EAGAIN is EWOULDBLOCK or WSAEWOULDBLOCK. 

Could it be that EAGAIN is 0 on windows?


Unfortunately I don't have the time and resources to debug this at the moment.
 [2009-01-07 20:44 UTC] felipe@php.net
I changed the EGAIN to EWOULDBLOCK in the checking.

http://news.php.net/php.cvs/55434
 [2009-01-10 16:12 UTC] jost_boekemeier at users dot sf dot net
Here's a test case:

----------------- TestServer.java ------------------
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;


public class TestServer {

    public static void main(String args[]) throws Exception {
	ServerSocket ss = new ServerSocket (9090);
	System.out.println("accepting connections");
	Socket s = ss.accept();
	System.out.println("got initial request");
	InputStream in = s.getInputStream();
	OutputStream out = s.getOutputStream();
	while (true) {
	    out.write((byte)in.read());
	    out.flush();
	    System.out.println("waiting for next request");
	}
	
    }
    
}
---------TestClient.php--------------
<?php

function dieWithMsg() {
  var_dump(error_get_last());
  exit(1);
}

$errno = null; $errstr = null;
$conn = pfsockopen("127.0.0.1", 9090, $errno, $errstr, 30) or dieWithMsg();
fwrite($conn, "\0") or dieWithMsg();
fread($conn, 1) or dieWithMsg();
echo "done";
exit(0);

?>

------------------------------------

To reproduce this bug on Windows XP and above, start the server with:

  java TestServer

and refresh the

   http://127.0.0.1/TestClient.php

a few times.

Then stop TestServer and start it again.

Refresh 

   http://127.0.0.1/TestClient.php

to get a broken connection from PHP.


I was able to reproduce this bug with yesterday's 5.2 windows snapshot.


Regards,
Jost Bökemeier
 [2009-10-24 00:57 UTC] srinatar@php.net
hi
 please refer to bug #49447 (http://bugs.php.net/bug.php?id=49447) where I have attempted to resolve this issue.

 i am not sure, if you tried with php 5.2.11 or with recent snapshot and let me know if this resolved your issue
 [2009-10-26 14:06 UTC] jost_boekemeier at users dot sf dot net
Using the test above I get:

array(4) { ["type"]=>  int(8) ["message"]=>  string(112) "fwrite(): send of 1 bytes failed with errno=10054 Eine vorhandene Verbindung wurde vom Remotehost geschlossen. " 

So the bug is still there, I think. pfsockopen should transparently check the socket error code and allocate a new connection if the previous persistent connection has an error.

I have tested this on Win XP and PHP 5.2.11 download.
 [2009-10-26 14:15 UTC] jost_boekemeier at users dot sf dot net
Err, 5.2.11 still uses the old code, doesn't it?

With 5.3dev, Build Date 	Oct 26 2009 13:56:57, I get:

array(4) { ["type"]=> int(2) ["message"]=> string(346) "fwrite(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'Europe/Paris' for '1.0/no DST' instead" ["file"]=> string(89) "C:\Programme\Apache Software Foundation\Tomcat 6.0\webapps\JavaBridgeTemplate554\test.php" ["line"]=> int(11) }
 
PHP Copyright © 2001-2018 The PHP Group
All rights reserved.
Last updated: Wed Jul 18 06:01:24 2018 UTC