Never expose ssl->bbio in the public API.

OpenSSL's bbio logic is kind of crazy. It would be good to eventually do the
buffering in a better way (notably, bbio is fragile, if not outright broken,
for DTLS). In the meantime, this fixes a number of bugs where the existence of
bbio was leaked in the public API and broke things.

- SSL_get_wbio returned the bbio during the handshake. It must always return
  the BIO the consumer configured. In doing so, internal accesses of
  SSL_get_wbio should be switched to ssl->wbio since those want to see bbio.
  For consistency, do the same with rbio.

- The logic in SSL_set_rfd, etc. (which I doubt is quite right since
  SSL_set_bio's lifetime is unclear) would get confused once wbio got wrapped.
  Those want to compare to SSL_get_wbio.

- If SSL_set_bio was called mid-handshake, bbio would get disconnected and lose
  state. It forgets to reattach the bbio afterwards. Unfortunately, Conscrypt
  does this a lot. It just never ended up calling it at a point where the bbio
  would cause problems.

- Make more explicit the invariant that any bbio's which exist are always
  attached. Simplify a few things as part of that.

Change-Id: Ia02d6bdfb9aeb1e3021a8f82dcbd0629f5c7fb8d
Reviewed-on: https://boringssl-review.googlesource.com/8023
Reviewed-by: Kenny Root <kroot@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 3e7fad8..bd583e8 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3863,7 +3863,9 @@
   BIO *wbio; /* used by SSL_write */
 
   /* bbio, if non-NULL, is a buffer placed in front of |wbio| to pack handshake
-   * messages within one flight into a single |BIO_write|.
+   * messages within one flight into a single |BIO_write|. In this case, |wbio|
+   * and |bbio| are equal and the true caller-configured BIO is
+   * |bbio->next_bio|.
    *
    * TODO(davidben): This does not work right for DTLS. It assumes the MTU is
    * smaller than the buffer size so that the buffer's internal flushing never
diff --git a/ssl/d1_both.c b/ssl/d1_both.c
index 074668c..8d8784b 100644
--- a/ssl/d1_both.c
+++ b/ssl/d1_both.c
@@ -249,12 +249,12 @@
   /* TODO(davidben): What is this code doing and do we need it? */
   if (ssl->d1->mtu < dtls1_min_mtu() &&
       !(SSL_get_options(ssl) & SSL_OP_NO_QUERY_MTU)) {
-    long mtu = BIO_ctrl(SSL_get_wbio(ssl), BIO_CTRL_DGRAM_QUERY_MTU, 0, NULL);
+    long mtu = BIO_ctrl(ssl->wbio, BIO_CTRL_DGRAM_QUERY_MTU, 0, NULL);
     if (mtu >= 0 && mtu <= (1 << 30) && (unsigned)mtu >= dtls1_min_mtu()) {
       ssl->d1->mtu = (unsigned)mtu;
     } else {
       ssl->d1->mtu = kDefaultMTU;
-      BIO_ctrl(SSL_get_wbio(ssl), BIO_CTRL_DGRAM_SET_MTU, ssl->d1->mtu, NULL);
+      BIO_ctrl(ssl->wbio, BIO_CTRL_DGRAM_SET_MTU, ssl->d1->mtu, NULL);
     }
   }
 
@@ -274,7 +274,7 @@
   }
   ret -= overhead;
 
-  size_t pending = BIO_wpending(SSL_get_wbio(ssl));
+  size_t pending = BIO_wpending(ssl->wbio);
   if (ret <= pending) {
     return 0;
   }
@@ -290,7 +290,7 @@
   /* During the handshake, wbio is buffered to pack messages together. Flush the
    * buffer if the ChangeCipherSpec would not fit in a packet. */
   if (dtls1_max_record_size(ssl) == 0) {
-    int ret = BIO_flush(SSL_get_wbio(ssl));
+    int ret = BIO_flush(ssl->wbio);
     if (ret <= 0) {
       ssl->rwstate = SSL_WRITING;
       return ret;
@@ -339,13 +339,13 @@
     /* During the handshake, wbio is buffered to pack messages together. Flush
      * the buffer if there isn't enough room to make progress. */
     if (dtls1_max_record_size(ssl) < DTLS1_HM_HEADER_LENGTH + 1) {
-      int flush_ret = BIO_flush(SSL_get_wbio(ssl));
+      int flush_ret = BIO_flush(ssl->wbio);
       if (flush_ret <= 0) {
         ssl->rwstate = SSL_WRITING;
         ret = flush_ret;
         goto err;
       }
-      assert(BIO_wpending(SSL_get_wbio(ssl)) == 0);
+      assert(BIO_wpending(ssl->wbio) == 0);
     }
 
     size_t todo = dtls1_max_record_size(ssl);
@@ -672,7 +672,7 @@
 
   if (!SSL_in_init(ssl)) {
     /* done, no need to send a retransmit */
-    BIO_set_flags(SSL_get_rbio(ssl), BIO_FLAGS_READ);
+    BIO_set_flags(ssl->rbio, BIO_FLAGS_READ);
     return code;
   }
 
@@ -742,7 +742,7 @@
     }
   }
 
