Fix "sslv3 alert unexpected message" errors in SSL renegotiation.
authorHeikki Linnakangas <[email protected]>
Thu, 5 Feb 2015 20:44:58 +0000 (22:44 +0200)
committerHeikki Linnakangas <[email protected]>
Mon, 23 Feb 2015 11:51:03 +0000 (13:51 +0200)
There seems to be a bug in OpenSSL renegotiation, so that when SSL_write()
needs to read data to complete the handshake, but it receives application
data instead, it gets confused and throws an "unexpected message" error. To
work around that, don't let SSL_write() to read data from the socket, even
if some is available. Arrange so that SSL_read() processes all incoming
messages instead.

Reviewed by Andres Freund

src/interfaces/libpq/fe-misc.c
src/interfaces/libpq/fe-secure-openssl.c

index 25aecc2f144de93d1949be0928c4063b96506178..7757ddbbc3b054b14a6862560e2c35f68d458e93 100644 (file)
@@ -910,11 +910,14 @@ pqSendSome(PGconn *conn, int len)
                         * to clear the channel eventually because it's blocked trying to
                         * send data to us.  (This can happen when we are sending a large
                         * amount of COPY data, and the server has generated lots of
-                        * NOTICE responses.)  To avoid a deadlock situation, we must be
-                        * prepared to accept and buffer incoming data before we try
-                        * again.  Furthermore, it is possible that such incoming data
-                        * might not arrive until after we've gone to sleep.  Therefore,
-                        * we wait for either read ready or write ready.
+                        * NOTICE responses.)  A similar scenario is that SSL
+                        * renegotiation is in progress, and the SSL library needs to read
+                        * a message to complete the handshake, before it can send more
+                        * data.  To avoid a deadlock situation, we must be prepared to
+                        * accept and buffer incoming data before we try again.
+                        * Furthermore, it is possible that such incoming data might not
+                        * arrive until after we've gone to sleep.  Therefore, we wait for
+                        * either read ready or write ready.
                         *
                         * In non-blocking mode, we don't wait here directly, but return
                         * 1 to indicate that data is still pending.  The caller should
index 1b9f3a4a7b0a7ad065c62bfcd1a85ba3c3b91df1..8e9e35411c7b4004ce805e38d22bd5dd4e2d6d4f 100644 (file)
@@ -78,6 +78,7 @@ static int my_sock_write(BIO *h, const char *buf, int size);
 static BIO_METHOD *my_BIO_s_socket(void);
 static int my_SSL_set_fd(PGconn *conn, int fd);
 
+static bool my_block_raw_read = false;
 
 static bool pq_init_ssl_lib = true;
 static bool pq_init_crypto_lib = true;
@@ -319,8 +320,28 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
        char            sebuf[256];
        int                     err;
 
+       /*
+        * To work-around an issue with OpenSSL and renegotiation, don't let
+        * SSL_write() read any incoming data.
+        *
+        * If SSL_write() is called, and renegotiation has just been inititated,
+        * SSL_write() might try to read from the socket to complete the
+        * handshake. If there was some application data in-flight, it might
+        * receive the application data instead. That confuses it, and it throws
+        * an "sslv3 alert unexpected message" error.
+        *
+        * We avoid that by setting a kill-switch, my_block_raw_read, which tells
+        * my_sock_read() to not return any data to the caller, even if some is
+        * available.
+        *
+        * NB: This relies on the calling code to call pqsecure_read(), completing
+        * the renegotiation handshake, if pqsecure_write() returns 0. Otherwise
+        * we'll never make progress.
+        */
        SOCK_ERRNO_SET(0);
+       my_block_raw_read = true;
        n = SSL_write(conn->ssl, ptr, len);
+       my_block_raw_read = false;
        err = SSL_get_error(conn->ssl, n);
        switch (err)
        {
@@ -1588,6 +1609,15 @@ my_sock_read(BIO *h, char *buf, int size)
        int                     res;
        int                     save_errno;
 
+       /* If the kill-switch is set, pretend that there is no data. */
+       if (my_block_raw_read)
+       {
+               errno = EWOULDBLOCK;
+               BIO_clear_retry_flags(h);
+               BIO_set_retry_read(h);
+               return -1;
+       }
+
        res = pqsecure_raw_read((PGconn *) h->ptr, buf, size);
        save_errno = errno;
        BIO_clear_retry_flags(h);