CVE-2020-28013: Heap buffer overflow in parse_fix_phrase()
[exim.git] / src / src / tls-gnu.c
index cf899454f69b512f1ccfe142dec71b5b5d715651..6ee603595dbd3c3ebbd1b7ff89b84865c6e25a04 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Phil Pennock 2012 */
@@ -544,7 +545,10 @@ else
 
 /* peercert is set in peer_status() */
 tlsp->peerdn = state->peerdn;
-tlsp->sni =    state->received_sni;
+
+/* do not corrupt sni sent by client; record sni rxd by server */
+if (!state->host)
+  tlsp->sni = state->received_sni;
 
 /* record our certificate */
   {
@@ -2513,7 +2517,7 @@ sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
 do
   rc = gnutls_handshake(state->session);
-while (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
+while (rc == GNUTLS_E_AGAIN ||  rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
 ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
@@ -2600,9 +2604,9 @@ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
   {
   state->exp_tls_verify_cert_hostnames =
 #ifdef SUPPORT_I18N
-    string_domain_utf8_to_alabel(host->name, NULL);
+    string_domain_utf8_to_alabel(host->certname, NULL);
 #else
-    host->name;
+    host->certname;
 #endif
   DEBUG(D_tls)
     debug_printf("TLS: server cert verification includes hostname: \"%s\".\n",
@@ -2862,7 +2866,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", cctx->so
 /* If dane is flagged, have either request or require dane for this host, and
 a TLSA record found.  Therefore, dane verify required.  Which implies cert must
 be requested and supplied, dane verify must pass, and cert verify irrelevant
-(incl.  hostnames), and (caller handled) require_tls */
+(incl.  hostnames), and (caller handled) require_tls and sni=$domain */
 
 if (conn_args->dane && ob->dane_require_tls_ciphers)
   {
@@ -2889,6 +2893,7 @@ if (!cipher_list)
       cipher_list, &state, tlsp, errstr) != OK)
     return FALSE;
 
+
 #ifdef MEASURE_TIMING
   report_time_since(&t0, US"client tls_init (delta)");
 #endif
@@ -2993,7 +2998,7 @@ sigalrm_seen = FALSE;
 ALARM(ob->command_timeout);
 do
   rc = gnutls_handshake(state->session);
-while (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
+while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
 ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
@@ -3157,7 +3162,11 @@ DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, buf
 sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
 
-inbytes = gnutls_record_recv(state->session, state->xfer_buffer, MIN(ssl_xfer_buffer_size, lim));
+errno = 0;
+do
+  inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
+    MIN(ssl_xfer_buffer_size, lim));
+while (inbytes == GNUTLS_E_AGAIN);
 
 if (smtp_receive_timeout > 0) ALARM_CLR(0);
 
@@ -3314,7 +3323,10 @@ DEBUG(D_tls)
   debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, len=" SIZE_T_FMT ")\n",
       state->session, buff, len);
 
-inbytes = gnutls_record_recv(state->session, buff, len);
+errno = 0;
+do
+  inbytes = gnutls_record_recv(state->session, buff, len);
+while (inbytes == GNUTLS_E_AGAIN);
 
 if (inbytes > 0) return inbytes;
 if (inbytes == 0)
@@ -3375,7 +3387,10 @@ while (left > 0)
   DEBUG(D_tls) debug_printf("gnutls_record_send(session=%p, buffer=%p, left=" SIZE_T_FMT ")\n",
       state->session, buff, left);
 
-  outbytes = gnutls_record_send(state->session, buff, left);
+  errno = 0;
+  do
+    outbytes = gnutls_record_send(state->session, buff, left);
+  while (outbytes == GNUTLS_E_AGAIN);
 
   DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
 
@@ -3407,14 +3422,21 @@ if (len > INT_MAX)
 if (!more && state->corked)
   {
   DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session);
-  do {
-      outbytes = gnutls_record_uncork(state->session, 0);
-     if (outbytes < 0)
-       {
-       record_io_error(state, len, US"uncork", NULL);
-       return -1;
-       }
-  } while (gnutls_record_check_corked(state->session) > 0);
+  do
+    /* We can't use GNUTLS_RECORD_WAIT here, as it retries on
+    GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm().
+    The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway.
+    But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN
+    match the EINTR and EAGAIN errno values.) */
+    outbytes = gnutls_record_uncork(state->session, 0);
+  while (outbytes == GNUTLS_E_AGAIN);
+
+  if (outbytes < 0)
+    {
+    record_io_error(state, len, US"uncork", NULL);
+    return -1;
+    }
+
   state->corked = FALSE;
   }
 #endif