Skip to content

Commit fd46416

Browse files
committed
Updated with SSL fixes (backported from trunk)
1 parent 4c5995b commit fd46416

File tree

1 file changed

+159
-45
lines changed

1 file changed

+159
-45
lines changed

ext/openssl/xp_ssl.c

Lines changed: 159 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC);
4141
SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC);
4242
int php_openssl_get_x509_list_id(void);
43+
static struct timeval subtract_timeval( struct timeval a, struct timeval b );
44+
static int compare_timeval( struct timeval a, struct timeval b );
45+
static size_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, size_t count TSRMLS_DC);
4346

4447
/* This implementation is very closely tied to the that of the native
4548
* sockets implemented in the core.
@@ -171,76 +174,199 @@ static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init
171174
return retry;
172175
}
173176

174-
175177
static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
178+
{
179+
return php_openssl_sockop_io( 0, stream, buf, count );
180+
}
181+
182+
static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
183+
{
184+
return php_openssl_sockop_io( 1, stream, buf, count );
185+
}
186+
187+
/**
188+
* Factored out common functionality (blocking, timeout, loop management) for read and write.
189+
* Perform IO (read or write) to an SSL socket. If we have a timeout, we switch to non-blocking mode
190+
* for the duration of the operation, using select to do our waits. If we time out, or we have an error
191+
* report that back to PHP
192+
*
193+
*/
194+
static size_t php_openssl_sockop_io(int read, php_stream *stream, char *buf, size_t count TSRMLS_DC)
176195
{
177196
php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
178-
int didwrite;
197+
int nr_bytes = 0;
179198

199+
/* Only do this if SSL is active. */
180200
if (sslsock->ssl_active) {
181201
int retry = 1;
202+
struct timeval start_time,
203+
*timeout;
204+
int blocked = sslsock->s.is_blocked,
205+
has_timeout = 0;
182206

183-
do {
184-
didwrite = SSL_write(sslsock->ssl_handle, buf, count);
207+
/* Begin by making the socket non-blocking. This allows us to check the timeout. */
208+
if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) {
209+
sslsock->s.is_blocked = 0;
210+
}
185211

186-
if (didwrite <= 0) {
187-
retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC);
188-
} else {
189-
break;
190-
}
191-
} while(retry);
212+
/* Get the timeout value (and make sure we are to check it. */
213+
timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout;
214+
has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec);
192215

193-
if (didwrite > 0) {
194-
php_stream_notify_progress_increment(stream->context, didwrite, 0);
195-
}
196-
} else {
197-
didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC);
216+
/* gettimeofday is not monotonic; using it here is not strictly correct */
217+
if (has_timeout) {
218+
gettimeofday(&start_time, NULL);
198219
}
199220

200-
if (didwrite < 0) {
201-
didwrite = 0;
202-
}
221+
/* Main IO loop. */
222+
do {
223+
struct timeval cur_time, elapsed_time, left_time;
203224

204-
return didwrite;
205-
}
225+
/* If we have a timeout to check, figure out how much time has elapsed since we started. */
226+
if (has_timeout) {
227+
gettimeofday(&cur_time, NULL);
206228

207-
static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
208-
{
209-
php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
210-
int nr_bytes = 0;
229+
/* Determine how much time we've taken so far. */
230+
elapsed_time = subtract_timeval( cur_time, start_time );
211231

212-
if (sslsock->ssl_active) {
213-
int retry = 1;
232+
/* and return an error if we've taken too long. */
233+
if (compare_timeval( elapsed_time, *timeout) > 0 ) {
234+
/* If the socket was originally blocking, set it back. */
235+
if (blocked) {
236+
php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC);
237+
sslsock->s.is_blocked = 1;
238+
}
239+
return -1;
240+
}
241+
}
214242

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

247+
if (sslsock->reneg && sslsock->reneg->should_close) {
248+
/* renegotiation rate limiting triggered */
249+
php_stream_xport_shutdown(stream, (stream_shutdown_t)SHUT_RDWR);
250+
nr_bytes = 0;
251+
stream->eof = 1;
252+
break;
253+
}
254+
} else {
255+
nr_bytes = SSL_write(sslsock->ssl_handle, buf, count);
256+
}
257+
258+
/* Now, how much time until we time out? */
259+
if (has_timeout) {
260+
left_time = subtract_timeval( *timeout, elapsed_time );
261+
}
262+
263+
/* If we didn't do anything on the last loop (or an error) check to see if we should retry or exit. */
218264
if (nr_bytes <= 0) {
265+
266+
/* Get the error code from SSL, and check to see if it's an error or not. */
267+
int err = SSL_get_error(sslsock->ssl_handle, nr_bytes );
219268
retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC);
269+
270+
/* If we get this (the above doesn't check) then we'll retry as well. */
271+
if (errno == EAGAIN && err == SSL_ERROR_WANT_READ && read) {
272+
retry = 1;
273+
}
274+
if (errno == EAGAIN && SSL_ERROR_WANT_WRITE && read == 0) {
275+
retry = 1;
276+
}
277+
278+
/* Also, on reads, we may get this condition on an EOF. We should check properly. */
279+
if (read) {
220280
stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle));
281+
}
221282

283+
/* Now, if we have to wait some time, and we're supposed to be blocking, wait for the socket to become
284+
* available. Now, php_pollfd_for uses select to wait up to our time_left value only...
285+
*/
286+
if (retry && blocked) {
287+
if (read) {
288+
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ?
289+
(POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL);
290+
} else {
291+
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ?
292+
(POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL);
293+
}
294+
}
222295
} else {
223-
/* we got the data */
296+
/* Else, if we got bytes back, check for possible errors. */
297+
int err = SSL_get_error(sslsock->ssl_handle, nr_bytes );
298+
299+
/* If we didn't get any error, then let's return it to PHP. */
300+
if (err == SSL_ERROR_NONE)
224301
break;
302+
303+
/* Otherwise, we need to wait again (up to time_left or we get an error) */
304+
if (blocked)
305+
if (read) {
306+
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_WRITE) ?
307+
(POLLOUT|POLLPRI) : (POLLIN|POLLPRI), has_timeout ? &left_time : NULL);
308+
} else {
309+
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ?
310+
(POLLIN|POLLPRI) : (POLLOUT|POLLPRI), has_timeout ? &left_time : NULL);
311+
}
225312
}
313+
/* Finally, we keep going until we got data, and an SSL_ERROR_NONE, unless we had an error. */
226314
} while (retry);
227315

316+
/* Tell PHP if we read / wrote bytes. */
228317
if (nr_bytes > 0) {
229318
php_stream_notify_progress_increment(stream->context, nr_bytes, 0);
230319
}
320+
321+
/* And if we were originally supposed to be blocking, let's reset the socket to that. */
322+
if (blocked) {
323+
php_set_sock_blocking(sslsock->s.socket, 1 TSRMLS_CC);
324+
sslsock->s.is_blocked = 1;
231325
}
232-
else
233-
{
326+
} else {
327+
/*
328+
* This block is if we had no timeout... We will just sit and wait forever on the IO operation.
329+
*/
330+
if (read) {
234331
nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC);
332+
} else {
333+
nr_bytes = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC);
235334
}
335+
}
236336

337+
/* PHP doesn't expect a negative return. */
237338
if (nr_bytes < 0) {
238339
nr_bytes = 0;
239340
}
240341

241342
return nr_bytes;
242343
}
243344

345+
struct timeval subtract_timeval( struct timeval a, struct timeval b )
346+
{
347+
struct timeval difference;
348+
349+
difference.tv_sec = a.tv_sec - b.tv_sec;
350+
difference.tv_usec = a.tv_usec - b.tv_usec;
351+
352+
if (a.tv_usec < b.tv_usec) {
353+
b.tv_sec -= 1L;
354+
b.tv_usec += 1000000L;
355+
}
356+
357+
return difference;
358+
}
359+
360+
int compare_timeval( struct timeval a, struct timeval b )
361+
{
362+
if (a.tv_sec > b.tv_sec || (a.tv_sec == b.tv_sec && a.tv_usec > b.tv_usec) ) {
363+
return 1;
364+
} else if( a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec ) {
365+
return 0;
366+
} else {
367+
return -1;
368+
}
369+
}
244370

245371
static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_DC)
246372
{
@@ -492,16 +618,9 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
492618

493619
if (has_timeout) {
494620
gettimeofday(&cur_time, NULL);
495-
elapsed_time.tv_sec = cur_time.tv_sec - start_time.tv_sec;
496-
elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec;
497-
if (cur_time.tv_usec < start_time.tv_usec) {
498-
elapsed_time.tv_sec -= 1L;
499-
elapsed_time.tv_usec += 1000000L;
500-
}
621+
elapsed_time = subtract_timeval( cur_time, start_time );
501622

502-
if (elapsed_time.tv_sec > timeout->tv_sec ||
503-
(elapsed_time.tv_sec == timeout->tv_sec &&
504-
elapsed_time.tv_usec > timeout->tv_usec)) {
623+
if (compare_timeval( elapsed_time, *timeout) > 0) {
505624
php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: crypto enabling timeout");
506625
return -1;
507626
}
@@ -517,12 +636,7 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
517636
struct timeval left_time;
518637

519638
if (has_timeout) {
520-
left_time.tv_sec = timeout->tv_sec - elapsed_time.tv_sec;
521-
left_time.tv_usec = timeout->tv_usec - elapsed_time.tv_usec;
522-
if (timeout->tv_usec < elapsed_time.tv_usec) {
523-
left_time.tv_sec -= 1L;
524-
left_time.tv_usec += 1000000L;
525-
}
639+
left_time = subtract_timeval( *timeout, elapsed_time );
526640
}
527641
php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ?
528642
(POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL);

0 commit comments

Comments
 (0)