Edit report at http://bugs.php.net/bug.php?id=46685&edit=1

 ID:                 46685
 Updated by:         cataphr...@php.net
 Reported by:        scott dot php at scottrix dot co dot uk
 Summary:            SSL code should use select
 Status:             Duplicate
 Type:               Bug
 Package:            OpenSSL related
 Operating System:   *
 PHP Version:        5.*, 6
 Block user comment: N
 Private report:     N

 New Comment:

This was fixed in 5.3.6 (see bug #53592)


Previous Comments:
------------------------------------------------------------------------
[2011-03-18 10:41:17] cataphr...@php.net

This was fixed in 5.3.6 (see bug #53592)

------------------------------------------------------------------------
[2010-07-21 22:53:31] voltara at gmail dot com

I have a patch against 5.3.2, but cannot post it because of the error
message "Please do not SPAM our bug system."  It's the same code, but in
the form of a diff applies cleanly.

------------------------------------------------------------------------
[2010-07-21 22:19:21] askalski at gmail dot com

I made a somewhat different patch.  As far as I can tell, the
busy-waiting only happens during the call to php_openssl_enable_crypto,
where the socket is forced nonblocking to support the connect timeout. 
My patch exposes the WANT_READ/WANT_WRITE error code through the return
value of handle_ssl_error(), and adds a select() to the SSL_connect
loop, using a deadline-based timeout calculation.



The diff is against 5.2.9, which is the version I need to fix.  It seems
to apply cleanly against 5.2.13, but I haven't tested it.  (I would need
to create a separate patch for 5.3, though.)



diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c

index c31a505..e0a72b1 100644

--- a/ext/openssl/xp_ssl.c

+++ b/ext/openssl/xp_ssl.c

@@ -92,19 +92,25 @@ static int handle_ssl_error(php_stream *stream, int
nr_bytes, zend_bool is_init

        char *ebuf = NULL, *wptr = NULL;

        size_t ebuf_size = 0;

        unsigned long code, ecode;

-       int retry = 1;

+       int retry = 0;

 

        switch(err) {

                case SSL_ERROR_ZERO_RETURN:

                        /* SSL terminated (but socket may still be active) */

-                       retry = 0;

                        break;

                case SSL_ERROR_WANT_READ:

                case SSL_ERROR_WANT_WRITE:

                        /* re-negotiation, or perhaps the SSL layer needs more

                         * packets: retry in next iteration */

                        errno = EAGAIN;

-                       retry = is_init ? 1 : sslsock->s.is_blocked;

+                       if (is_init || sslsock->s.is_blocked) {

+                               /* Return the actual error code, rather than 
just "1".  The
SSL_connect

+                                * call is non-blocking for connect_timeout 
support ("blocking with
timeout"

+                                * from the outside perspective.)  The caller 
needs to know whether
to

+                                * 'select' on read or write.

+                                */

+                               retry = err;

+                       }

                        break;

                case SSL_ERROR_SYSCALL:

                        if (ERR_peek_error() == 0) {

@@ -115,7 +121,6 @@ static int handle_ssl_error(php_stream *stream, int
nr_bytes, zend_bool is_init

                                        }

                                        SSL_set_shutdown(sslsock->ssl_handle,
SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);

                                        stream->eof = 1;

-                                       retry = 0;

                                } else {

                                        char *estr = 
php_socket_strerror(php_socket_errno(), NULL, 0);

 

@@ -123,7 +128,6 @@ static int handle_ssl_error(php_stream *stream, int
nr_bytes, zend_bool is_init

                                                        "SSL: %s", estr);

 

                                        efree(estr);

-                                       retry = 0;

                                }

                                break;

                        }

@@ -137,7 +141,6 @@ static int handle_ssl_error(php_stream *stream, int
nr_bytes, zend_bool is_init

                        switch (ERR_GET_REASON(ecode)) {

                                case SSL_R_NO_SHARED_CIPHER:

                                        php_error_docref(NULL TSRMLS_CC, 
E_WARNING,
"SSL_R_NO_SHARED_CIPHER: no suitable shared cipher could be used.  This
could be because the server is missing an SSL certificate (local_cert
context option)");

-                                       retry = 0;

                                        break;

 

                                default:

@@ -175,7 +178,6 @@ static int handle_ssl_error(php_stream *stream, int
nr_bytes, zend_bool is_init

                                        }

                        }

                                

-                       retry = 0;

                        errno = 0;

        }

        return retry;

@@ -391,12 +393,9 @@ static inline int
php_openssl_enable_crypto(php_stream *stream,

                php_stream_xport_crypto_param *cparam

                TSRMLS_DC)

 {

-       int n, retry = 1;

+       int n, retry;

 

        if (cparam->inputs.activate && !sslsock->ssl_active) {

-               float timeout = sslsock->connect_timeout.tv_sec +
sslsock->connect_timeout.tv_usec / 1000000;

-               int blocked = sslsock->s.is_blocked;

-

                if (!sslsock->state_set) {

                        if (sslsock->is_client) {

                                SSL_set_connect_state(sslsock->ssl_handle);

@@ -406,36 +405,85 @@ static inline int
php_openssl_enable_crypto(php_stream *stream,

                        sslsock->state_set = 1;

                }

        

-               if (sslsock->is_client && SUCCESS ==
php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) {

-                       sslsock->s.is_blocked = 0;

-               }

-               do {

-                       if (sslsock->is_client) {

-                               struct timeval tvs, tve;

-                               struct timezone tz;

+               if (sslsock->is_client) {

+                       int blocked = sslsock->s.is_blocked;

+                       struct timeval deadline, timeout;

+

+                       /* temporarily force non-blocking, to support 
connection timeout */

+                       if (sslsock->s.is_blocked && SUCCESS ==
php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) {

+                               sslsock->s.is_blocked = 0;

+                       }

+

+                       /* set a deadline for completing negotiation, based on
connect_timeout */

+                       gettimeofday(&deadline, NULL);

+                       deadline.tv_sec += sslsock->connect_timeout.tv_sec;

+                       deadline.tv_usec += sslsock->connect_timeout.tv_usec;

+                       if (deadline.tv_usec > 1000000) {

+                               deadline.tv_usec -= 1000000;

+                               deadline.tv_sec++;

+                       }

+

+                       while ((n = SSL_connect(sslsock->ssl_handle)) <= 0 &&

+                              (retry = handle_ssl_error(stream, n, 1 
TSRMLS_CC)))

+                       {

+                               fd_set fds;

+

+                               FD_ZERO(&fds);

+                               FD_SET(sslsock->s.socket, &fds);

+

+                               /* calculate select timeout based on deadline 
vs current time */

+                               gettimeofday(&timeout, NULL);

+                               timeout.tv_sec = deadline.tv_sec - 
timeout.tv_sec;

+                               timeout.tv_usec = deadline.tv_usec - 
timeout.tv_usec;

+                               if (timeout.tv_usec < 0) {

+                                       timeout.tv_usec += 1000000;

+                                       timeout.tv_sec--;

+                               }

+

+                               /* if there's any time left, select() on read 
or write */

+                               if (timeout.tv_sec >= 0) {

+                                       if (retry == SSL_ERROR_WANT_READ) {

+                                               n = select(sslsock->s.socket + 
1, &fds, NULL, NULL, &timeout);

+                                       }

+                                       else {

+                                               n = select(sslsock->s.socket + 
1, NULL, &fds, NULL, &timeout);

+                                       }

+                               }

+                               else {

+                                       n = 0;

+                               }

+

+                               /* handle errors */

+                               if (n <= 0) {

+                                       if (n == 0) {

+                                               errno = ETIMEDOUT;

+                                               php_error_docref(NULL 
TSRMLS_CC, E_WARNING, "SSL: connection
timeout");

+                                       }

+                                       else {

+                                               char *estr = 
php_socket_strerror(php_socket_errno(), NULL, 0);

+                                               php_error_docref(NULL 
TSRMLS_CC, E_WARNING,

+                                                               "SSL: %s", 
estr);

+                                               efree(estr);

+                                       }

 

-                               gettimeofday(&tvs, &tz);

-                               n = SSL_connect(sslsock->ssl_handle);

-                               gettimeofday(&tve, &tz);

+                                       if (sslsock->s.is_blocked != blocked && 
SUCCESS ==
php_set_sock_blocking(sslsock->s.socket, blocked TSRMLS_CC)) {

+                                               sslsock->s.is_blocked = blocked;

+                                       }

 

-                               timeout -= (tve.tv_sec + (float) tve.tv_usec / 
1000000) -
(tvs.tv_sec + (float) tvs.tv_usec / 1000000);

-                               if (timeout < 0) {

-                                       php_error_docref(NULL TSRMLS_CC, 
E_WARNING, "SSL: connection
timeout");

                                        return -1;

                                }

-                       } else {

-                               n = SSL_accept(sslsock->ssl_handle);

                        }

 

-                       if (n <= 0) {

-                               retry = handle_ssl_error(stream, n, 1 
TSRMLS_CC);

-                       } else {

-                               break;

+                       if (sslsock->s.is_blocked != blocked && SUCCESS ==
php_set_sock_blocking(sslsock->s.socket, blocked TSRMLS_CC)) {

+                               sslsock->s.is_blocked = blocked;

                        }

-               } while (retry);

+               }

+               else {

+                       n = SSL_accept(sslsock->ssl_handle);

 

-               if (sslsock->is_client && sslsock->s.is_blocked != blocked && 
SUCCESS
== php_set_sock_blocking(sslsock->s.socket, blocked TSRMLS_CC)) {

-                       sslsock->s.is_blocked = blocked;

+                       if (n <= 0) {

+                               handle_ssl_error(stream, n, 1 TSRMLS_CC);

+                       }

                }

 

                if (n == 1) {

------------------------------------------------------------------------
[2010-07-21 16:56:39] askalski at gmail dot com

Any progress on this ticket?  We've begun encountering this bug somewhat
regularly, whenever one of our third-party SOAP services has problems. 
The symptom is our entire cluster of PHP servers redlines the CPU for
the duration of the SOAP outage.



Do you still need a test case reduction?  I have reproduced the issue in
the latest PHP 5.2 and 5.3 releases.



In the meantime, I am going to look over the original submitter's
proposed patch, and use that to fix our immediate problem.

------------------------------------------------------------------------
[2009-11-27 18:44:53] paj...@php.net

assigned to Rasmus, looks like he is willing to figure out the cause and
improve this patch.

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


The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at

    http://bugs.php.net/bug.php?id=46685


-- 
Edit this bug report at http://bugs.php.net/bug.php?id=46685&edit=1

Reply via email to