]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
TLS: start shutdown only when peer did not already close
authorStefan Eissing <stefan@eissing.org>
Fri, 8 Mar 2024 08:37:27 +0000 (09:37 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 15 Mar 2024 08:19:58 +0000 (09:19 +0100)
- When curl sees a TCP close from the peer, do not start a TLS shutdown.
  TLS shutdown is a handshake and if the peer already closed the
  connection, it is not interested in participating.

Reported-by: dfdity on github
Assisted-by: Jiří Bok
Assisted-by: Pēteris Caune
Fixes #10290
Closes #13087

lib/cw-out.c
lib/vtls/gtls.c
lib/vtls/openssl.c
lib/vtls/rustls.c
lib/vtls/vtls_int.h
lib/vtls/wolfssl.c

index 25a27a68cd6a1aee731bb4bec2e9d600669a9a67..c1688378179386d9ce5d69ed603dae494ee794c8 100644 (file)
@@ -231,7 +231,7 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx,
     }
     if(nwritten != wlen) {
       failf(data, "Failure writing output to destination, "
-            "passed %zu returned %zu", wlen, nwritten);
+            "passed %zu returned %zd", wlen, nwritten);
       return CURLE_WRITE_ERROR;
     }
     *pconsumed += nwritten;
index b95c5be3c5e5ce4d4ceab7f26f2afdd39d7ee84d..6eaa6a8f739fa2f1bb319127a289d6c8756201e3 100644 (file)
@@ -117,6 +117,8 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen)
                                (CURLE_AGAIN == result)? EAGAIN : EINVAL);
     nread = -1;
   }
+  else if(nread == 0)
+    connssl->peer_closed = TRUE;
   return nread;
 }
 
@@ -1489,7 +1491,7 @@ static int gtls_shutdown(struct Curl_cfilter *cf,
     bool done = FALSE;
     char buf[120];
 
-    while(!done) {
+    while(!done && !connssl->peer_closed) {
       int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
                                  SSL_SHUTDOWN_TIMEOUT);
       if(what > 0) {
index eda36606c56b1953adc61b4cae23e9efa3333cfc..a3953f6c3f805a4ccb60ff1a35fe8901a7a62738 100644 (file)
@@ -769,6 +769,9 @@ static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen)
     if(CURLE_AGAIN == result)
       BIO_set_retry_read(bio);
   }
+  else if(nread == 0) {
+    connssl->peer_closed = TRUE;
+  }
 
   /* Before returning server replies to the SSL instance, we need
    * to have setup the x509 store or verification will fail. */
@@ -1887,16 +1890,41 @@ static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
   DEBUGASSERT(backend);
 
   if(backend->handle) {
-    if(cf->next && cf->next->connected) {
+    /* Send the TLS shutdown if we are still connected *and* if
+     * the peer did not already close the connection. */
+    if(cf->next && cf->next->connected && !connssl->peer_closed) {
       char buf[1024];
       int nread, err;
       long sslerr;
 
       /* Maybe the server has already sent a close notify alert.
          Read it to avoid an RST on the TCP connection. */
-      (void)SSL_read(backend->handle, buf, (int)sizeof(buf));
       ERR_clear_error();
-      if(SSL_shutdown(backend->handle) == 1) {
+      nread = SSL_read(backend->handle, buf, (int)sizeof(buf));
+      err = SSL_get_error(backend->handle, nread);
+      if(!nread && err == SSL_ERROR_ZERO_RETURN) {
+        CURLcode result;
+        ssize_t n;
+        size_t blen = sizeof(buf);
+        CURL_TRC_CF(data, cf, "peer has shutdown TLS");
+        /* SSL_read() will not longer touch the socket, let's receive
+         * directly from the next filter to see if the underlying
+         * connection has also been closed. */
+        n = Curl_conn_cf_recv(cf->next, data, buf, blen, &result);
+        if(!n) {
+          connssl->peer_closed = TRUE;
+          CURL_TRC_CF(data, cf, "peer closed connection");
+        }
+      }
+      ERR_clear_error();
+      if(connssl->peer_closed) {
+        /* As the peer closed, we do not expect it to read anything more we
+         * may send. It may be harmful, leading to TCP RST and delaying
+         * a lingering close. Just leave. */
+        CURL_TRC_CF(data, cf, "not from sending TLS shutdown on "
+                    "connection closed by peer");
+      }
+      else if(SSL_shutdown(backend->handle) == 1) {
         CURL_TRC_CF(data, cf, "SSL shutdown finished");
       }
       else {
index 367d7a5f409bcbabf97678ee3d7469d0be4a56ed..ceb26c5bd95102a7a7ba16876018dcd042db7c21 100644 (file)
@@ -86,6 +86,7 @@ static int
 read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n)
 {
   struct io_ctx *io_ctx = userdata;
+  struct ssl_connect_data *const connssl = io_ctx->cf->ctx;
   CURLcode result;
   int ret = 0;
   ssize_t nread = Curl_conn_cf_recv(io_ctx->cf->next, io_ctx->data,
@@ -97,6 +98,8 @@ read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n)
     else
       ret = EINVAL;
   }
+  else if(nread == 0)
+    connssl->peer_closed = TRUE;
   *out_n = (int)nread;
   return ret;
 }
@@ -697,7 +700,7 @@ cr_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 
   DEBUGASSERT(backend);
 
-  if(backend->conn) {
+  if(backend->conn && !connssl->peer_closed) {
     rustls_connection_send_close_notify(backend->conn);
     n = cr_send(cf, data, NULL, 0, &tmperr);
     if(n < 0) {
index af7ae552edcd7e837f1baecf187957a8347745c3..0361fa95a8ddf72059f53f4fe329bc68d0a201ee 100644 (file)
@@ -76,6 +76,7 @@ struct ssl_connect_data {
   int port;                         /* remote port at origin */
   BIT(use_alpn);                    /* if ALPN shall be used in handshake */
   BIT(reused_session);              /* session-ID was reused for this */
+  BIT(peer_closed);                 /* peer has closed connection */
 };
 
 
index a3c017ceaab54e4a9ec8f186645d7b11f468fbad..c264639bacd528778ebe43569bb7017fe68edeca 100644 (file)
@@ -321,6 +321,8 @@ static int wolfssl_bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen)
   wolfSSL_BIO_clear_retry_flags(bio);
   if(nread < 0 && CURLE_AGAIN == result)
     BIO_set_retry_read(bio);
+  else if(nread == 0)
+    connssl->peer_closed = TRUE;
   return (int)nread;
 }
 
@@ -1059,7 +1061,8 @@ static void wolfssl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
     /* Maybe the server has already sent a close notify alert.
        Read it to avoid an RST on the TCP connection. */
     (void)wolfSSL_read(backend->handle, buf, (int)sizeof(buf));
-    (void)wolfSSL_shutdown(backend->handle);
+    if(!connssl->peer_closed)
+      (void)wolfSSL_shutdown(backend->handle);
     wolfSSL_free(backend->handle);
     backend->handle = NULL;
   }