fix(protocol): classify TLS EOF before EINTR retry; harden current-ssl resolver; real TLS regression test

This commit is contained in:
2026-09-23 01:36:08 +02:00
parent c29bce54fc
commit 46dcefe218
4 changed files with 363 additions and 13 deletions
+17 -7
View File
@@ -99,8 +99,10 @@ static ssize_t tls_io_send(ProtocolSession* session, const void* data, size_t si
return PROTOCOL_IO_RETRY;
}
/* A signal (e.g. Ctrl-C) interrupts the blocking TLS write: retry so the
* send loop can observe the abort flag at the next checkpoint. */
if (ssl_err == SSL_ERROR_SYSCALL && errno == EINTR)
* send loop can observe the abort flag at the next checkpoint. Only an
* actual negative return is an interrupted syscall; a 0-byte SSL_write is
* not a valid EINTR retry. */
if (written < 0 && ssl_err == SSL_ERROR_SYSCALL && errno == EINTR)
return PROTOCOL_IO_RETRY;
return PROTOCOL_IO_ERROR;
}
@@ -125,8 +127,13 @@ static ssize_t tls_io_recv(ProtocolSession* session, void* data, size_t size, sh
return PROTOCOL_IO_RETRY;
}
/* A signal interrupts the blocking TLS read: retry (mirrors the send path)
* so the loop reaches its next abort/deadline checkpoint. */
if (ssl_err == SSL_ERROR_SYSCALL && errno == EINTR)
* so the loop reaches its next abort/deadline checkpoint. Only an actual
* negative return is an interrupted syscall: a 0-byte SSL_read is an
* unexpected EOF (the peer closed without close_notify), which OpenSSL also
* reports as SSL_ERROR_SYSCALL with errno possibly still EINTR from an
* earlier interrupted poll/read. Retrying that would busy-spin the
* status-read loop until its deadline, so classify it as closed instead. */
if (received < 0 && ssl_err == SSL_ERROR_SYSCALL && errno == EINTR)
return PROTOCOL_IO_RETRY;
/* A zero-length SSL_read is the peer's clean close_notify (or EOF without
* one); report it distinctly so the caller can log it as a close. */
@@ -396,9 +403,12 @@ SSL* protocol_current_ssl(void) {
/* The bound session is the authoritative transport for a worker thread: it
* was explicitly handed to protocol_session_bind() and carries its own SSL,
* whereas io_ssl is thread-local and NULL in a thread that never performed
* the handshake. With no session bound (the fd-shim path), fall back to the
* legacy thread-local SSL. */
if (bound_session)
* the handshake. Only a session whose selected dispatch is TLS may supply
* the SSL: a bound plaintext session has ssl == NULL and must not shadow a
* live thread-local io_ssl, or file_send.c would take the raw sendfile(2)
* path on a socket this thread is driving with TLS. With no TLS session
* bound (plaintext session, or the fd-shim path), fall back to io_ssl. */
if (bound_session && bound_session->ops == &tls_io_ops && bound_session->ssl)
return bound_session->ssl;
return io_ssl;
}
+6 -4
View File
@@ -249,13 +249,15 @@ unsigned long long io_get_bwlimit(void);
void io_set_ssl(SSL* ssl);
SSL* io_get_ssl(void);
/* SSL object of the transport in effect on this thread: the currently bound
* session's SSL when a session is bound, otherwise the legacy thread-local
* session's SSL when a TLS session is bound, otherwise the legacy thread-local
* io_ssl. NULL for a plaintext transport. Unlike io_get_ssl(), this resolves
* worker threads that bound a TLS session via protocol_session_set_ssl()/
* protocol_session_bind() but never called io_set_ssl() themselves (C11
* _Thread_local state is not inherited by a new thread). Callers that must
* choose a TLS-only code path (e.g. file_send.c's sendfile fallback) must use
* this instead of io_get_ssl(). */
* _Thread_local state is not inherited by a new thread). A bound session only
* wins when its selected dispatch is TLS; a bound plaintext session (ssl ==
* NULL) falls back to io_ssl so it can never mask a live encrypted transport.
* Callers that must choose a TLS-only code path (e.g. file_send.c's sendfile
* fallback) must use this instead of io_get_ssl(). */
SSL* protocol_current_ssl(void);
/* Process-wide wire byte counters. protocol_send_n_data/protocol_receive_n_data