Skip to content

Updated openSSL to fix timout issue on SSL streams - Bug #61285 #264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 176 additions & 69 deletions ext/openssl/xp_ssl.c
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -1647,16 +1647,9 @@ static int php_openssl_enable_crypto(php_stream *stream,

if (has_timeout) {
gettimeofday(&cur_time, NULL);
elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec;
elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec;
if (cur_time.tv_usec < start_time.tv_usec) {
elapsed_time.tv_sec -= 1L;
elapsed_time.tv_usec += 1000000L;
}
elapsed_time = subtractTimeval( cur_time, start_time );

if (elapsed_time.tv_sec > timeout->tv_sec ||
(elapsed_time.tv_sec == timeout->tv_sec &&
elapsed_time.tv_usec > timeout->tv_usec)) {
if (compareTimeval( elapsed_time, timeout) > 0) {
php_error_docref(NULL, E_WARNING, "SSL: Handshake timed out");
return -1;
}
Expand All @@ -1672,12 +1665,7 @@ static int php_openssl_enable_crypto(php_stream *stream,
struct timeval left_time;

if (has_timeout) {
left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec;
left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec;
if (timeout->tv_usec < elapsed_time.tv_usec) {
left_time.tv_sec -= 1L;
left_time.tv_usec += 1000000L;
}
left_time = subtractTimeval( timeout, elapsed_time );
}
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ?
(POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL);
Expand Down Expand Up @@ -1742,82 +1730,201 @@ static int php_openssl_enable_crypto(php_stream *stream,
return -1;
}

static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count) /* {{{ */
static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) /* {{{ */
{
php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
int didwrite;

if (sslsock->ssl_active) {
int retry = 1;
return php_openssl_sockop_io( true, stream, buf, count );
}
/* }}} */

static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) /* {{{ */
{
return php_openssl_sockop_io( false, stream, buf, count );
}
/* }}} */

/**
* Factored out common functionality (blocking, timeout, loop management) for read and write.
* Perform IO (read or write) to an SSL socket. If we have a timeout, we switch to non-blocking mode
* for the duration of the operation, using select to do our waits. If we time out, or we have an error
* report that back to PHP
*
*/
static size_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, size_t count TSRMLS_DC) /* {{{ */
{
php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
int nr_bytes = 0;

/* Only do this if SSL is active. */
if (sslsock->ssl_active) {
int retry = 1;
struct timeval start_time,
*timeout;
int blocked = sslsock->s.is_blocked,
has_timeout = 0;

/* Begin by making the socket non-blocking. This allows us to check the timeout. */
if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) {
sslsock->s.is_blocked = 0;
}

/* Get the timeout value (and make sure we are to check it. */
timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout;
has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec);

/* gettimeofday is not monotonic; using it here is not strictly correct */
if (has_timeout) {
gettimeofday(&start_time, NULL);
}

/* Main IO loop. */
do {
didwrite = SSL_write(sslsock->ssl_handle, buf, count);
struct timeval cur_time,
elapsed_time;

/* If we have a timeout to check, figure out how much time has elapsed since we started. */
if (has_timeout) {
gettimeofday(&cur_time, NULL);

/* Determine how much time we've taken so far. */
elapsed_time = subtractTimeval( curr_time, start_time );

if (didwrite <= 0) {
retry = handle_ssl_error(stream, didwrite, 0);
/* and return an error if we've taken too long. */
if (compareTimeval( elapsed_time, timeout) > 0 ) {
/* If the socket was originally blocking, set it back. */
if (blocked) {
php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC);
sslsock->s.is_blocked = 1;
}
return -1;
}
}

/* Now, do the IO operation. Don't block if we can't complete... */
if (read) {
nr_bytes = SSL_read(sslsock->ssl_handle, buf, count);

if (sslsock->reneg && sslsock->reneg->should_close) {
/* renegotiation rate limiting triggered */
php_stream_xport_shutdown(stream, (stream_shutdown_t)SHUT_RDWR);
nr_bytes = 0;
stream->eof = 1;
break;
}
} else {
break;
nr_bytes = SSL_write(sslsock->ssl_handle, buf, count);
}
} while(retry);

if (didwrite > 0) {
php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0);
}
} else {
didwrite = php_stream_socket_ops.write(stream, buf, count);
}
/* Now, how much time until we time out? */
struct timeval left_time;
if (has_timeout) {
left_time = subtractTimeval( timeout, elapsed_time );
}

