diff options
author | Daniel Lowrey <rdlowrey@php.net> | 2015-02-09 11:44:09 -0500 |
---|---|---|
committer | Daniel Lowrey <rdlowrey@php.net> | 2015-02-09 11:44:09 -0500 |
commit | fb2314798e28b4970cb35497ebc615dffc48d930 (patch) | |
tree | 6127dc451f7b49ce561b74fe007470cfc4ab3276 | |
parent | 3d1ec33f96de25abdcc5bb239e86e98ac278f955 (diff) | |
parent | f2b12424c09cc27390e27fded86af2e4cdbe279b (diff) | |
download | php-git-fb2314798e28b4970cb35497ebc615dffc48d930.tar.gz |
Merge branch 'PHP-5.5' into PHP-5.6
Conflicts:
ext/openssl/xp_ssl.c
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | ext/openssl/xp_ssl.c | 223 |
2 files changed, 170 insertions, 56 deletions
@@ -5,6 +5,9 @@ - ODBC: . Bug #68964 (Allowed memory size exhausted with odbc_exec). (Anatol) +- OpenSSL: + . Fix bug #61285, #68329, #68046, #41631: encrypted streams don't observe + socket timeouts (Brad Broerman) ?? Feb 2015, PHP 5.6.6 diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 4811e803cd..70ec4591d7 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -78,6 +78,9 @@ extern php_stream* php_openssl_get_stream_from_ssl_handle(const SSL *ssl); extern int php_openssl_x509_fingerprint(X509 *peer, const char *method, zend_bool raw, char **out, int *out_len TSRMLS_DC); extern int php_openssl_get_ssl_stream_data_index(); extern int php_openssl_get_x509_list_id(void); +static struct timeval subtract_timeval( struct timeval a, struct timeval b ); +static int compare_timeval( struct timeval a, struct timeval b ); +static size_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, size_t count TSRMLS_DC); php_stream_ops php_openssl_socket_ops; @@ -1679,16 +1682,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 = subtract_timeval( 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 (compare_timeval( elapsed_time, *timeout) > 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: Handshake timed out"); return -1; } @@ -1704,12 +1700,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 = subtract_timeval( *timeout, elapsed_time ); } php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL); @@ -1774,75 +1765,169 @@ static int php_openssl_enable_crypto(php_stream *stream, return -1; } +static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)/* {{{ */ +{ + return php_openssl_sockop_io(1, stream, buf, count TSRMLS_CC); +} +/* }}} */ + static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) /* {{{ */ { + return php_openssl_sockop_io(0, stream, (char*)buf, count TSRMLS_CC); +} +/* }}} */ + +/** + * 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 didwrite; + int nr_bytes = 0; + /* Only do this if SSL is active. */ if (sslsock->ssl_active) { int retry = 1; + struct timeval start_time; + struct timeval *timeout; + int blocked = sslsock->s.is_blocked; + int has_timeout = 0; - do { - didwrite = SSL_write(sslsock->ssl_handle, buf, count); + /* 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; + } - if (didwrite <= 0) { - retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC); - } else { - break; - } - } while(retry); + /* 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); - if (didwrite > 0) { - php_stream_notify_progress_increment(stream->context, didwrite, 0); + /* gettimeofday is not monotonic; using it here is not strictly correct */ + if (has_timeout) { + gettimeofday(&start_time, NULL); } - } else { - didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); - } - if (didwrite < 0) { - didwrite = 0; - } + /* Main IO loop. */ + do { + struct timeval cur_time, elapsed_time, left_time; - return didwrite; -} -/* }}} */ + /* If we have a timeout to check, figure out how much time has elapsed since we started. */ + if (has_timeout) { + gettimeofday(&cur_time, NULL); -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 nr_bytes = 0; + /* Determine how much time we've taken so far. */ + elapsed_time = subtract_timeval( cur_time, start_time ); - if (sslsock->ssl_active) { - int retry = 1; + /* and return an error if we've taken too long. */ + if (compare_timeval( 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; + } + } - do { - nr_bytes = SSL_read(sslsock->ssl_handle, buf, count); + /* 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 TSRMLS_CC); - nr_bytes = 0; - stream->eof = 1; - break; - } else if (nr_bytes <= 0) { + if (sslsock->reneg && sslsock->reneg->should_close) { + /* renegotiation rate limiting triggered */ + php_stream_xport_shutdown(stream, (stream_shutdown_t)SHUT_RDWR TSRMLS_CC); + nr_bytes = 0; + stream->eof = 1; + break; + } + } else { + nr_bytes = SSL_write(sslsock->ssl_handle, buf, count); + } + + /* Now, how much time until we time out? */ + if (has_timeout) { + left_time = subtract_timeval( *timeout, elapsed_time ); + } + + /* 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) { + + /* 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 we get this (the above doesn't check) then we'll retry as well. */ + if (errno == EAGAIN && err == SSL_ERROR_WANT_READ && read) { + retry = 1; + } + if (errno == EAGAIN && SSL_ERROR_WANT_WRITE && read == 0) { + retry = 1; + } + + /* 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)); + } + /* 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 */ + /* 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(stream->context, nr_bytes, 0); } - } - else - { - nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); + + /* 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; } @@ -1851,6 +1936,32 @@ static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t coun } /* }}} */ +struct timeval subtract_timeval( struct timeval a, struct timeval b ) +{ + struct 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 difference; +} + +int compare_timeval( 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 TSRMLS_DC) /* {{{ */ { php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; @@ -2168,7 +2279,7 @@ static long get_crypto_method(php_stream_context *ctx, long crypto_method) if (ctx && php_stream_context_get_option(ctx, "ssl", "crypto_method", &val) == SUCCESS) { convert_to_long_ex(val); crypto_method = (long)Z_LVAL_PP(val); - crypto_method |= STREAM_CRYPTO_IS_CLIENT; + crypto_method |= STREAM_CRYPTO_IS_CLIENT; } return crypto_method; |