Push alert handling down into the record functions.

Alert handling is more-or-less identical across all contexts. Push it down from
read_bytes into the low-level record functions. This also deduplicates the code
shared between TLS and DTLS.

Now the only type mismatch managed by read_bytes is if we get handshake data in
read_app_data.

Change-Id: Ia8331897b304566e66d901899cfbf31d2870194e
Reviewed-on: https://boringssl-review.googlesource.com/8124
Reviewed-by: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/ssl/tls_record.c b/ssl/tls_record.c
index 7036c87..869831c 100644
--- a/ssl/tls_record.c
+++ b/ssl/tls_record.c
@@ -113,6 +113,7 @@
 
 #include <openssl/bytestring.h>
 #include <openssl/err.h>
+#include <openssl/mem.h>
 
 #include "internal.h"
 
@@ -123,6 +124,10 @@
  * forever. */
 static const uint8_t kMaxEmptyRecords = 32;
 
+/* kMaxWarningAlerts is the number of consecutive warning alerts that will be
+ * processed. */
+static const uint8_t kMaxWarningAlerts = 4;
+
 /* ssl_needs_record_splitting returns one if |ssl|'s current outgoing cipher
  * state needs record-splitting and zero otherwise. */
 static int ssl_needs_record_splitting(const SSL *ssl) {
@@ -191,6 +196,8 @@
     SSL *ssl, uint8_t *out_type, uint8_t *out, size_t *out_len,
     size_t *out_consumed, uint8_t *out_alert, size_t max_out, const uint8_t *in,
     size_t in_len) {
+  *out_consumed = 0;
+
   CBS cbs;
   CBS_init(&cbs, in, in_len);
 
@@ -238,6 +245,8 @@
     *out_alert = SSL_AD_BAD_RECORD_MAC;
     return ssl_open_record_error;
   }
+  *out_consumed = in_len - CBS_len(&cbs);
+
   if (!ssl_record_sequence_update(ssl->s3->read_sequence, 8)) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     return ssl_open_record_error;
@@ -281,9 +290,14 @@
     ssl->s3->empty_record_count = 0;
   }
 
+  if (type == SSL3_RT_ALERT) {
+    return ssl_process_alert(ssl, out_alert, out, plaintext_len);
+  }
+
+  ssl->s3->warning_alert_count = 0;
+
   *out_type = type;
   *out_len = plaintext_len;
-  *out_consumed = in_len - CBS_len(&cbs);
   return ssl_open_record_success;
 }
 
@@ -417,3 +431,51 @@
   SSL_AEAD_CTX_free(ssl->s3->aead_write_ctx);
   ssl->s3->aead_write_ctx = aead_ctx;
 }
+
+enum ssl_open_record_t ssl_process_alert(SSL *ssl, uint8_t *out_alert,
+                                         const uint8_t *in, size_t in_len) {
+  /* Alerts records may not contain fragmented or multiple alerts. */
+  if (in_len != 2) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ALERT);
+    return ssl_open_record_error;
+  }
+
+  ssl_do_msg_callback(ssl, 0 /* read */, ssl->version, SSL3_RT_ALERT, in, in_len);
+
+  const uint8_t alert_level = in[0];
+  const uint8_t alert_descr = in[1];
+
+  uint16_t alert = (alert_level << 8) | alert_descr;
+  ssl_do_info_callback(ssl, SSL_CB_READ_ALERT, alert);
+
+  if (alert_level == SSL3_AL_WARNING) {
+    if (alert_descr == SSL_AD_CLOSE_NOTIFY) {
+      ssl->s3->recv_shutdown = ssl_shutdown_close_notify;
+      return ssl_open_record_close_notify;
+    }
+
+    ssl->s3->warning_alert_count++;
+    if (ssl->s3->warning_alert_count > kMaxWarningAlerts) {
+      *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MANY_WARNING_ALERTS);
+      return ssl_open_record_error;
+    }
+    return ssl_open_record_discard;
+  }
+
+  if (alert_level == SSL3_AL_FATAL) {
+    ssl->s3->recv_shutdown = ssl_shutdown_fatal_alert;
+    SSL_CTX_remove_session(ssl->ctx, ssl->session);
+
+    char tmp[16];
+    OPENSSL_PUT_ERROR(SSL, SSL_AD_REASON_OFFSET + alert_descr);
+    BIO_snprintf(tmp, sizeof(tmp), "%d", alert_descr);
+    ERR_add_error_data(2, "SSL alert number ", tmp);
+    return ssl_open_record_fatal_alert;
+  }
+
+  *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+  OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_ALERT_TYPE);
+  return ssl_open_record_error;
+}