Commit 7b5ddfb157 for openssl.org
commit 7b5ddfb157671b360d04175dcac656c6dc6b8edd
Author: Igor Ustinov <igus68@gmail.com>
Date: Sat Feb 7 10:21:22 2026 +0100
SSL_get_error(): Do not depend on the state of the error stack
We check in relevant functions (SSL_handshake(), SSL_read(), etc.) whether
a new error has been pushed onto the error stack, and if so, memorise this
fact in the SSL structure. After that SSL_get_error() uses this memorised
information instead of checking the error stack itself.
Fixes #11889
Fixes openssl/project#1715
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
MergeDate: Wed Feb 18 15:27:38 2026
(Merged from https://github.com/openssl/openssl/pull/29991)
diff --git a/CHANGES.md b/CHANGES.md
index a2621cb75c..d30b96ace3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -73,6 +73,12 @@ OpenSSL 4.0
*Neil Horman*
+ * SSL_get_error() no longer depends on the state of the error stack,
+ so it is no longer necessary to empty the error queue before the
+ TLS/SSL I/O operations.
+
+ *Igor Ustinov*
+
* Added configure options to disable KDF algorithms for
hmac-drbg-kdf, kbkdf, krb5kdf, pvkkdf, snmpkdf, sskdf, sshkdf, x942kdf and x963kdf.
diff --git a/doc/man3/SSL_get_error.pod b/doc/man3/SSL_get_error.pod
index db750cc37f..07a79d667a 100644
--- a/doc/man3/SSL_get_error.pod
+++ b/doc/man3/SSL_get_error.pod
@@ -18,14 +18,6 @@ SSL_read_ex(), SSL_read(), SSL_peek_ex(), SSL_peek(), SSL_shutdown(),
SSL_write_ex() or SSL_write() on B<ssl>. The value returned by that TLS/SSL I/O
function must be passed to SSL_get_error() in parameter B<ret>.
-In addition to B<ssl> and B<ret>, SSL_get_error() inspects the
-current thread's OpenSSL error queue. Thus, SSL_get_error() must be
-used in the same thread that performed the TLS/SSL I/O operation, and no
-other OpenSSL function calls should appear in between. The current
-thread's error queue must be empty before the TLS/SSL I/O operation is
-attempted, or SSL_get_error() will not work reliably. Emptying the
-current thread's error queue is done with L<ERR_clear_error(3)>.
-
=head1 NOTES
Some TLS implementations do not send a close_notify alert on shutdown.
@@ -195,6 +187,10 @@ L<ERR_clear_error(3)>, ERR_print_errors(3), ERR_peek_last_error_all(3)
The SSL_ERROR_WANT_ASYNC error code was added in OpenSSL 1.1.0.
The SSL_ERROR_WANT_CLIENT_HELLO_CB error code was added in OpenSSL 1.1.1.
+Since OpenSSL 4.0 SSL_get_error() no longer depends on the state of the
+error stack, so it is no longer necessary to empty the error queue
+before the TLS/SSL I/O operations.
+
=head1 COPYRIGHT
Copyright 2000-2024 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/include/internal/statem.h b/include/internal/statem.h
index a41ed32270..aab3dcbab5 100644
--- a/include/internal/statem.h
+++ b/include/internal/statem.h
@@ -81,6 +81,12 @@ typedef enum {
CON_FUNC_DONT_SEND
} CON_FUNC_RETURN;
+typedef enum {
+ ERROR_STATE_NOERROR = 0,
+ ERROR_STATE_SSL,
+ ERROR_STATE_SYSCALL
+} ERROR_STATE;
+
typedef int (*ossl_statem_mutate_handshake_cb)(const unsigned char *msgin,
size_t inlen,
unsigned char **msgout,
@@ -106,6 +112,7 @@ struct ossl_statem_st {
OSSL_HANDSHAKE_STATE hand_state;
/* The handshake state requested by an API call (e.g. HelloRequest) */
OSSL_HANDSHAKE_STATE request_state;
+ ERROR_STATE error_state;
int in_init;
int read_state_first_init;
/* true when we are actually in SSL_accept() or SSL_connect() */
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index f105408ecd..4949fc9229 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -2185,23 +2185,58 @@ int SSL_get_async_status(SSL *s, int *status)
return 1;
}
+static void ssl_clear_error_state(SSL *s)
+{
+ SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s);
+
+ if (sc != NULL)
+ sc->statem.error_state = ERROR_STATE_NOERROR;
+ ERR_set_mark();
+}
+
+static void ssl_check_error_stack(SSL *s)
+{
+ SSL_CONNECTION *sc;
+ unsigned long l;
+ if (ERR_count_to_mark() > 0) {
+ sc = SSL_CONNECTION_FROM_SSL(s);
+ l = ERR_peek_error();
+ if (sc != NULL && l != 0) {
+ if (ERR_GET_LIB(l) == ERR_LIB_SYS)
+ sc->statem.error_state = ERROR_STATE_SYSCALL;
+ else
+ sc->statem.error_state = ERROR_STATE_SSL;
+ }
+ }
+ ERR_clear_last_mark();
+}
+
int SSL_accept(SSL *s)
{
SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s);
+ ssl_clear_error_state(s);
+
#ifndef OPENSSL_NO_QUIC
- if (IS_QUIC(s))
- return s->method->ssl_accept(s);
+ if (IS_QUIC(s)) {
+ int ret = s->method->ssl_accept(s);
+ ssl_check_error_stack(s);
+ return ret;
+ }
#endif
- if (sc == NULL)
+ if (sc == NULL) {
+ ERR_clear_last_mark();
return 0;
+ }
if (sc->handshake_func == NULL) {
/* Not properly initialized yet */
SSL_set_accept_state(s);
}
+ ssl_check_error_stack(s);
+
return SSL_do_handshake(s);
}
@@ -2209,19 +2244,28 @@ int SSL_connect(SSL *s)
{
SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s);
+ ssl_clear_error_state(s);
+
#ifndef OPENSSL_NO_QUIC
- if (IS_QUIC(s))
- return s->method->ssl_connect(s);
+ if (IS_QUIC(s)) {
+ int ret = s->method->ssl_connect(s);
+ ssl_check_error_stack(s);
+ return ret;
+ }
#endif
- if (sc == NULL)
+ if (sc == NULL) {
+ ERR_clear_last_mark();
return 0;
+ }
if (sc->handshake_func == NULL) {
/* Not properly initialized yet */
SSL_set_connect_state(s);
}
+ ssl_check_error_stack(s);
+
return SSL_do_handshake(s);
}
@@ -2366,8 +2410,11 @@ int SSL_read(SSL *s, void *buf, int num)
int ret;
size_t readbytes;
+ ssl_clear_error_state(s);
+
if (num < 0) {
ERR_raise(ERR_LIB_SSL, SSL_R_BAD_LENGTH);
+ ssl_check_error_stack(s);
return -1;
}
@@ -2380,15 +2427,23 @@ int SSL_read(SSL *s, void *buf, int num)
if (ret > 0)
ret = (int)readbytes;
+ ssl_check_error_stack(s);
+
return ret;
}
int SSL_read_ex(SSL *s, void *buf, size_t num, size_t *readbytes)
{
- int ret = ssl_read_internal(s, buf, num, readbytes);
+ int ret;
+
+ ssl_clear_error_state(s);
+ ret = ssl_read_internal(s, buf, num, readbytes);
if (ret < 0)
ret = 0;
+
+ ssl_check_error_stack(s);
+
return ret;
}
@@ -2501,8 +2556,11 @@ int SSL_peek(SSL *s, void *buf, int num)
int ret;
size_t readbytes;
+ ssl_clear_error_state(s);
+
if (num < 0) {
ERR_raise(ERR_LIB_SSL, SSL_R_BAD_LENGTH);
+ ssl_check_error_stack(s);
return -1;
}
@@ -2515,15 +2573,23 @@ int SSL_peek(SSL *s, void *buf, int num)
if (ret > 0)
ret = (int)readbytes;
+ ssl_check_error_stack(s);
+
return ret;
}
int SSL_peek_ex(SSL *s, void *buf, size_t num, size_t *readbytes)
{
- int ret = ssl_peek_internal(s, buf, num, readbytes);
+ int ret;
+
+ ssl_clear_error_state(s);
+ ret = ssl_peek_internal(s, buf, num, readbytes);
if (ret < 0)
ret = 0;
+
+ ssl_check_error_stack(s);
+
return ret;
}
@@ -2656,8 +2722,11 @@ int SSL_write(SSL *s, const void *buf, int num)
int ret;
size_t written;
+ ssl_clear_error_state(s);
+
if (num < 0) {
ERR_raise(ERR_LIB_SSL, SSL_R_BAD_LENGTH);
+ ssl_check_error_stack(s);
return -1;
}
@@ -2670,6 +2739,8 @@ int SSL_write(SSL *s, const void *buf, int num)
if (ret > 0)
ret = (int)written;
+ ssl_check_error_stack(s);
+
return ret;
}
@@ -2681,10 +2752,16 @@ int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written)
int SSL_write_ex2(SSL *s, const void *buf, size_t num, uint64_t flags,
size_t *written)
{
- int ret = ssl_write_internal(s, buf, num, flags, written);
+ int ret;
+
+ ssl_clear_error_state(s);
+ ret = ssl_write_internal(s, buf, num, flags, written);
if (ret < 0)
ret = 0;
+
+ ssl_check_error_stack(s);
+
return ret;
}
@@ -2773,18 +2850,26 @@ int SSL_shutdown(SSL *s)
* (see ssl3_shutdown).
*/
SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s);
+ int ret;
+
+ ssl_clear_error_state(s);
#ifndef OPENSSL_NO_QUIC
- if (IS_QUIC(s))
- return ossl_quic_conn_shutdown(s, 0, NULL, 0);
+ if (IS_QUIC(s)) {
+ ret = ossl_quic_conn_shutdown(s, 0, NULL, 0);
+ goto end;
+ }
#endif
- if (sc == NULL)
- return -1;
+ if (sc == NULL) {
+ ret = -1;
+ goto end;
+ }
if (sc->handshake_func == NULL) {
ERR_raise(ERR_LIB_SSL, SSL_R_UNINITIALIZED);
- return -1;
+ ret = -1;
+ goto end;
}
if (!SSL_in_init(s)) {
@@ -2796,14 +2881,18 @@ int SSL_shutdown(SSL *s)
args.type = OTHERFUNC;
args.f.func_other = s->method->ssl_shutdown;
- return ssl_start_async_job(s, &args, ssl_io_intern);
+ ret = ssl_start_async_job(s, &args, ssl_io_intern);
} else {
- return s->method->ssl_shutdown(s);
+ ret = s->method->ssl_shutdown(s);
}
} else {
ERR_raise(ERR_LIB_SSL, SSL_R_SHUTDOWN_WHILE_IN_INIT);
- return -1;
+ ret = -1;
}
+
+end:
+ ssl_check_error_stack(s);
+ return ret;
}
int SSL_key_update(SSL *s, int updatetype)
@@ -4858,7 +4947,6 @@ int SSL_get_error(const SSL *s, int i)
int ossl_ssl_get_error(const SSL *s, int i, int check_err)
{
int reason;
- unsigned long l;
BIO *bio;
const SSL_CONNECTION *sc = SSL_CONNECTION_FROM_CONST_SSL(s);
@@ -4876,15 +4964,11 @@ int ossl_ssl_get_error(const SSL *s, int i, int check_err)
if (sc == NULL)
return SSL_ERROR_SSL;
- /*
- * Make things return SSL_ERROR_SYSCALL when doing SSL_do_handshake etc,
- * where we do encode the error
- */
- if (check_err && (l = ERR_peek_error()) != 0) {
- if (ERR_GET_LIB(l) == ERR_LIB_SYS)
- return SSL_ERROR_SYSCALL;
- else
+ if (check_err != 0) {
+ if (sc->statem.error_state == ERROR_STATE_SSL)
return SSL_ERROR_SSL;
+ if (sc->statem.error_state == ERROR_STATE_SYSCALL)
+ return SSL_ERROR_SYSCALL;
}
#ifndef OPENSSL_NO_QUIC
@@ -4976,21 +5060,30 @@ int SSL_do_handshake(SSL *s)
int ret = 1;
SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s);
+ ssl_clear_error_state(s);
+
#ifndef OPENSSL_NO_QUIC
- if (IS_QUIC(s))
- return ossl_quic_do_handshake(s);
+ if (IS_QUIC(s)) {
+ ret = ossl_quic_do_handshake(s);
+ goto end;
+ }
#endif
- if (sc == NULL)
- return -1;
+ if (sc == NULL) {
+ ret = -1;
+ goto end;
+ }
if (sc->handshake_func == NULL) {
ERR_raise(ERR_LIB_SSL, SSL_R_CONNECTION_TYPE_NOT_SET);
- return -1;
+ ret = -1;
+ goto end;
}
- if (!ossl_statem_check_finish_init(sc, -1))
- return -1;
+ if (!ossl_statem_check_finish_init(sc, -1)) {
+ ret = -1;
+ goto end;
+ }
s->method->ssl_renegotiate_check(s, 0);
@@ -5007,6 +5100,8 @@ int SSL_do_handshake(SSL *s)
}
}
+end:
+ ssl_check_error_stack(s);
return ret;
}
diff --git a/ssl/statem/statem.c b/ssl/statem/statem.c
index d649e38474..8f24979ece 100644
--- a/ssl/statem/statem.c
+++ b/ssl/statem/statem.c
@@ -131,6 +131,7 @@ void ossl_statem_clear(SSL_CONNECTION *s)
{
s->statem.state = MSG_FLOW_UNINITED;
s->statem.hand_state = TLS_ST_BEFORE;
+ s->statem.error_state = ERROR_STATE_NOERROR;
ossl_statem_set_in_init(s, 1);
s->statem.no_cert_verify = 0;
}
@@ -288,6 +289,7 @@ void ossl_statem_set_hello_verify_done(SSL_CONNECTION *s)
* sensible.
*/
s->statem.hand_state = TLS_ST_SR_CLNT_HELLO;
+ s->statem.error_state = ERROR_STATE_NOERROR;
}
int ossl_statem_connect(SSL *s)
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c
index 2f6cef3343..63b1278575 100644
--- a/ssl/statem/statem_srvr.c
+++ b/ssl/statem/statem_srvr.c
@@ -1199,7 +1199,17 @@ WORK_STATE ossl_statem_server_post_work(SSL_CONNECTION *s, WORK_STATE wst)
case TLS_ST_SW_SESSION_TICKET:
clear_sys_error();
+ ERR_set_mark();
+ st->error_state = ERROR_STATE_NOERROR;
if (SSL_CONNECTION_IS_TLS13(s) && statem_flush(s) != 1) {
+ if (ERR_count_to_mark() > 0) {
+ unsigned long l = ERR_peek_error();
+ if (ERR_GET_LIB(l) == ERR_LIB_SYS)
+ st->error_state = ERROR_STATE_SYSCALL;
+ else
+ st->error_state = ERROR_STATE_SSL;
+ }
+ ERR_clear_last_mark();
if (SSL_get_error(ssl, 0) == SSL_ERROR_SYSCALL
&& conn_is_closed()) {
/*
@@ -1215,6 +1225,7 @@ WORK_STATE ossl_statem_server_post_work(SSL_CONNECTION *s, WORK_STATE wst)
return WORK_MORE_A;
}
+ ERR_clear_last_mark();
break;
}
diff --git a/test/helpers/handshake.c b/test/helpers/handshake.c
index 5e5606056f..98d46a4e54 100644
--- a/test/helpers/handshake.c
+++ b/test/helpers/handshake.c
@@ -833,6 +833,7 @@ typedef enum {
PEER_SUCCESS,
PEER_RETRY,
PEER_ERROR,
+ PEER_FINAL_ERROR,
PEER_WAITING,
PEER_TEST_FAILURE
} peer_status_t;
@@ -1028,6 +1029,10 @@ static void do_reneg_setup_step(const SSL_TEST_CTX *test_ctx, PEER *peer)
*/
if (SSL_is_server(peer->ssl)) {
ret = SSL_renegotiate(peer->ssl);
+ if (!ret) {
+ peer->status = PEER_FINAL_ERROR;
+ return;
+ }
} else {
int full_reneg = 0;
@@ -1047,10 +1052,10 @@ static void do_reneg_setup_step(const SSL_TEST_CTX *test_ctx, PEER *peer)
ret = SSL_renegotiate(peer->ssl);
else
ret = SSL_renegotiate_abbreviated(peer->ssl);
- }
- if (!ret) {
- peer->status = PEER_ERROR;
- return;
+ if (!ret) {
+ peer->status = PEER_FINAL_ERROR;
+ return;
+ }
}
do_handshake_step(peer);
/*
@@ -1311,6 +1316,7 @@ static handshake_status_t handshake_status(peer_status_t last_status,
/* Let the first peer finish. */
return HANDSHAKE_RETRY;
case PEER_ERROR:
+ case PEER_FINAL_ERROR:
/*
* Second peer succeeded despite the fact that the first peer
* already errored. This shouldn't happen.
@@ -1322,6 +1328,9 @@ static handshake_status_t handshake_status(peer_status_t last_status,
case PEER_RETRY:
return HANDSHAKE_RETRY;
+ case PEER_FINAL_ERROR:
+ return client_spoke_last ? CLIENT_ERROR : SERVER_ERROR;
+
case PEER_ERROR:
switch (previous_status) {
case PEER_TEST_FAILURE:
@@ -1336,6 +1345,7 @@ static handshake_status_t handshake_status(peer_status_t last_status,
/* We errored; let the peer finish. */
return HANDSHAKE_RETRY;
case PEER_ERROR:
+ case PEER_FINAL_ERROR:
/* Both peers errored. Return the one that errored first. */
return client_spoke_last ? SERVER_ERROR : CLIENT_ERROR;
}
diff --git a/test/quicapitest.c b/test/quicapitest.c
index d311ec65da..5afd571b8b 100644
--- a/test/quicapitest.c
+++ b/test/quicapitest.c
@@ -3489,7 +3489,7 @@ int setup_tests(void)
goto err;
cprivkey = test_mk_file_path(certsdir, "ee-key.pem");
- if (privkey == NULL)
+ if (cprivkey == NULL)
goto err;
ADD_ALL_TESTS(test_quic_write_read, 3);
diff --git a/test/recipes/70-test_renegotiation.t b/test/recipes/70-test_renegotiation.t
index d2ff9a8ab6..cc48f0be7c 100644
--- a/test/recipes/70-test_renegotiation.t
+++ b/test/recipes/70-test_renegotiation.t
@@ -39,12 +39,23 @@ my $proxy = TLSProxy::Proxy->new(
(!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
);
+sub success_or_closenotify
+{
+ return 1 if TLSProxy::Message->success();
+
+ my $alert = TLSProxy::Message->alert();
+ return 0 unless defined $alert;
+
+ return ($alert->level() == TLSProxy::Message::AL_LEVEL_WARN()
+ && $alert->description() == TLSProxy::Message::AL_DESC_CLOSE_NOTIFY());
+}
+
#Test 1: A basic renegotiation test
$proxy->clientflags("-no_tls1_3");
$proxy->serverflags("-client_renegotiation");
$proxy->reneg(1);
$proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
-ok(TLSProxy::Message->success(), "Basic renegotiation");
+ok(success_or_closenotify(), "Basic renegotiation");
#Test 2: Seclevel 0 client does not send the Reneg SCSV. Reneg should fail
$proxy->clear();
@@ -97,7 +108,7 @@ SKIP: {
}
}
}
- ok(TLSProxy::Message->success() && $chmatch,
+ ok(success_or_closenotify() && $chmatch,
"Check ClientHello version is the same");
}