From 908c4a2ac9587acafb7a0ebce812d4458f482c9b Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 Jul 2026 12:34:58 -0700 Subject: [PATCH] Fix TLS 1.3 reads with mbedtls, and errno signaling on Windows (#592) In TLS 1.3 the server sends NewSessionTicket after the handshake. With mbedtls 3.6+ mbedtls_ssl_read can surface this as MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET (same for early data), a non-fatal "read again" code. SocketMbedTLS::recv treated it as a fatal error, so the first read after the handshake failed with "Failed reading HTTP status line". Retry the read instead. Also fix would-block signaling on Windows: recv/send set the CRT errno, but Socket::getErrno() reads WSAGetLastError(), so isWaitNeeded() aborted legitimate would-block retries. Add a Socket::setErrno() helper mirroring getErrno() and use it at all TLS backend error-signaling sites. This fixes the same latent bug in the OpenSSL backend. Map ECONNRESET to WSAECONNRESET in IXNetSystem.h so it round-trips through setErrno()/getErrno() like the other errno codes. Co-Authored-By: Claude Fable 5 --- ixwebsocket/IXNetSystem.h | 2 ++ ixwebsocket/IXSocket.cpp | 11 +++++++++++ ixwebsocket/IXSocket.h | 1 + ixwebsocket/IXSocketAppleSSL.cpp | 8 ++++---- ixwebsocket/IXSocketMbedTLS.cpp | 22 +++++++++++++++++++--- ixwebsocket/IXSocketOpenSSL.cpp | 4 ++-- 6 files changed, 39 insertions(+), 9 deletions(-) diff --git a/ixwebsocket/IXNetSystem.h b/ixwebsocket/IXNetSystem.h index 2ff12823..061b3622 100644 --- a/ixwebsocket/IXNetSystem.h +++ b/ixwebsocket/IXNetSystem.h @@ -34,6 +34,7 @@ #undef EINPROGRESS #undef EBADF #undef EINVAL +#undef ECONNRESET // map to WSA error codes #define EWOULDBLOCK WSAEWOULDBLOCK @@ -41,6 +42,7 @@ #define EINPROGRESS WSAEINPROGRESS #define EBADF WSAEBADF #define EINVAL WSAEINVAL +#define ECONNRESET WSAECONNRESET // Define our own poll on Windows, as a wrapper on top of select typedef unsigned long int nfds_t; diff --git a/ixwebsocket/IXSocket.cpp b/ixwebsocket/IXSocket.cpp index 8f9e5665..0181db10 100644 --- a/ixwebsocket/IXSocket.cpp +++ b/ixwebsocket/IXSocket.cpp @@ -286,6 +286,17 @@ namespace ix return err; } + void Socket::setErrno(int err) + { +#ifdef _WIN32 + // getErrno() reads WSAGetLastError() on Windows, the CRT errno is + // invisible to it + WSASetLastError(err); +#else + errno = err; +#endif + } + bool Socket::isWaitNeeded() { int err = getErrno(); diff --git a/ixwebsocket/IXSocket.h b/ixwebsocket/IXSocket.h index 411371bb..6c9f54de 100644 --- a/ixwebsocket/IXSocket.h +++ b/ixwebsocket/IXSocket.h @@ -81,6 +81,7 @@ namespace ix const CancellationRequest& isCancellationRequested); static int getErrno(); + static void setErrno(int err); static bool isWaitNeeded(); static void closeSocket(socket_t fd); diff --git a/ixwebsocket/IXSocketAppleSSL.cpp b/ixwebsocket/IXSocketAppleSSL.cpp index 3a529dd2..caa6ae16 100644 --- a/ixwebsocket/IXSocketAppleSSL.cpp +++ b/ixwebsocket/IXSocketAppleSSL.cpp @@ -267,13 +267,13 @@ namespace ix if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify || status == errSSLClosedAbort) { - errno = ECONNRESET; + Socket::setErrno(ECONNRESET); return -1; } if (status == errSSLWouldBlock) { - errno = EWOULDBLOCK; + Socket::setErrno(EWOULDBLOCK); return -1; } } @@ -297,13 +297,13 @@ namespace ix if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify || status == errSSLClosedAbort) { - errno = ECONNRESET; + Socket::setErrno(ECONNRESET); return -1; } if (status == errSSLWouldBlock) { - errno = EWOULDBLOCK; + Socket::setErrno(EWOULDBLOCK); return -1; } } diff --git a/ixwebsocket/IXSocketMbedTLS.cpp b/ixwebsocket/IXSocketMbedTLS.cpp index aa404f76..fc2013a1 100644 --- a/ixwebsocket/IXSocketMbedTLS.cpp +++ b/ixwebsocket/IXSocketMbedTLS.cpp @@ -347,7 +347,7 @@ namespace ix } else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE) { - errno = EWOULDBLOCK; + Socket::setErrno(EWOULDBLOCK); return -1; } else @@ -369,14 +369,30 @@ namespace ix return res; } + // TLS 1.3 (mbedtls 3.6+): the server can send post-handshake messages + // (NewSessionTicket, early data) which surface as non-fatal "read again" + // return codes. Retry the read instead of treating them as errors. +#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) + if (res == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) + { + continue; + } +#endif +#if defined(MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA) + if (res == MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA) + { + continue; + } +#endif + if (res == 0) { - errno = ECONNRESET; + Socket::setErrno(ECONNRESET); } if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE) { - errno = EWOULDBLOCK; + Socket::setErrno(EWOULDBLOCK); } return -1; } diff --git a/ixwebsocket/IXSocketOpenSSL.cpp b/ixwebsocket/IXSocketOpenSSL.cpp index 5ce165f4..93863315 100644 --- a/ixwebsocket/IXSocketOpenSSL.cpp +++ b/ixwebsocket/IXSocketOpenSSL.cpp @@ -837,7 +837,7 @@ namespace ix } else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) { - errno = EWOULDBLOCK; + Socket::setErrno(EWOULDBLOCK); return -1; } else @@ -869,7 +869,7 @@ namespace ix if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) { - errno = EWOULDBLOCK; + Socket::setErrno(EWOULDBLOCK); } return -1; }