-  ret = BIO_flush(SSL_get_wbio(ssl));
+  ret = BIO_flush(ssl->wbio);
   if (ret <= 0) {
     ssl->rwstate = SSL_WRITING;
     goto err;
diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c
index dc3c640..c1c9f91 100644
--- a/ssl/d1_lib.c
+++ b/ssl/d1_lib.c
@@ -177,7 +177,7 @@
     ssl->d1->next_timeout.tv_sec++;
     ssl->d1->next_timeout.tv_usec -= 1000000;
   }
-  BIO_ctrl(SSL_get_rbio(ssl), BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, 0,
+  BIO_ctrl(ssl->rbio, BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, 0,
            &ssl->d1->next_timeout);
 }
 
@@ -251,7 +251,7 @@
   ssl->d1->num_timeouts = 0;
   memset(&ssl->d1->next_timeout, 0, sizeof(struct timeval));
   ssl->d1->timeout_duration_ms = ssl->initial_timeout_duration_ms;
-  BIO_ctrl(SSL_get_rbio(ssl), BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, 0,
+  BIO_ctrl(ssl->rbio, BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, 0,
            &ssl->d1->next_timeout);
   /* Clear retransmission buffer */
   dtls1_clear_record_buffer(ssl);
@@ -263,8 +263,7 @@
   /* Reduce MTU after 2 unsuccessful retransmissions */
   if (ssl->d1->num_timeouts > DTLS1_MTU_TIMEOUTS &&
       !(SSL_get_options(ssl) & SSL_OP_NO_QUERY_MTU)) {
-    long mtu = BIO_ctrl(SSL_get_wbio(ssl), BIO_CTRL_DGRAM_GET_FALLBACK_MTU, 0,
-                        NULL);
+    long mtu = BIO_ctrl(ssl->wbio, BIO_CTRL_DGRAM_GET_FALLBACK_MTU, 0, NULL);
     if (mtu >= 0 && mtu <= (1 << 30) && (unsigned)mtu >= dtls1_min_mtu()) {
       ssl->d1->mtu = (unsigned)mtu;
     }
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index fd8c04c..86057d1 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -471,14 +471,8 @@
 
   CRYPTO_free_ex_data(&g_ex_data_class_ssl, ssl, &ssl->ex_data);
 
-  if (ssl->bbio != NULL) {
-    /* If the buffering BIO is in place, pop it off */
-    if (ssl->bbio == ssl->wbio) {
-      ssl->wbio = BIO_pop(ssl->wbio);
-    }
-    BIO_free(ssl->bbio);
-    ssl->bbio = NULL;
-  }
+  ssl_free_wbio_buffer(ssl);
+  assert(ssl->bbio == NULL);
 
   int free_wbio = ssl->wbio != ssl->rbio;
   BIO_free_all(ssl->rbio);
@@ -529,10 +523,7 @@
 void SSL_set_bio(SSL *ssl, BIO *rbio, BIO *wbio) {
   /* If the output buffering BIO is still in place, remove it. */
   if (ssl->bbio != NULL) {
-    if (ssl->wbio == ssl->bbio) {
-      ssl->wbio = ssl->wbio->next_bio;
-      ssl->bbio->next_bio = NULL;
-    }
+    ssl->wbio = BIO_pop(ssl->wbio);
   }
 
   if (ssl->rbio != rbio) {
@@ -543,11 +534,23 @@
   }
   ssl->rbio = rbio;
   ssl->wbio = wbio;
+
+  /* Re-attach |bbio| to the new |wbio|. */
+  if (ssl->bbio != NULL) {
+    ssl->wbio = BIO_push(ssl->bbio, ssl->wbio);
+  }
 }
 
 BIO *SSL_get_rbio(const SSL *ssl) { return ssl->rbio; }
 
-BIO *SSL_get_wbio(const SSL *ssl) { return ssl->wbio; }
+BIO *SSL_get_wbio(const SSL *ssl) {
+  if (ssl->bbio != NULL) {
+    /* If |bbio| is active, the true caller-configured BIO is its |next_bio|. */
+    assert(ssl->bbio == ssl->wbio);
+    return ssl->bbio->next_bio;
+  }
+  return ssl->wbio;
+}
 
 int SSL_do_handshake(SSL *ssl) {
   ssl->rwstate = SSL_NOTHING;
@@ -1030,35 +1033,36 @@
 }
 
 int SSL_set_wfd(SSL *ssl, int fd) {
-  if (ssl->rbio == NULL ||
-      BIO_method_type(ssl->rbio) != BIO_TYPE_SOCKET ||
-      BIO_get_fd(ssl->rbio, NULL) != fd) {
+  BIO *rbio = SSL_get_rbio(ssl);
+  if (rbio == NULL || BIO_method_type(rbio) != BIO_TYPE_SOCKET ||
+      BIO_get_fd(rbio, NULL) != fd) {
     BIO *bio = BIO_new(BIO_s_socket());
     if (bio == NULL) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
       return 0;
     }
     BIO_set_fd(bio, fd, BIO_NOCLOSE);
-    SSL_set_bio(ssl, SSL_get_rbio(ssl), bio);
+    SSL_set_bio(ssl, rbio, bio);
   } else {
-    SSL_set_bio(ssl, SSL_get_rbio(ssl), SSL_get_rbio(ssl));
+    SSL_set_bio(ssl, rbio, rbio);
   }
 
   return 1;
 }
 
 int SSL_set_rfd(SSL *ssl, int fd) {
-  if (ssl->wbio == NULL || BIO_method_type(ssl->wbio) != BIO_TYPE_SOCKET ||
-      BIO_get_fd(ssl->wbio, NULL) != fd) {
+  BIO *wbio = SSL_get_wbio(ssl);
+  if (wbio == NULL || BIO_method_type(wbio) != BIO_TYPE_SOCKET ||
+      BIO_get_fd(wbio, NULL) != fd) {
     BIO *bio = BIO_new(BIO_s_socket());
     if (bio == NULL) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
       return 0;
     }
     BIO_set_fd(bio, fd, BIO_NOCLOSE);
-    SSL_set_bio(ssl, bio, SSL_get_wbio(ssl));
+    SSL_set_bio(ssl, bio, wbio);
   } else {
-    SSL_set_bio(ssl, SSL_get_wbio(ssl), SSL_get_wbio(ssl));
+    SSL_set_bio(ssl, wbio, wbio);
   }
   return 1;
 }
@@ -1861,35 +1865,25 @@
 int *SSL_get_server_tmp_key(SSL *ssl, EVP_PKEY **out_key) { return 0; }
 
 int ssl_is_wbio_buffered(const SSL *ssl) {
-  return ssl->bbio != NULL && ssl->wbio == ssl->bbio;
+  return ssl->bbio != NULL;
 }
 
 int ssl_init_wbio_buffer(SSL *ssl) {
-  BIO *bbio;
-
-  if (ssl->bbio == NULL) {
-    bbio = BIO_new(BIO_f_buffer());
-    if (bbio == NULL) {
-      return 0;
-    }
-    ssl->bbio = bbio;
-  } else {
-    bbio = ssl->bbio;
-    if (ssl->bbio == ssl->wbio) {
-      ssl->wbio = BIO_pop(ssl->wbio);
-    }
+  if (ssl->bbio != NULL) {
+    /* Already buffered. */
+    assert(ssl->bbio == ssl->wbio);
+    return 1;
   }
 
-  BIO_reset(bbio);
-  if (!BIO_set_read_buffer_size(bbio, 1)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
+  BIO *bbio = BIO_new(BIO_f_buffer());
+  if (bbio == NULL ||
+      !BIO_set_read_buffer_size(bbio, 1)) {
+    BIO_free(bbio);
     return 0;
   }
 
-  if (ssl->wbio != bbio) {
-    ssl->wbio = BIO_push(bbio, ssl->wbio);
-  }
-
+  ssl->bbio = bbio;
+  ssl->wbio = BIO_push(bbio, ssl->wbio);
   return 1;
 }
 
@@ -1898,11 +1892,9 @@
     return;
   }
 
-  if (ssl->bbio == ssl->wbio) {
-    /* remove buffering */
-    ssl->wbio = BIO_pop(ssl->wbio);
-  }
+  assert(ssl->bbio == ssl->wbio);
 
+  ssl->wbio = BIO_pop(ssl->wbio);
   BIO_free(ssl->bbio);
   ssl->bbio = NULL;
 }