]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: ssl: Fix sometimes reneg fails if requested by server.
authorEmeric Brun <ebrun@exceliance.fr>
Thu, 8 Nov 2012 18:21:55 +0000 (19:21 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 12 Nov 2012 10:46:08 +0000 (11:46 +0100)
SSL_do_handshake is not appropriate for reneg, it's only appropriate at the
beginning of a connection. OpenSSL correctly handles renegs using the data
functions, so we use SSL_peek() here to make its state machine progress if
SSL_renegotiate_pending() says a reneg is pending.

src/ssl_sock.c

index 8fec632ba6887a051ed825f10234aa263b6fd327..75f7b5d88171437fccd0c870bc4fa2594433bc38 100644 (file)
@@ -855,6 +855,61 @@ int ssl_sock_handshake(struct connection *conn, unsigned int flag)
        if (!conn->xprt_ctx)
                goto out_error;
 
+       /* If we use SSL_do_handshake to process a reneg initiated by
+        * the remote peer, it sometimes returns SSL_ERROR_SSL.
+        * Usually SSL_write and SSL_read are used and process implicitly
+        * the reneg handshake.
+        * Here we use SSL_peek as a workaround for reneg.
+        */
+       if ((conn->flags & CO_FL_CONNECTED) && SSL_renegotiate_pending(conn->xprt_ctx)) {
+               char c;
+
+               ret = SSL_peek(conn->xprt_ctx, &c, 1);
+               if (ret <= 0) {
+                       /* handshake may have not been completed, let's find why */
+                       ret = SSL_get_error(conn->xprt_ctx, ret);
+                       if (ret == SSL_ERROR_WANT_WRITE) {
+                               /* SSL handshake needs to write, L4 connection may not be ready */
+                               __conn_sock_stop_recv(conn);
+                               __conn_sock_poll_send(conn);
+                               return 0;
+                       }
+                       else if (ret == SSL_ERROR_WANT_READ) {
+                               /* handshake may have been completed but we have
+                                * no more data to read.
+                                 */
+                               if (!SSL_renegotiate_pending(conn->xprt_ctx)) {
+                                       ret = 1;
+                                       goto reneg_ok;
+                               }
+                               /* SSL handshake needs to read, L4 connection is ready */
+                               if (conn->flags & CO_FL_WAIT_L4_CONN)
+                                       conn->flags &= ~CO_FL_WAIT_L4_CONN;
+                               __conn_sock_stop_send(conn);
+                               __conn_sock_poll_recv(conn);
+                               return 0;
+                       }
+                       else if (ret == SSL_ERROR_SYSCALL) {
+                               /* if errno is null, then connection was successfully established */
+                               if (!errno && conn->flags & CO_FL_WAIT_L4_CONN)
+                                       conn->flags &= ~CO_FL_WAIT_L4_CONN;
+                               goto out_error;
+                       }
+                       else {
+                               /* Fail on all other handshake errors */
+                               /* Note: OpenSSL may leave unread bytes in the socket's
+                                * buffer, causing an RST to be emitted upon close() on
+                                * TCP sockets. We first try to drain possibly pending
+                                * data to avoid this as much as possible.
+                                */
+                               ret = recv(conn->t.sock.fd, trash.str, trash.size, MSG_NOSIGNAL|MSG_DONTWAIT);
+                               goto out_error;
+                       }
+               }
+               /* read some data: consider handshake completed */
+               goto reneg_ok;
+       }
+
        ret = SSL_do_handshake(conn->xprt_ctx);
        if (ret != 1) {
                /* handshake did not complete, let's find why */
@@ -892,6 +947,8 @@ int ssl_sock_handshake(struct connection *conn, unsigned int flag)
                }
        }
 
+reneg_ok:
+
        /* Handshake succeeded */
        if (objt_server(conn->target)) {
                if (!SSL_session_reused(conn->xprt_ctx)) {