[PATCH 4 of 5] SSL: clarified clean shutdown condition
Maxim Dounin
mdounin at mdounin.ru
Sun Mar 15 12:08:27 UTC 2026
# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1773535443 -10800
# Sun Mar 15 03:44:03 2026 +0300
# Node ID e13d7e2a2a80a32bffd14e444edc05039b35c066
# Parent 0909ff44dc383f6869ad127c0c71eaf394af94fa
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. Starting
with OpenSSL 3.0 and with SSL_OP_IGNORE_UNEXPECTED_EOF, the
SSL_ERROR_ZERO_RETURN is reported.
Closing the connection without close_notify alert is incorrect, yet quite
common in the real world, and therefore this is handled as a non-error
condition. Potential truncation attacks are expected to be handled at
the protocol level (notably, truncation attacks are not at all possible
for HTTP/1.x requests, and only possible for HTTP/1.x responses if the
server uses neither Content-Length nor chunked transfer encoding).
Still, previously "ERR_peek_error() == 0" was checked to catch this, which
seems too broad. This condition also catches TCP-level errors, such as
ECONNRESET, which are better to be explicitly reported as errors.
With this change, only clean TCP close is reported as clean connection
close. Most notably, connection resets now result in "SSL_read() failed"
errors.
diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -2430,6 +2430,17 @@ ngx_ssl_handshake(ngx_connection_t *c)
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;
+ }
+
if (sslerr != SSL_ERROR_SYSCALL) {
err = 0;
}
@@ -2438,7 +2449,7 @@ ngx_ssl_handshake(ngx_connection_t *c)
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");
@@ -2581,6 +2592,17 @@ ngx_ssl_try_early_data(ngx_connection_t
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;
+ }
+
if (sslerr != SSL_ERROR_SYSCALL) {
err = 0;
}
@@ -2589,7 +2611,7 @@ ngx_ssl_try_early_data(ngx_connection_t
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");
@@ -3101,6 +3123,17 @@ ngx_ssl_handle_recv(ngx_connection_t *c,
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;
+ }
+
if (sslerr != SSL_ERROR_SYSCALL) {
err = 0;
}
@@ -3108,7 +3141,7 @@ ngx_ssl_handle_recv(ngx_connection_t *c,
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;
@@ -3892,7 +3925,18 @@ ngx_ssl_shutdown(ngx_connection_t *c)
return NGX_AGAIN;
}
- if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 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;
+ }
+
+ if (sslerr == SSL_ERROR_ZERO_RETURN) {
goto done;
}
More information about the nginx-devel
mailing list