On Fri, Aug 15, 2025 at 10:56:40AM +0300, Vladimir Homutov wrote: > Hello, > > Commits 7468a10b62276be4adee0fcd6aaf6244270984ab > "QUIC: adjusted handling of callback errors." > > and 47f96993f669543c6cb4979dd3f680ad01314ee5 > "QUIC: logging of SSL library errors." > > lead to the situation when you may get spurious > "ignoring stale global SSL error" errors in unrelated connections. > > This happens due to the fact that openssl error queue is (thread) global, > and quic handshake handler does not read out the error after the failed > handshake and relies on qc->error set by callback. > > So the error stays in queue and may show itself in unrelated quic connection, > typically on SSL shutdown. > > config below may be used to reproduce the issue: > > >>> > daemon off; > error_log logs/error.log debug; > events { > } > > http { > ssl_certificate_key localhost.key; > ssl_certificate localhost.crt; > server { > error_log logs/good.log debug; > listen 127.0.0.1:8080 quic; > location / { return 200 OK; } > } > server { > error_log logs/reject.log debug; > listen 127.0.0.1:8081 quic; > ssl_reject_handshake on; > location / { return 200 OK; } > } > } > <<< > > start the server and run: > > $ curl -k --http3 https://127.0.0.1:8080/ > $ curl -k --http3 https://127.0.0.1:8081/ > > The result is alert in good.log: > > 2025/08/15 09:58:19 [alert] 1154786#1154786: *1 ignoring stale global SSL > error (SSL: error:10000084:SSL routines:OPENSSL_internal:CLIENTHELLO_TLSEXT > error:100000be:SSL routines:OPENSSL_internal:PARSE_TLSEXT) while preparing > ack, client: 127.0.0.1, server: 127.0.0.1:8080 > > caused by failed handshake in server 2;
Thanks for reporting. I was able to reproduce this with Test::Nginx reliably: my $s = Test::Nginx::HTTP3->new(8980); my $s2 = Test::Nginx::HTTP3->new(8981, probe => 1); select undef, undef, undef, 0.1; $s = undef; select undef, undef, undef, 0.2; $t->stop(); So the error queue remains non-empty until the 1st connection SSL shutdown. The following happens as seen in debug (filtered): 2025/08/21 15:52:02 [debug] 73543#1490328: *1 quic run 2025/08/21 15:52:02 [debug] 73543#1490328: *1 SSL_do_handshake: -1 2025/08/21 15:52:02 [debug] 73543#1490328: *1 SSL_do_handshake: 1 2025/08/21 15:52:02 [debug] 73543#1490328: *6 quic run 2025/08/21 15:52:02 [debug] 73543#1490328: *6 quic ngx_quic_send_alert() level:0 alert:112 2025/08/21 15:52:02 [debug] 73543#1490328: *6 SSL_do_handshake: -1 2025/08/21 15:52:02 [alert] 73543#1490328: *1 ignoring stale global SSL error (SSL: error:10000084:SSL routines:OPENSSL_internal:CLIENTHELLO_TLSEXT error:100000be:SSL routines:OPENSSL_internal:PARSE_TLSEXT) while preparing ack, client: 127.0.0.1, server: 127.0.0.1:8980 2025/08/21 15:52:02 [debug] 73543#1490328: *1 quic ngx_quic_send_alert() level:3 alert:0 2025/08/21 15:52:02 [debug] 73543#1490328: *1 SSL_shutdown: 1 The above is using BoringSSL to match the report. This is equivalently broken with all other libraries with a slightly different error message: 2025/08/21 16:03:40 [alert] 78666#1507465: *1 ignoring stale global SSL error (SSL: error:0A0000EA:SSL routines::callback failed) while preparing ack, client: 127.0.0.1, server: 127.0.0.1:8980 I don't think 47f96993f669543c6cb4979dd3f680ad01314ee5 is related. Although it formally changes reproduction surface by ngx_ssl_error(), this touches errors, which should not normally happen in practice. 7468a10b62276be4adee0fcd6aaf6244270984ab is a real culprit. > > when the ngx_http_ssl_servername() callback returns error, > the ngx_quic_send_alert() sets qc->error, and the ngx_quic_ssl_handshake() > checks qc->error after SSL_do_handshake() and closes connection. > But the error stays, and manifests itself later in another connection > in the same process. > > The quic fix is probably something like this: > > diff --git a/src/event/quic/ngx_event_quic_ssl.c > b/src/event/quic/ngx_event_quic_ssl.c > index e961c80cd..dc0a030ff 100644 > --- a/src/event/quic/ngx_event_quic_ssl.c > +++ b/src/event/quic/ngx_event_quic_ssl.c > @@ -696,6 +696,7 @@ ngx_quic_handshake(ngx_connection_t *c) > ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", > n); > > if (qc->error) { > + ERR_clear_error(); > return NGX_ERROR; > } > > Or may be it makes sense to clear error in the moment of setting qc->error; > and do not clear everything, but try to log using ngx_ssl_error() with some > non-alert level. What happens here is the added early check for qc->error broke the check for handshake_rejected, resulting in a missing diagnostics for the SNI-rejected handshake, and in a missing ERR_clear_error(). This can be fixed by moving the qc->error check to later such that handshake_rejected is checked first to make the error queue cleared. Although not practicably visible as needed, this can be also accompanied by clearing the error queue under the qc->error case as well, to be safe. Fixing this is complicated by different codes returned from SSL_get_error() for handshake errors happened in QUIC callbacks in various SSL libraries: # define SSL_ERROR_SSL 1 # define SSL_ERROR_WANT_READ 2 # define SSL_ERROR_WANT_WRITE 3 compat 3.5 bssl qtls lssl tp 2 3* 2 2 2 sni 1 1 1 1 1 In this regard, checking c->ssl->handshake_rejected is moved out of "sslerr != SSL_ERROR_WANT_READ". It is a set only flag, so this should be safe to do. This also somewhat simplifies and streamlines code. Patch also reconstructs a missing "SSL_do_handshake() failed" diagnostics for the qc->error case. It is made this way to avoid logging at the crit log level because qc->error is expected to have an empty error queue. Please test if it works for you. diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index e961c80cd..1de896291 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -695,25 +695,25 @@ ngx_quic_handshake(ngx_connection_t *c) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - if (qc->error) { - return NGX_ERROR; - } - if (n <= 0) { sslerr = SSL_get_error(ssl_conn, n); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); - if (sslerr != SSL_ERROR_WANT_READ) { - - if (c->ssl->handshake_rejected) { - ngx_connection_error(c, 0, "handshake rejected"); - ERR_clear_error(); + if (c->ssl->handshake_rejected) { + ngx_connection_error(c, 0, "handshake rejected"); + ERR_clear_error(); + return NGX_ERROR; + } - return NGX_ERROR; - } + if (qc->error) { + ngx_connection_error(c, 0, "SSL_do_handshake() failed"); + ERR_clear_error(); + return NGX_ERROR; + } + if (sslerr != SSL_ERROR_WANT_READ) { ngx_ssl_connection_error(c, sslerr, 0, "SSL_do_handshake() failed"); return NGX_ERROR; } _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel