Skip to content

Commit 01038fd

Browse files
author
Alexander Nozdrin
committed
A patch for Bug#17087862: THREAD POOL PLUGIN FAILS TO MAINTAIN
ENCRYPTED CONNECTION UNDER LOAD. The user visible problem was that an error in one connection might have affected other connections. When that happened, the other connection was closed by the server, thus the client received the "Lost connection" error. The server however didn't crash. Technically, the problem was that OpenSSL requires that the thread's error queue is empty before invoking any I/O operation (https://www.openssl.org/docs/ssl/SSL_get_error.html). That was not the case. The bug happened when the error queue is not empty and a non-blocking I/O-operation failed because it would block. YaSSL is not affected as it employs different scheme for handling errors. MySQL-5.5 is not affected as it uses blocking I/O calls. Test case is missing because the bug appeared under the highload and depended on thread scheduling.
1 parent befb557 commit 01038fd

File tree

4 files changed

+115
-28
lines changed

4 files changed

+115
-28
lines changed

mysys_ssl/my_rnd.cc

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
2+
Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
33
44
This program is free software; you can redistribute it and/or modify
55
it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@
2626

2727
#elif defined(HAVE_OPENSSL)
2828
#include <openssl/rand.h>
29+
#include <openssl/err.h>
2930
#endif /* HAVE_YASSL */
3031

3132

@@ -65,22 +66,32 @@ double my_rnd(struct rand_struct *rand_st)
6566

6667
double my_rnd_ssl(struct rand_struct *rand_st)
6768
{
68-
6969
#if defined(HAVE_YASSL) || defined(HAVE_OPENSSL)
7070
int rc;
7171
unsigned int res;
7272

73-
#if defined(HAVE_YASSL)
73+
#if defined(HAVE_YASSL) /* YaSSL */
7474
rc= yaSSL::RAND_bytes((unsigned char *) &res, sizeof (unsigned int));
75-
#else
75+
76+
if (!rc)
77+
return my_rnd(rand_st);
78+
#else /* OpenSSL */
7679
rc= RAND_bytes((unsigned char *) &res, sizeof (unsigned int));
80+
81+
if (!rc)
82+
{
83+
ERR_clear_error();
84+
return my_rnd(rand_st);
85+
}
7786
#endif /* HAVE_YASSL */
7887

79-
if (rc)
80-
return (double)res / (double)UINT_MAX;
81-
else
88+
return (double)res / (double)UINT_MAX;
89+
90+
#else /* !defined(HAVE_YASSL) && !defined(HAVE_OPENSSL) */
91+
92+
return my_rnd(rand_st);
93+
8294
#endif /* defined(HAVE_YASSL) || defined(HAVE_OPENSSL) */
83-
return my_rnd(rand_st);
8495
}
8596

8697
#ifdef __cplusplus

sql-common/client_authentication.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ RSA *rsa_init(MYSQL *mysql)
111111
fclose(pub_key_file);
112112
if (g_public_key == NULL)
113113
{
114+
ERR_clear_error();
114115
fprintf(stderr, "Public key is not in PEM format: '%s'\n",
115116
mysql->options.extension->server_public_key_path);
116117
return 0;
@@ -205,7 +206,10 @@ int sha256_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
205206
public_key= PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
206207
BIO_free(bio);
207208
if (public_key == 0)
209+
{
210+
ERR_clear_error();
208211
DBUG_RETURN(CR_ERROR);
212+
}
209213
got_public_key_from_server= true;
210214
}
211215

sql/sql_acl.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11567,6 +11567,13 @@ class Rsa_authentication_keys
1156711567
ERR_error_string_n(ERR_get_error(), error_buf, MYSQL_ERRMSG_SIZE);
1156811568
sql_print_error("Failure to parse RSA %s key (file exists): %s:"
1156911569
" %s", key_type, key_file_path.c_ptr(), error_buf);
11570+
11571+
/*
11572+
Call ERR_clear_error() just in case there are more than 1 entry in the
11573+
OpenSSL thread's error queue.
11574+
*/
11575+
ERR_clear_error();
11576+
1157011577
return true;
1157111578
}
1157211579

vio/viossl.c

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
1+
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
22
33
This program is free software; you can redistribute it and/or modify
44
it under the terms of the GNU General Public License as published by
@@ -106,18 +106,24 @@ static void ssl_set_sys_error(int ssl_error)
106106

107107

108108
/**
109-
Indicate whether a SSL I/O operation must be retried later.
109+
This function does two things:
110+
- it indicates whether a SSL I/O operation must be retried later;
111+
- it clears the OpenSSL error queue, thus the next OpenSSL-operation can be
112+
performed even after failed OpenSSL-call.
110113
111114
@param vio VIO object representing a SSL connection.
112115
@param ret Value returned by a SSL I/O function.
113-
@param event[out] The type of I/O event to wait/retry.
116+
@param event[out] The type of I/O event to wait/retry.
117+
@param ssl_errno_holder[out] The SSL error code.
114118
115119
@return Whether a SSL I/O operation should be deferred.
116120
@retval TRUE Temporary failure, retry operation.
117121
@retval FALSE Indeterminate failure.
118122
*/
119123

120-
static my_bool ssl_should_retry(Vio *vio, int ret, enum enum_vio_io_event *event)
124+
static my_bool ssl_should_retry(Vio *vio, int ret,
125+
enum enum_vio_io_event *event,
126+
unsigned long *ssl_errno_holder)
121127
{
122128
int ssl_error;
123129
SSL *ssl= vio->ssl_arg;
@@ -136,14 +142,22 @@ static my_bool ssl_should_retry(Vio *vio, int ret, enum enum_vio_io_event *event
136142
*event= VIO_IO_EVENT_WRITE;
137143
break;
138144
default:
139-
#ifndef DBUG_OFF
145+
#ifndef DBUG_OFF /* Debug build */
146+
/* Note: the OpenSSL error queue gets cleared in report_errors(). */
140147
report_errors(ssl);
148+
#else /* Release build */
149+
# ifndef HAVE_YASSL
150+
/* OpenSSL: clear the error queue. */
151+
ERR_clear_error();
152+
# endif
141153
#endif
142154
should_retry= FALSE;
143155
ssl_set_sys_error(ssl_error);
144156
break;
145157
}
146158

159+
*ssl_errno_holder= ssl_error;
160+
147161
return should_retry;
148162
}
149163

@@ -152,14 +166,30 @@ size_t vio_ssl_read(Vio *vio, uchar *buf, size_t size)
152166
{
153167
int ret;
154168
SSL *ssl= vio->ssl_arg;
169+
unsigned long ssl_errno_not_used;
170+
155171
DBUG_ENTER("vio_ssl_read");
156172

157-
while ((ret= SSL_read(ssl, buf, size)) < 0)
173+
while (1)
158174
{
159175
enum enum_vio_io_event event;
160176

177+
#ifndef HAVE_YASSL
178+
/*
179+
OpenSSL: check that the SSL thread's error queue is cleared. Otherwise
180+
SSL_read() returns an error from the error queue, when SSL_read() failed
181+
because it would block.
182+
*/
183+
DBUG_ASSERT(ERR_peek_error() == 0);
184+
#endif
185+
186+
ret= SSL_read(ssl, buf, size);
187+
188+
if (ret >= 0)
189+
break;
190+
161191
/* Process the SSL I/O error. */
162-
if (!ssl_should_retry(vio, ret, &event))
192+
if (!ssl_should_retry(vio, ret, &event, &ssl_errno_not_used))
163193
break;
164194

165195
/* Attempt to wait for an I/O event. */
@@ -175,14 +205,30 @@ size_t vio_ssl_write(Vio *vio, const uchar *buf, size_t size)
175205
{
176206
int ret;
177207
SSL *ssl= vio->ssl_arg;
208+
unsigned long ssl_errno_not_used;
209+
178210
DBUG_ENTER("vio_ssl_write");
179211

180-
while ((ret= SSL_write(ssl, buf, size)) < 0)
212+
while (1)
181213
{
182214
enum enum_vio_io_event event;
183215

216+
#ifndef HAVE_YASSL
217+
/*
218+
OpenSSL: check that the SSL thread's error queue is cleared. Otherwise
219+
SSL_write() returns an error from the error queue, when SSL_write() failed
220+
because it would block.
221+
*/
222+
DBUG_ASSERT(ERR_peek_error() == 0);
223+
#endif
224+
225+
ret= SSL_write(ssl, buf, size);
226+
227+
if (ret >= 0)
228+
break;
229+
184230
/* Process the SSL I/O error. */
185-
if (!ssl_should_retry(vio, ret, &event))
231+
if (!ssl_should_retry(vio, ret, &event, &ssl_errno_not_used))
186232
break;
187233

188234
/* Attempt to wait for an I/O event. */
@@ -277,23 +323,40 @@ typedef int (*ssl_handshake_func_t)(SSL*);
277323
@param vio VIO object representing a SSL connection.
278324
@param ssl SSL structure for the connection.
279325
@param func SSL handshake handler.
326+
@param ssl_errno_holder[out] The SSL error code.
280327
281328
@return Return value is 1 on success.
282329
*/
283330

284-
static int ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func)
331+
static int ssl_handshake_loop(Vio *vio, SSL *ssl,
332+
ssl_handshake_func_t func,
333+
unsigned long *ssl_errno_holder)
285334
{
286335
int ret;
287336

288337
vio->ssl_arg= ssl;
289338

290339
/* Initiate the SSL handshake. */
291-
while ((ret= func(ssl)) < 1)
340+
while (1)
292341
{
293342
enum enum_vio_io_event event;
294343

344+
#ifndef HAVE_YASSL
345+
/*
346+
OpenSSL: check that the SSL thread's error queue is cleared. Otherwise
347+
SSL-handshake-function returns an error from the error queue, when the
348+
function failed because it would block.
349+
*/
350+
DBUG_ASSERT(ERR_peek_error() == 0);
351+
#endif
352+
353+
ret= func(ssl);
354+
355+
if (ret >= 1)
356+
break;
357+
295358
/* Process the SSL I/O error. */
296-
if (!ssl_should_retry(vio, ret, &event))
359+
if (!ssl_should_retry(vio, ret, &event, ssl_errno_holder))
297360
break;
298361

299362
/* Wait for I/O so that the handshake can proceed. */
@@ -308,7 +371,8 @@ static int ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func)
308371

309372

310373
static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout,
311-
ssl_handshake_func_t func, unsigned long *errptr)
374+
ssl_handshake_func_t func,
375+
unsigned long *ssl_errno_holder)
312376
{
313377
int r;
314378
SSL *ssl;
@@ -320,7 +384,7 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout,
320384
if (!(ssl= SSL_new(ptr->ssl_context)))
321385
{
322386
DBUG_PRINT("error", ("SSL_new failure"));
323-
*errptr= ERR_get_error();
387+
*ssl_errno_holder= ERR_get_error();
324388
DBUG_RETURN(1);
325389
}
326390
DBUG_PRINT("info", ("ssl: 0x%lx timeout: %ld", (long) ssl, timeout));
@@ -345,10 +409,9 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout,
345409
yaSSL_transport_set_send_function(ssl, yassl_send);
346410
#endif
347411

348-
if ((r= ssl_handshake_loop(vio, ssl, func)) < 1)
412+
if ((r= ssl_handshake_loop(vio, ssl, func, ssl_errno_holder)) < 1)
349413
{
350414
DBUG_PRINT("error", ("SSL_connect/accept failure"));
351-
*errptr= SSL_get_error(ssl, r);
352415
SSL_free(ssl);
353416
DBUG_RETURN(1);
354417
}
@@ -395,17 +458,19 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout,
395458
}
396459

397460

398-
int sslaccept(struct st_VioSSLFd *ptr, Vio *vio, long timeout, unsigned long *errptr)
461+
int sslaccept(struct st_VioSSLFd *ptr, Vio *vio, long timeout,
462+
unsigned long *ssl_errno_holder)
399463
{
400464
DBUG_ENTER("sslaccept");
401-
DBUG_RETURN(ssl_do(ptr, vio, timeout, SSL_accept, errptr));
465+
DBUG_RETURN(ssl_do(ptr, vio, timeout, SSL_accept, ssl_errno_holder));
402466
}
403467

404468

405-
int sslconnect(struct st_VioSSLFd *ptr, Vio *vio, long timeout, unsigned long *errptr)
469+
int sslconnect(struct st_VioSSLFd *ptr, Vio *vio, long timeout,
470+
unsigned long *ssl_errno_holder)
406471
{
407472
DBUG_ENTER("sslconnect");
408-
DBUG_RETURN(ssl_do(ptr, vio, timeout, SSL_connect, errptr));
473+
DBUG_RETURN(ssl_do(ptr, vio, timeout, SSL_connect, ssl_errno_holder));
409474
}
410475

411476

0 commit comments

Comments
 (0)