Skip to content

Fix TLS 1.3 reads with mbedtls, and errno signaling on Windows#594

Merged
bsergean merged 1 commit into
masterfrom
fix/tls13-mbedtls-new-session-ticket
Jul 15, 2026
Merged

Fix TLS 1.3 reads with mbedtls, and errno signaling on Windows#594
bsergean merged 1 commit into
masterfrom
fix/tls13-mbedtls-new-session-ticket

Conversation

@bsergean

Copy link
Copy Markdown
Collaborator

Fixes #592.

Problem

Connecting to a TLS 1.3 wss/https server (e.g. behind Cloudflare/nginx) with the mbedtls backend (mbedtls 3.6+, TLS 1.3 on by default) fails on the first read after the handshake with Failed reading HTTP status line.

Two issues combine:

  1. TLS 1.3 post-handshake messages treated as fatal. In TLS 1.3 the server sends NewSessionTicket after the handshake. mbedtls_ssl_read surfaces this as MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET (-0x7B00), a non-fatal "read again" code (same for MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA). SocketMbedTLS::recv treated any such code as a fatal error.
  2. Would-block signaling invisible on Windows. On WANT_READ/WANT_WRITE, recv/send set the CRT errno, but Socket::getErrno() reads WSAGetLastError() on Windows, so isWaitNeeded() aborted legitimate would-block retries. The OpenSSL backend had the same latent bug.

Fix

  • SocketMbedTLS::recv: retry the read on MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET / MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA (both guarded with #if defined(...) so older mbedtls still compiles).
  • New Socket::setErrno() helper mirroring getErrno(): WSASetLastError() on Windows, errno elsewhere. Used at all TLS backend error-signaling sites (mbedtls, OpenSSL, SecureTransport), fixing the Windows would-block bug in both the mbedtls and OpenSSL backends.
  • IXNetSystem.h: map ECONNRESETWSAECONNRESET alongside the existing errno mappings so it round-trips through setErrno()/getErrno().

Verification

Reproduced and verified on macOS against real TLS 1.3 servers (mbedtls 3.6.3):

  • On mbedtls 3.6.1+ the NewSessionTicket signal is opt-in (mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets, cf. Breakage due to enabling TLS 1.3 by default in 3.6.0 Mbed-TLS/mbedtls#9223), so to reproduce, the signal was temporarily forced on. Instrumentation confirmed -0x7b00 coming out of mbedtls_ssl_read, and an HTTPS GET to cloudflare.com failed exactly as reported (on POSIX the bug is usually masked by a stale errno == EAGAIN from the preceding nonblocking read, which is why it mostly bites on Windows).
  • With the fix, that same worst-case configuration passes: HTTPS GET returns 200 and a wss://echo.websocket.org round-trip echoes correctly.
  • All three TLS backends (mbedtls, OpenSSL, SecureTransport) build cleanly and pass the same end-to-end test.

🤖 Generated with Claude Code

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 <noreply@anthropic.com>
@bsergean
bsergean merged commit e120cfc into master Jul 15, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TLS 1.3 NewSessionTicket breaks read ("Failed reading HTTP status line")

1 participant