]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
gnutls_session_get_data2: fix operation without a timeout callback
authorNikos Mavrogiannopoulos <nmav@redhat.com>
Wed, 25 Sep 2019 04:23:22 +0000 (06:23 +0200)
committerNikos Mavrogiannopoulos <nmav@redhat.com>
Thu, 26 Sep 2019 05:13:57 +0000 (07:13 +0200)
When TLS1.3 was introduced, gnutls_session_get_data2 was modified
to assume that the callbacks set included the timeout one which was
not previously necessary except for some special cases. This corrects
that issue and makes sure that gnutls_session_get_data2() does not
fail (but not necessarily succeed), if that timeout callback is not
set.

Resolves: #823

Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
NEWS
doc/cha-upgrade.texi
lib/buffers.c
lib/buffers.h
lib/session.c
lib/system_override.c
tests/Makefile.am
tests/tls13-without-timeout-func.c [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index e0320042c3769c33cde1a00b47ca92314aa7fef8..538256a0b73f32871215866edf0f01f2815ee7d8 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -15,6 +15,10 @@ See the end for copying conditions.
 ** libgnutls: add gnutls_aead_cipher_encryptv2 and gnutls_aead_cipher_decryptv2
    functions that will perform in-place encryption/decryption on data buffers (#718).
 
+** libgnutls: Corrected issue in gnutls_session_get_data2() which could fail under
+   TLS1.3, if a timeout callback was not set using gnutls_transport_set_pull_timeout_function()
+   (#823).
+
 ** libgnutls: added interoperability tests with gnutls 2.12.x; addressed
    issue with large record handling due to random padding (#811).
 
index 286790de5b448f54af900da8b4043fd534fe7d12..91c6803ec6742fffceef1b4bf52bf491e01db46e 100644 (file)
@@ -241,7 +241,9 @@ TLS 1.3 is done via session tickets, c.f. @funcref{gnutls_session_ticket_enable_
 @item @funcref{gnutls_session_get_data2}, @funcref{gnutls_session_get_data}
 @tab These functions may introduce a slight delay under TLS 1.3 for few
 milliseconds. Check output of @funcref{gnutls_session_get_flags} for GNUTLS_SFLAGS_SESSION_TICKET
-before calling this function to avoid delays.
+before calling this function to avoid delays. To work efficiently under
+TLS 1.3 this function requires the application setting
+@funcref{gnutls_transport_set_pull_timeout_function}.
 
 @item SRP and RSA-PSK key exchanges are not supported under TLS 1.3
 @tab SRP and RSA-PSK key exchanges are not supported in TLS 1.3, so when these key exchanges are present in a priority string, TLS 1.3 is disabled.
index 32202c6ffbaff473464569efcf1bb7421c5cf9a8..f3749b70e208b873d17d79c0accf7c5155aa97d6 100644 (file)
@@ -741,9 +741,7 @@ int _gnutls_io_check_recv(gnutls_session_t session, unsigned int ms)
        gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr;
        int ret = 0, err;
 
-       if (unlikely
-           (session->internals.pull_timeout_func == gnutls_system_recv_timeout
-            && session->internals.pull_func != system_read)) {
+       if (NO_TIMEOUT_FUNC_SET(session)) {
                _gnutls_debug_log("The pull function has been replaced but not the pull timeout.\n");
                return gnutls_assert_val(GNUTLS_E_PULL_ERROR);
        }
index 7b76423c4be2e836172f884c4f5d87cd607385f6..7f30b0ade11a93b6d0ff6f3f8b104905d16ee8fd 100644 (file)
@@ -37,6 +37,9 @@ inline static int _gnutls_record_buffer_get_size(gnutls_session_t session)
        return session->internals.record_buffer.byte_length;
 }
 
+#define NO_TIMEOUT_FUNC_SET(session) unlikely(session->internals.pull_timeout_func == gnutls_system_recv_timeout \
+            && session->internals.pull_func != system_read)
+
 /*-
  * record_check_unprocessed:
  * @session: is a #gnutls_session_t structure.
index 6deda99c0759d486e5afc70418bef6455e7b0938..71bcb40515ce3b2ee20700590bec2c148677a128 100644 (file)
@@ -109,6 +109,12 @@ gnutls_session_get_data(gnutls_session_t session,
  * received, will return session resumption data corresponding to the last
  * received ticket.
  *
+ * Note that this function under TLS1.3 requires a callback to be set with
+ * gnutls_transport_set_pull_timeout_function() for successful operation. There
+ * was a bug before 3.6.10 which could make this function fail if that callback
+ * was not set. On later versions if not set, the function will return a successful
+ * error code, but will return dummy data that cannot lead to a resumption.
+ *
  * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise
  *   an error code is returned.
  **/
@@ -128,10 +134,17 @@ gnutls_session_get_data2(gnutls_session_t session, gnutls_datum_t *data)
                 * the value(s). */
                ertt += 60;
 
-               /* wait for a message with timeout */
-               ret = _gnutls_recv_in_buffers(session, GNUTLS_APPLICATION_DATA, -1, ertt);
-               if (ret < 0 && (gnutls_error_is_fatal(ret) && ret != GNUTLS_E_TIMEDOUT)) {
-                       return gnutls_assert_val(ret);
+               /* we cannot use a read with timeout if the caller has not set
+                * a callback with gnutls_transport_set_pull_timeout_function() */
+               if (NO_TIMEOUT_FUNC_SET(session) || (session->internals.flags & GNUTLS_NONBLOCK)) {
+                       if (!(session->internals.flags & GNUTLS_NONBLOCK))
+                               _gnutls_debug_log("TLS1.3 works efficiently if a callback with gnutls_transport_set_pull_timeout_function() is set\n");
+               } else {
+                       /* wait for a message with timeout */
+                       ret = _gnutls_recv_in_buffers(session, GNUTLS_APPLICATION_DATA, -1, ertt);
+                       if (ret < 0 && (gnutls_error_is_fatal(ret) && ret != GNUTLS_E_TIMEDOUT)) {
+                               return gnutls_assert_val(ret);
+                       }
                }
 
                if (!(session->internals.hsk_flags & HSK_TICKET_RECEIVED)) {
index f2fc5749d9e6b88e79ca4de3176ab8e74d9c9b9b..9179bf5c8a2c67df19e5b8e6d5832d9728a84129 100644 (file)
@@ -106,15 +106,13 @@ gnutls_transport_set_pull_function(gnutls_session_t session,
  * int (*gnutls_pull_timeout_func)(gnutls_transport_ptr_t, unsigned int ms);
  *
  * This callback is necessary when gnutls_handshake_set_timeout() or 
- * gnutls_record_set_timeout() are set, and for calculating the DTLS mode
- * timeouts.
- *
- * In short, this callback should be set when a custom pull function is
- * registered. The callback will not be used when the session is in TLS mode with
- * non-blocking sockets. That is, when %GNUTLS_NONBLOCK is specified for a TLS
- * session in gnutls_init(). For compatibility with future GnuTLS versions
- * it is recommended to always set this function when a custom pull function
- * is registered.
+ * gnutls_record_set_timeout() are set, under TLS1.3 and for enforcing the DTLS
+ * mode timeouts when in blocking mode.
+ *
+ * For compatibility with future GnuTLS versions this callback must be set when
+ * a custom pull function is registered. The callback will not be used when the
+ * session is in TLS mode with non-blocking sockets. That is, when %GNUTLS_NONBLOCK
+ * is specified for a TLS session in gnutls_init().
  *
  * The helper function gnutls_system_recv_timeout() is provided to
  * simplify writing callbacks. 
index f08f76d0dd439591be5d6b67533eba26cbfd0389..c8458c2ba5e366304cebd475d12774ed5e38fef2 100644 (file)
@@ -214,7 +214,8 @@ ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniquei
         null_retrieve_function tls-record-size-limit tls-crt_type-neg \
         resume-with-stek-expiration resume-with-previous-stek rawpk-api \
         tls-record-size-limit-asym dh-compute ecdh-compute sign-verify-data-newapi \
-        sign-verify-newapi sign-verify-deterministic iov aead-cipher-vec
+        sign-verify-newapi sign-verify-deterministic iov aead-cipher-vec \
+        tls13-without-timeout-func
 
 if HAVE_SECCOMP_TESTS
 ctests += dtls-with-seccomp tls-with-seccomp dtls-client-with-seccomp tls-client-with-seccomp
diff --git a/tests/tls13-without-timeout-func.c b/tests/tls13-without-timeout-func.c
new file mode 100644 (file)
index 0000000..8235835
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 Red Hat, Inc.
+ *
+ * Author: Nikos Mavrogiannopoulos
+ *
+ * This file is part of GnuTLS.
+ *
+ * GnuTLS is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuTLS is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <gnutls/gnutls.h>
+#include "utils.h"
+#include "eagain-common.h"
+#include "cert-common.h"
+
+/* This tests TLS1.3 and gnutls_session_get_data2() when no
+ * callback with gnutls_transport_set_pull_timeout_function()
+ * is set */
+
+const char *side;
+
+static void tls_log_func(int level, const char *str)
+{
+       fprintf(stderr, "%s|<%d>| %s", side, level, str);
+}
+
+static time_t mytime(time_t * t)
+{
+       time_t then = 1461671166;
+
+       if (t)
+               *t = then;
+
+       return then;
+}
+
+static ssize_t
+server_pull_fail(gnutls_transport_ptr_t tr, void *data, size_t len)
+{
+       fail("unexpected call to pull callback detected\n");
+       return -1;
+}
+
+void doit(void)
+{
+       int ret;
+       /* Server stuff. */
+       gnutls_certificate_credentials_t serverx509cred;
+       gnutls_session_t server;
+       int sret = GNUTLS_E_AGAIN;
+       /* Client stuff. */
+       gnutls_certificate_credentials_t clientx509cred;
+       gnutls_session_t client;
+       int cret = GNUTLS_E_AGAIN;
+       gnutls_datum_t data;
+       char buf[128];
+
+       /* General init. */
+       global_init();
+       gnutls_global_set_log_function(tls_log_func);
+       if (debug)
+               gnutls_global_set_log_level(6);
+
+       gnutls_global_set_time_function(mytime);
+
+       assert(gnutls_certificate_allocate_credentials(&serverx509cred) >= 0);
+       assert(gnutls_certificate_set_x509_key_mem(serverx509cred,
+                                           &server_cert, &server_key,
+                                           GNUTLS_X509_FMT_PEM) >= 0);
+
+       assert(gnutls_init(&server, GNUTLS_SERVER) >= 0);
+       gnutls_credentials_set(server, GNUTLS_CRD_CERTIFICATE,
+                               serverx509cred);
+       assert(gnutls_set_default_priority(server)>=0);
+       gnutls_transport_set_push_function(server, server_push);
+       gnutls_transport_set_pull_function(server, server_pull);
+       gnutls_transport_set_ptr(server, server);
+
+       assert(gnutls_certificate_allocate_credentials(&clientx509cred)>=0);
+
+       assert(gnutls_certificate_set_x509_trust_mem(clientx509cred, &ca_cert, GNUTLS_X509_FMT_PEM)>=0);
+
+       assert(gnutls_init(&client, GNUTLS_CLIENT)>=0);
+
+       assert(gnutls_credentials_set(client, GNUTLS_CRD_CERTIFICATE,
+                                     clientx509cred)>=0);
+
+       assert(gnutls_priority_set_direct(client, "NORMAL:-VERS-ALL:+VERS-TLS1.3", NULL)>=0);
+       gnutls_transport_set_push_function(client, client_push);
+       gnutls_transport_set_pull_function(client, client_pull);
+       gnutls_transport_set_ptr(client, client);
+
+       HANDSHAKE(client, server);
+
+       ret = gnutls_record_recv(client, buf, sizeof(buf));
+       if (ret < 0 && ret != GNUTLS_E_AGAIN) {
+               fail("unexpected error: %s\n", gnutls_strerror(ret));
+       }
+
+       gnutls_transport_set_pull_function(server, server_pull_fail);
+
+       ret = gnutls_session_get_data2(client, &data);
+       if (ret != 0) {
+               fail("unexpected error: %s\n", gnutls_strerror(ret));
+       }
+       gnutls_free(data.data);
+       gnutls_transport_set_pull_function(server, server_pull);
+
+       ret = gnutls_record_recv(client, buf, sizeof(buf));
+       if (ret < 0 && ret != GNUTLS_E_AGAIN) {
+               fail("unexpected error: %s\n", gnutls_strerror(ret));
+       }
+
+       gnutls_bye(client, GNUTLS_SHUT_RDWR);
+       gnutls_bye(server, GNUTLS_SHUT_RDWR);
+
+       gnutls_deinit(client);
+       gnutls_deinit(server);
+
+       gnutls_certificate_free_credentials(serverx509cred);
+       gnutls_certificate_free_credentials(clientx509cred);
+
+       gnutls_global_deinit();
+}