-
Notifications
You must be signed in to change notification settings - Fork 21
ossl: improve error queue hygiene #267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c8887e2
808f6e2
9b013e0
8277c2a
e5f91e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -183,6 +183,10 @@ std::system_error make_ossl_error(const std::string & msg) { | |||||||||||||||||
| return make_ossl_error(msg, get_all_ossl_errors()); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| std::runtime_error make_unknown_ossl_error(const std::string & msg) { | ||||||||||||||||||
| return std::runtime_error(fmt::format("{}: {}", msg, get_all_ossl_errors())); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| bool contains_ossl_error(const std::vector<ossl_errc> & error_codes, int lib, int reason) { | ||||||||||||||||||
| return std::any_of(error_codes.cbegin(), error_codes.cend(), [lib, reason](const ossl_errc & code) { | ||||||||||||||||||
| return ERR_GET_LIB(static_cast<unsigned long>(code)) == lib && | ||||||||||||||||||
|
|
@@ -1023,7 +1027,7 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||||||||||||||||
| bio_ptr in_bio(BIO_new(get_method())); | ||||||||||||||||||
| bio_ptr out_bio(BIO_new(get_method())); | ||||||||||||||||||
| if (!in_bio || !out_bio) { | ||||||||||||||||||
| throw std::runtime_error("Failed to create BIOs"); | ||||||||||||||||||
| throw make_ossl_error("Failed to create BIOs"); | ||||||||||||||||||
| } | ||||||||||||||||||
| if (1 != BIO_ctrl(in_bio.get(), BIO_C_SET_POINTER, 0, this)) { | ||||||||||||||||||
| throw make_ossl_error("Failed to set bio ptr to in bio"); | ||||||||||||||||||
|
|
@@ -1151,7 +1155,7 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||||||||||||||||
| default: | ||||||||||||||||||
| { | ||||||||||||||||||
| // Some other unhandled situation | ||||||||||||||||||
| auto err = std::runtime_error( | ||||||||||||||||||
| auto err = make_unknown_ossl_error( | ||||||||||||||||||
| "Unknown error encountered during SSL write"); | ||||||||||||||||||
| return handle_output_error(std::move(err)).then([] { | ||||||||||||||||||
| return stop_iteration::yes; | ||||||||||||||||||
|
|
@@ -1191,6 +1195,7 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||||||||||||||||
| // This do_until runs until either a renegotiation occurs or the packet is empty | ||||||||||||||||||
| while (!eof() && size > 0) { | ||||||||||||||||||
| size_t bytes_written = 0; | ||||||||||||||||||
| verify_clean_error_queue("SSL_write_ex"); | ||||||||||||||||||
| auto write_rc = SSL_write_ex(_ssl.get(), ptr, size, &bytes_written); | ||||||||||||||||||
| tls_log.trace("{} do_put: SSL_write_ex: {}", *this, write_rc); | ||||||||||||||||||
| if (write_rc != 1) { | ||||||||||||||||||
|
|
@@ -1201,6 +1206,7 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||||||||||||||||
| co_return; | ||||||||||||||||||
| } | ||||||||||||||||||
| } else { | ||||||||||||||||||
| clear_stale_ssl_errors("SSL_write_ex"); | ||||||||||||||||||
| SEASTAR_ASSERT(bytes_written <= size); | ||||||||||||||||||
| tls_log.trace("{} do_put: bytes_written: {}", *this, bytes_written); | ||||||||||||||||||
| ptr += bytes_written; | ||||||||||||||||||
|
|
@@ -1282,6 +1288,7 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||||||||||||||||
| [this] { return connected() || eof(); }, | ||||||||||||||||||
| [this] { | ||||||||||||||||||
| try { | ||||||||||||||||||
| verify_clean_error_queue("SSL_do_handshake"); | ||||||||||||||||||
| auto n = SSL_do_handshake(_ssl.get()); | ||||||||||||||||||
| tls_log.trace("{} do_handshake: SSL_do_handshake: {}", *this, n); | ||||||||||||||||||
| if (n <= 0) { | ||||||||||||||||||
|
|
@@ -1326,11 +1333,12 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||||||||||||||||
| return handle_output_error(std::move(err)); | ||||||||||||||||||
| } | ||||||||||||||||||
| default: | ||||||||||||||||||
| auto err = std::runtime_error( | ||||||||||||||||||
| auto err = make_unknown_ossl_error( | ||||||||||||||||||
| "Unknown error encountered during handshake"); | ||||||||||||||||||
| return handle_output_error(std::move(err)); | ||||||||||||||||||
| } | ||||||||||||||||||
| } else { | ||||||||||||||||||
| clear_stale_ssl_errors("SSL_do_handshake"); | ||||||||||||||||||
| if (_type == session_type::CLIENT | ||||||||||||||||||
| || _creds->get_client_auth() != client_auth::NONE) { | ||||||||||||||||||
| verify(); | ||||||||||||||||||
|
|
@@ -1393,6 +1401,7 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||||||||||||||||
| tls_log.trace("{} do_get: available: {}", *this, avail); | ||||||||||||||||||
| buf_type buf(avail); | ||||||||||||||||||
| size_t bytes_read = 0; | ||||||||||||||||||
| verify_clean_error_queue("SSL_read_ex"); | ||||||||||||||||||
| auto read_result = SSL_read_ex( | ||||||||||||||||||
| _ssl.get(), buf.get_write(), avail, &bytes_read); | ||||||||||||||||||
| tls_log.trace("{} do_get: SSL_read_ex: {}", *this, read_result); | ||||||||||||||||||
|
|
@@ -1451,11 +1460,12 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||||||||||||||||
| return make_exception_future<buf_type>(_error); | ||||||||||||||||||
| } | ||||||||||||||||||
| default: | ||||||||||||||||||
| _error = std::make_exception_ptr(std::runtime_error( | ||||||||||||||||||
| _error = std::make_exception_ptr(make_unknown_ossl_error( | ||||||||||||||||||
| "Unexpected error condition during SSL read")); | ||||||||||||||||||
| return make_exception_future<buf_type>(_error); | ||||||||||||||||||
| } | ||||||||||||||||||
| } else { | ||||||||||||||||||
| clear_stale_ssl_errors("SSL_read_ex"); | ||||||||||||||||||
| buf.trim(bytes_read); | ||||||||||||||||||
| return make_ready_future<buf_type>(std::move(buf)); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -1488,11 +1498,14 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||||||||||||||||
| return make_ready_future(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| verify_clean_error_queue("SSL_shutdown"); | ||||||||||||||||||
| auto res = SSL_shutdown(_ssl.get()); | ||||||||||||||||||
| tls_log.trace("{} do_shutdown: SSL_shutdown: {}", *this, res); | ||||||||||||||||||
| if (res == 1) { | ||||||||||||||||||
| clear_stale_ssl_errors("SSL_shutdown"); | ||||||||||||||||||
| return wait_for_output(); | ||||||||||||||||||
| } else if (res == 0) { | ||||||||||||||||||
| clear_stale_ssl_errors("SSL_shutdown"); | ||||||||||||||||||
| return yield().then([this] { return do_shutdown(); }); | ||||||||||||||||||
| } else { | ||||||||||||||||||
| auto ssl_err = SSL_get_error(_ssl.get(), res); | ||||||||||||||||||
|
|
@@ -1536,7 +1549,7 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||||||||||||||||
| } | ||||||||||||||||||
| default: | ||||||||||||||||||
| { | ||||||||||||||||||
| auto err = std::runtime_error( | ||||||||||||||||||
| auto err = make_unknown_ossl_error( | ||||||||||||||||||
| "Unknown error occurred during SSL shutdown"); | ||||||||||||||||||
| return handle_output_error(std::move(err)); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -1821,6 +1834,33 @@ SEASTAR_INTERNAL_END_IGNORE_DEPRECATIONS | |||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| private: | ||||||||||||||||||
| // Some SSL operations return success while leaving stale errors on the | ||||||||||||||||||
| // queue (e.g. from internal BIO write failures that OpenSSL absorbed). | ||||||||||||||||||
| // Drain them so they don't poison the next operation on this shard. | ||||||||||||||||||
| void clear_stale_ssl_errors(const char* operation) { | ||||||||||||||||||
| if (ERR_peek_error() == 0) [[likely]] { | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| auto errors = get_all_ossl_errors(); | ||||||||||||||||||
| tls_log.debug("{} {}: ignoring stale errors on queue: {}", *this, operation, errors); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Checks that the OpenSSL per-thread error queue is clean before | ||||||||||||||||||
| // calling an SSL function. A dirty queue can cause SSL_get_error | ||||||||||||||||||
| // to misclassify results (e.g. reporting SSL_ERROR_SYSCALL instead | ||||||||||||||||||
| // of SSL_ERROR_SSL), which can affect unrelated sessions that share | ||||||||||||||||||
| // the same thread. | ||||||||||||||||||
| void verify_clean_error_queue(const char* operation) { | ||||||||||||||||||
| auto err = ERR_peek_error(); | ||||||||||||||||||
| if (err == 0) [[likely]] { | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| char buf[256]; | ||||||||||||||||||
| ERR_error_string_n(err, buf, sizeof(buf)); | ||||||||||||||||||
| tls_log.warn("{} stale error on queue before {}: {}", *this, operation, buf); | ||||||||||||||||||
| SEASTAR_ASSERT(0 && "stale errors on OpenSSL error queue"); | ||||||||||||||||||
|
Comment on lines
+1860
to
+1861
|
||||||||||||||||||
| tls_log.warn("{} stale error on queue before {}: {}", *this, operation, buf); | |
| SEASTAR_ASSERT(0 && "stale errors on OpenSSL error queue"); | |
| size_t cleared = 0; | |
| while (ERR_get_error() != 0) { | |
| ++cleared; | |
| } | |
| tls_log.warn("{} cleared {} stale error(s) on queue before {}: {}", *this, cleared, operation, buf); | |
| assert(false && "stale errors on OpenSSL error queue"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new pre-call error-queue invariant (verify_clean_error_queue before SSL_write_ex/SSL_read_ex/SSL_do_handshake/SSL_shutdown) is central to the fix, but there’s no regression test exercising the stale-error-queue scenario described in the PR. Since TLS already has unit coverage (tests/unit/tls_test.cc), consider adding a test that deliberately leaves an error on the OpenSSL per-thread queue and verifies a subsequent, unrelated TLS operation does not misclassify via SSL_get_error (and/or that the queue is drained/handled as intended).