Always process handshake records in full.

This removes the last place where non-app-data hooks leave anything
uncomsumed in rrec. (There is still a place where non-app-data hooks see
a non-empty rrec an entrance. read_app_data calls into read_handshake.
That'll be fixed in a later patch in this series.)

This should not change behavior, though some error codes may change due
to some processing happening in a slightly different order.

Since we do this in a few places, this adds a BUF_MEM_append with tests.

Change-Id: I9fe1fc0103e47f90e3c9f4acfe638927aecdeff6
Reviewed-on: https://boringssl-review.googlesource.com/21345
Commit-Queue: Steven Valdez <svaldez@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: Steven Valdez <svaldez@google.com>
diff --git a/ssl/d1_both.cc b/ssl/d1_both.cc
index 1110e98..3a31a02 100644
--- a/ssl/d1_both.cc
+++ b/ssl/d1_both.cc
@@ -461,7 +461,11 @@
   }
 }
 
-int dtls_has_incoming_messages(const SSL *ssl) {
+bool dtls_has_unprocessed_handshake_data(const SSL *ssl) {
+  if (ssl->d1->has_change_cipher_spec) {
+    return true;
+  }
+
   size_t current = ssl->d1->handshake_read_seq % SSL_MAX_HANDSHAKE_FLIGHT;
   for (size_t i = 0; i < SSL_MAX_HANDSHAKE_FLIGHT; i++) {
     // Skip the current message.
@@ -470,10 +474,10 @@
       continue;
     }
     if (ssl->d1->incoming_messages[i] != NULL) {
-      return 1;
+      return true;
     }
   }
-  return 0;
+  return false;
 }
 
 int dtls1_parse_fragment(CBS *cbs, struct hm_header_st *out_hdr,
diff --git a/ssl/dtls_method.cc b/ssl/dtls_method.cc
index f433428..daaec2d 100644
--- a/ssl/dtls_method.cc
+++ b/ssl/dtls_method.cc
@@ -83,8 +83,8 @@
 }
 
 static int dtls1_set_read_state(SSL *ssl, UniquePtr<SSLAEADContext> aead_ctx) {
-  // Cipher changes are illegal when there are buffered incoming messages.
-  if (dtls_has_incoming_messages(ssl) || ssl->d1->has_change_cipher_spec) {
+  // Cipher changes are forbidden if the current epoch has leftover data.
+  if (dtls_has_unprocessed_handshake_data(ssl)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFERED_MESSAGES_ON_CIPHER_CHANGE);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
     return 0;
diff --git a/ssl/internal.h b/ssl/internal.h
index 4c1f9ea..f141923 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1000,9 +1000,13 @@
 // dtls_clear_incoming_messages releases all buffered incoming messages.
 void dtls_clear_incoming_messages(SSL *ssl);
 
-// dtls_has_incoming_messages returns one if there are buffered incoming
-// messages ahead of the current message and zero otherwise.
-int dtls_has_incoming_messages(const SSL *ssl);
+// tls_has_unprocessed_handshake_data returns whether there is buffered
+// handshake data that has not been consumed by |get_message|.
+bool tls_has_unprocessed_handshake_data(const SSL *ssl);
+
+// dtls_has_unprocessed_handshake_data behaves like
+// |tls_has_unprocessed_handshake_data| for DTLS.
+bool dtls_has_unprocessed_handshake_data(const SSL *ssl);
 
 struct DTLS_OUTGOING_MESSAGE {
   uint8_t *data;
@@ -2687,7 +2691,10 @@
                        int peek);
 int ssl3_read_change_cipher_spec(SSL *ssl);
 void ssl3_read_close_notify(SSL *ssl);
-int ssl3_read_handshake_bytes(SSL *ssl, uint8_t *buf, int len);
+// ssl3_get_record reads a new input record. On success, it places it in
+// |ssl->s3->rrec| and returns one. Otherwise it returns <= 0 on error or if
+// more data is needed.
+int ssl3_get_record(SSL *ssl);
 int ssl3_write_app_data(SSL *ssl, bool *out_needs_handshake, const uint8_t *buf,
                         int len);
 
diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc
index 3cc01e9..1c6882e 100644
--- a/ssl/s3_both.cc
+++ b/ssl/s3_both.cc
@@ -274,22 +274,6 @@
   return 1;
 }
 
-static int extend_handshake_buffer(SSL *ssl, size_t length) {
-  if (!BUF_MEM_reserve(ssl->init_buf, length)) {
-    return -1;
-  }
-  while (ssl->init_buf->length < length) {
-    int ret = ssl3_read_handshake_bytes(
-        ssl, (uint8_t *)ssl->init_buf->data + ssl->init_buf->length,
-        length - ssl->init_buf->length);
-    if (ret <= 0) {
-      return ret;
-    }
-    ssl->init_buf->length += (size_t)ret;
-  }
-  return 1;
-}
-
 static int read_v2_client_hello(SSL *ssl) {
   // Read the first 5 bytes, the size of the TLS record header. This is
   // sufficient to detect a V2ClientHello and ensures that we never read beyond
@@ -438,9 +422,8 @@
   return 1;
 }
 
-// TODO(davidben): Remove |out_bytes_needed| and inline into |ssl3_get_message|
-// when the entire record is copied into |init_buf|.
-static bool parse_message(SSL *ssl, SSLMessage *out, size_t *out_bytes_needed) {
+static bool parse_message(const SSL *ssl, SSLMessage *out,
+                          size_t *out_bytes_needed) {
   if (ssl->init_buf == NULL) {
     *out_bytes_needed = 4;
     return false;
@@ -481,6 +464,19 @@
   return true;
 }
 
+bool tls_has_unprocessed_handshake_data(const SSL *ssl) {
+  size_t msg_len = 0;
+  if (ssl->s3->has_message) {
+    SSLMessage msg;
+    size_t unused;
+    if (parse_message(ssl, &msg, &unused)) {
+      msg_len = CBS_len(&msg.raw);
+    }
+  }
+
+  return ssl->init_buf != NULL && ssl->init_buf->length > msg_len;
+}
+
 int ssl3_read_message(SSL *ssl) {
   SSLMessage msg;
   size_t bytes_needed;
@@ -513,7 +509,40 @@
     return ret;
   }
 
-  return extend_handshake_buffer(ssl, bytes_needed);
+  SSL3_RECORD *rr = &ssl->s3->rrec;
+  // Get new packet if necessary.
+  if (rr->length == 0) {
+    int ret = ssl3_get_record(ssl);
+    if (ret <= 0) {
+      return ret;
+    }
+  }
+
+  // WatchGuard's TLS 1.3 interference bug is very distinctive: they drop the
+  // ServerHello and send the remaining encrypted application data records
+  // as-is. This manifests as an application data record when we expect
+  // handshake. Report a dedicated error code for this case.
+  if (!ssl->server && rr->type == SSL3_RT_APPLICATION_DATA &&
+      ssl->s3->aead_read_ctx->is_null_cipher()) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_APPLICATION_DATA_INSTEAD_OF_HANDSHAKE);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+    return -1;
+  }
+
+  if (rr->type != SSL3_RT_HANDSHAKE) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+    return -1;
+  }
+
+  // Append the entire handshake record to the buffer.
+  if (!BUF_MEM_append(ssl->init_buf, rr->data, rr->length)) {
+    return -1;
+  }
+
+  rr->length = 0;
+  ssl_read_buffer_discard(ssl);
+  return 1;
 }
 
 void ssl3_next_message(SSL *ssl) {
diff --git a/ssl/s3_pkt.cc b/ssl/s3_pkt.cc
index 890e8b7..b182826 100644
--- a/ssl/s3_pkt.cc
+++ b/ssl/s3_pkt.cc
@@ -126,10 +126,7 @@
 
 static int do_ssl3_write(SSL *ssl, int type, const uint8_t *buf, unsigned len);
 
-// ssl3_get_record reads a new input record. On success, it places it in
-// |ssl->s3->rrec| and returns one. Otherwise it returns <= 0 on error or if
-// more data is needed.
-static int ssl3_get_record(SSL *ssl) {
+int ssl3_get_record(SSL *ssl) {
 again:
   switch (ssl->s3->read_shutdown) {
     case ssl_shutdown_none:
@@ -462,7 +459,6 @@
 
 int ssl3_read_change_cipher_spec(SSL *ssl) {
   SSL3_RECORD *rr = &ssl->s3->rrec;
-
   if (rr->length == 0) {
     int ret = ssl3_get_record(ssl);
     if (ret <= 0) {
@@ -470,7 +466,8 @@
     }
   }
 
-  if (rr->type != SSL3_RT_CHANGE_CIPHER_SPEC) {
+  if (rr->type != SSL3_RT_CHANGE_CIPHER_SPEC ||
+      tls_has_unprocessed_handshake_data(ssl)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
     return -1;
@@ -497,43 +494,6 @@
   }
 }
 
-int ssl3_read_handshake_bytes(SSL *ssl, uint8_t *buf, int len) {
-  SSL3_RECORD *rr = &ssl->s3->rrec;
-
-  for (;;) {
-    // Get new packet if necessary.
-    if (rr->length == 0) {
-      int ret = ssl3_get_record(ssl);
-      if (ret <= 0) {
-        return ret;
-      }
-    }
-
-    // WatchGuard's TLS 1.3 interference bug is very distinctive: they drop the
-    // ServerHello and send the remaining encrypted application data records
-    // as-is. This manifests as an application data record when we expect
-    // handshake. Report a dedicated error code for this case.
-    if (!ssl->server && rr->type == SSL3_RT_APPLICATION_DATA &&
-        ssl->s3->aead_read_ctx->is_null_cipher()) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_APPLICATION_DATA_INSTEAD_OF_HANDSHAKE);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-      return -1;
-    }
-
-    if (rr->type != SSL3_RT_HANDSHAKE) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
-      return -1;
-    }
-
-    if (rr->length != 0) {
-      return consume_record(ssl, buf, len, 0 /* consume data */);
-    }
-
-    // Discard empty records and loop again.
-  }
-}
-
 int ssl_send_alert(SSL *ssl, int level, int desc) {
   // It is illegal to send an alert when we've already sent a closing one.
   if (ssl->s3->write_shutdown != ssl_shutdown_none) {
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 88161ad..c975337 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -924,28 +924,27 @@
       }
     }
 
-    bool got_handshake = false;
-    int ret = ssl->method->read_app_data(ssl, &got_handshake, (uint8_t *)buf,
-                                         num, peek);
-    if (ret > 0 || !got_handshake) {
-      ssl->s3->key_update_count = 0;
-      return ret;
-    }
-
-    // If we received an interrupt in early read (the end_of_early_data alert),
-    // loop again for the handshake to process it.
-    if (SSL_in_init(ssl)) {
-      continue;
-    }
-
+    // Process any buffered post-handshake messages.
     SSLMessage msg;
-    while (ssl->method->get_message(ssl, &msg)) {
+    if (ssl->method->get_message(ssl, &msg)) {
       // Handle the post-handshake message and try again.
       if (!ssl_do_post_handshake(ssl, msg)) {
         return -1;
       }
       ssl->method->next_message(ssl);
+      continue;  // Loop again. We may have begun a new handshake.
     }
+
+    bool got_handshake = false;
+    int ret = ssl->method->read_app_data(ssl, &got_handshake, (uint8_t *)buf,
+                                         num, peek);
+    if (got_handshake) {
+      continue;  // Loop again to process the handshake data.
+    }
+    if (ret > 0) {
+      ssl->s3->key_update_count = 0;
+    }
+    return ret;
   }
 }
 
diff --git a/ssl/ssl_transcript.cc b/ssl/ssl_transcript.cc
index 8bddbe2..ab9822f 100644
--- a/ssl/ssl_transcript.cc
+++ b/ssl/ssl_transcript.cc
@@ -212,16 +212,9 @@
 bool SSLTranscript::Update(Span<const uint8_t> in) {
   // Depending on the state of the handshake, either the handshake buffer may be
   // active, the rolling hash, or both.
-  if (buffer_) {
-    size_t new_len = buffer_->length + in.size();
-    if (new_len < in.size()) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
-      return false;
-    }
-    if (!BUF_MEM_grow(buffer_.get(), new_len)) {
-      return false;
-    }
-    OPENSSL_memcpy(buffer_->data + new_len - in.size(), in.data(), in.size());
+  if (buffer_ &&
+      !BUF_MEM_append(buffer_.get(), in.data(), in.size())) {
+    return false;
   }
 
   if (EVP_MD_CTX_md(hash_.get()) != NULL) {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 9ec0d80..a57362d 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -2491,7 +2491,7 @@
 				"-expect-total-renegotiations", "1",
 			},
 			shouldFail:    true,
-			expectedError: ":EXCESSIVE_MESSAGE_SIZE:",
+			expectedError: ":BAD_HELLO_REQUEST:",
 		},
 		{
 			name:        "BadHelloRequest-2",
diff --git a/ssl/tls_method.cc b/ssl/tls_method.cc
index 77f9d23..b4d08a0 100644
--- a/ssl/tls_method.cc
+++ b/ssl/tls_method.cc
@@ -74,11 +74,10 @@
   assert(!ssl->s3->has_message);
 
   // During the handshake, |init_buf| is retained. Release if it there is no
-  // excess in it.
+  // excess in it. There may be excess left if there server sent Finished and
+  // HelloRequest in the same record.
   //
-  // TODO(davidben): The second check is always true but will not be once we
-  // switch to copying the entire handshake record. Replace this comment with an
-  // explanation when that happens and a TODO to reject it.
+  // TODO(davidben): SChannel does not support this. Reject this case.
   if (ssl->init_buf != NULL && ssl->init_buf->length == 0) {
     BUF_MEM_free(ssl->init_buf);
     ssl->init_buf = NULL;
@@ -86,8 +85,11 @@
 }
 
 static int ssl3_set_read_state(SSL *ssl, UniquePtr<SSLAEADContext> aead_ctx) {
-  if (ssl->s3->rrec.length != 0) {
-    // There may not be unprocessed record data at a cipher change.
+  // Cipher changes are forbidden if the current epoch has leftover data.
+  //
+  // TODO(davidben): ssl->s3->rrec.length should be impossible now. Remove it
+  // once it is only used for application data.
+  if (ssl->s3->rrec.length != 0 || tls_has_unprocessed_handshake_data(ssl)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFERED_MESSAGES_ON_CIPHER_CHANGE);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
     return 0;