From 381c876ec6a266177f8e3ae5df78b5a99a558360 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Mon, 14 Sep 2026 13:34:20 +0000 Subject: [PATCH] SSL: clarified clean shutdown condition OpenSSL up to 3.0, when a TCP connection is closed by the peer, returns SSL_ERROR_SYSCALL without an error queue and with errno set to 0. Since OpenSSL 3.0 with SSL_OP_IGNORE_UNEXPECTED_EOF, which nginx sets, this is reported as SSL_ERROR_ZERO_RETURN instead. Closing a connection without the close_notify alert is incorrect, yet common enough in the real world to be handled as a non-error condition. Truncation attacks are expected to be prevented at the protocol level; notably, they are not possible for HTTP/1.x requests at all, and only possible for HTTP/1.x responses if neither Content-Length nor chunked transfer encoding is used. Previously, "ERR_peek_error() == 0" was used to detect this, which is too broad: it also matches TCP-level errors, such as ECONNRESET, which are better reported as errors, the way the plain TCP code does it in ngx_unix_recv(). Now only a clean TCP close is treated as a clean connection close. Most notably, connection resets now result in "SSL_read() failed" errors. This change is a modified version of a patch by Maxim Dounin. The modifications include resolved conflicts and the refined commit log. Signed-off-by: Sergey Kandaurov Origin: --- src/event/ngx_event_openssl.c | 56 +++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 1f3cbb2bd..c9f94c982 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2319,11 +2319,22 @@ ngx_ssl_handshake(ngx_connection_t *c) err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; c->read->eof = 1; - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_ZERO_RETURN) { ngx_connection_error(c, err, "peer closed connection in SSL handshake"); @@ -2466,11 +2477,22 @@ ngx_ssl_try_early_data(ngx_connection_t *c) err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; c->read->eof = 1; - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_ZERO_RETURN) { ngx_connection_error(c, err, "peer closed connection in SSL handshake"); @@ -2979,10 +3001,21 @@ ngx_ssl_handle_recv(ngx_connection_t *c, int n) return NGX_AGAIN; } + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; + } + c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + if (sslerr == SSL_ERROR_ZERO_RETURN) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "peer shutdown SSL cleanly"); return NGX_DONE; @@ -3738,11 +3771,22 @@ ngx_ssl_shutdown(ngx_connection_t *c) return NGX_AGAIN; } - if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { - goto done; + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + + if (sslerr == SSL_ERROR_SYSCALL && ERR_peek_error() == 0 && err == 0) { + + /* + * OpenSSL up to 3.0 returns SSL_ERROR_SYSCALL + * without an error queue and with errno set to 0 + * if connection is closed cleanly + */ + + sslerr = SSL_ERROR_ZERO_RETURN; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + if (sslerr == SSL_ERROR_ZERO_RETURN) { + goto done; + } ngx_ssl_connection_error(c, sslerr, err, "SSL_shutdown() failed"); -- 2.47.3