if (didwrite < 0) {
didwrite = 0;
}

return didwrite;
}
/* }}} */
/* If we didn't do anything on the last loop (or an error) check to see if we should retry or exit. */
if (nr_bytes <= 0) {

static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count) /* {{{ */
{
php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
int nr_bytes = 0;
/* Get the error code from SSL, and check to see if it's an error or not. */
int err = SSL_get_error(sslsock->ssl_handle, nr_bytes );
retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC);

if (sslsock->ssl_active) {
int retry = 1;
/* If we get this (the above doesn't check) then we'll retry as well. */
if (errno == EAGAIN && ( err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE ) ) {
retry = 1;
}

do {
nr_bytes = SSL_read(sslsock->ssl_handle, buf, count);
/* Also, on reads, we may get this condition on an EOF. We should check properly. */
if (read) {
stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle));
}

if (sslsock->reneg && sslsock->reneg->should_close) {
/* renegotiation rate limiting triggered */
php_stream_xport_shutdown(stream, (stream_shutdown_t)SHUT_RDWR);
nr_bytes = 0;
stream->eof = 1;
break;
} else if (nr_bytes <= 0) {
retry = handle_ssl_error(stream, nr_bytes, 0);
stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle));

/* Now, if we have to wait some time, and we're supposed to be blocking, wait for the socket to become
* available. Now, php_pollfd_for uses select to wait up to our time_left value only...
*/
if (retry && blocked) {
if (read) {
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ?
(POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL);
} else {
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ?
(POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL);
}
}
} else {
/* we got the data */
break;
/* Else, if we got bytes back, check for possible errors. */
int err = SSL_get_error(sslsock->ssl_handle, nr_bytes );

/* If we didn't get any error, then let's return it to PHP. */
if (err == SSL_ERROR_NONE)
break;

/* Otherwise, we need to wait again (up to time_left or we get an error) */
if (blocked)
if (read) {
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ?
(POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL);
} else {
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ?
(POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL);
}
}
/* Finally, we keep going until we got data, and an SSL_ERROR_NONE, unless we had an error. */
} while (retry);

/* Tell PHP if we read / wrote bytes. */
if (nr_bytes > 0) {
php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0);
php_stream_notify_progress_increment(stream->context, nr_bytes, 0);
}
}
else
{
nr_bytes = php_stream_socket_ops.read(stream, buf, count);
}

if (nr_bytes < 0) {
nr_bytes = 0;
/* And if we were originally supposed to be blocking, let's reset the socket to that. */
if (blocked) {
php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC);
sslsock->s.is_blocked = 1;
}
} else {
/*
* This block is if we had no timeout... We will just sit and wait forever on the IO operation.
*/
if (read) {
nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC);
} else {
nr_bytes = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC);
}
}

/* PHP doesn't expect a negative return. */
if (nr_bytes < 0) {
nr_bytes = 0;
}

return nr_bytes;
}
/* }}} */

struct timeval subtractTimeval( struct timeval a, struct timeval b )
{
timeval difference;

difference.tv_sec = a.tv_sec - b.tv_sec;
difference.tv_usec = a.tv_usec - b.tv_usec;

if (a.tv_usec < b.tv_usec) {
b.tv_sec -= 1L;
b.tv_usec += 1000000L;
}

return nr_bytes;
return difference;
}

int compareTimeval( struct timeval a, struct timeval b )
{
if (a.tv_sec > b.tv_sec || (a.tv_sec == b.tv_sec && a.tv_usec > b.tv_usec) ) {
return 1;
} else if( a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec ) {
return 0;
} else {
return -1;
}
}
/* }}} */

static int php_openssl_sockop_close(php_stream *stream, int close_handle) /* {{{ */
{
Expand Down