https://bugs.gentoo.org/985448 https://github.com/mongodb/mongo-c-driver/pull/2299 (rebased for 1.x) From 546c4f0bf4b8e57b9c562df007c220bd152bbbcf Mon Sep 17 00:00:00 2001 From: Ezra Chung <88335979+eramongodb@users.noreply.github.com> Date: Tue, 12 May 2026 12:39:19 -0500 Subject: [PATCH 2/2] CDRIVER-6319 Improve handling `EAGAIN` errors in OpenSSL BIO read callbacks (#2297) * Extend ssl_test API to support client stream wrappers * Add regression test for EAGAIN behavior * Re-enable "run auth tests" for OpenSSL compat matrix --- .../mongoc/mongoc-stream-tls-openssl-bio.c | 13 ++- src/libmongoc/tests/ssl-test.c | 77 +++++++------ src/libmongoc/tests/ssl-test.h | 9 ++ src/libmongoc/tests/test-mongoc-stream-tls.c | 104 +++++++++++++++++- 4 files changed, 165 insertions(+), 38 deletions(-) diff --git a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl-bio.c b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl-bio.c index ba77ec3..8cb0732 100644 --- a/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl-bio.c +++ b/src/libmongoc/src/mongoc/mongoc-stream-tls-openssl-bio.c @@ -224,15 +224,18 @@ mongoc_stream_tls_openssl_bio_read (BIO *b, char *buf, int len) openssl = (mongoc_stream_tls_openssl_t *) tls->ctx; errno = 0; - const ssize_t ret = mongoc_stream_read (tls->base_stream, buf, (size_t) len, 0, (int32_t) tls->timeout_msec); + ssize_t ret = mongoc_stream_read(tls->base_stream, buf, (size_t)len, 0, (int32_t)tls->timeout_msec); BIO_clear_retry_flags (b); if ((ret <= 0) && MONGOC_ERRNO_IS_AGAIN (errno)) { - /* this BIO is not the same as "b", which openssl passed in to this func. - * set its retry flag, which we check with BIO_should_retry in - * mongoc-stream-tls-openssl.c - */ + // Set the retry flag for the BIO used by functions in mongoc-stream-tls-openssl.c. BIO_set_retry_read (openssl->bio); + + // Also set the retry flag for the BIO used by OpenSSL. + BIO_set_retry_read(b); + + // EAGAIN is an error (negative value), not an EOF (zero). + ret = -1; } BSON_ASSERT (mcommon_in_range_signed (int, ret)); diff --git a/src/libmongoc/tests/ssl-test.c b/src/libmongoc/tests/ssl-test.c index 5c84548..5fb4d22 100644 --- a/src/libmongoc/tests/ssl-test.c +++ b/src/libmongoc/tests/ssl-test.c @@ -196,6 +196,9 @@ static BSON_THREAD_FUN (ssl_test_client, ptr) sock_stream = mongoc_stream_socket_new (conn_sock); BSON_ASSERT (sock_stream); + if (data->client_stream_wrapper) { + sock_stream = data->client_stream_wrapper(sock_stream); + } ssl_stream = mongoc_stream_tls_new_with_hostname (sock_stream, data->host, data->client, 1); if (!ssl_stream) { #ifdef MONGOC_ENABLE_SSL_OPENSSL @@ -263,14 +266,25 @@ static BSON_THREAD_FUN (ssl_test_client, ptr) BSON_THREAD_RETURN; } +static void +_ssl_test_run(ssl_test_data_t *data) +{ + bson_thread_t server; + bson_thread_t client; + + bson_mutex_init(&data->cond_mutex); + mongoc_cond_init(&data->cond); + + ASSERT_CMPINT(mcommon_thread_create(&server, &ssl_test_server, data), ==, 0); + ASSERT_CMPINT(mcommon_thread_create(&client, &ssl_test_client, data), ==, 0); + + mcommon_thread_join(server); + mcommon_thread_join(client); + + bson_mutex_destroy(&data->cond_mutex); + mongoc_cond_destroy(&data->cond); +} -/** This is the testing function for the ssl-test lib - * - * The basic idea is that you spin up a client and server, which will - * communicate over a mongoc-stream-tls, with varying mongoc_ssl_opt's. The - * client and server speak a simple echo protocol, so all we're really testing - * here is that any given configuration succeeds or fails as it should - */ void ssl_test (mongoc_ssl_opt_t *client, mongoc_ssl_opt_t *server, @@ -278,30 +292,29 @@ ssl_test (mongoc_ssl_opt_t *client, ssl_test_result_t *client_result, ssl_test_result_t *server_result) { - ssl_test_data_t data = {0}; - bson_thread_t threads[2]; - int i, r; - - data.server = server; - data.client = client; - data.client_result = client_result; - data.server_result = server_result; - data.host = host; - - bson_mutex_init (&data.cond_mutex); - mongoc_cond_init (&data.cond); - - r = mcommon_thread_create (threads, &ssl_test_server, &data); - BSON_ASSERT (r == 0); - - r = mcommon_thread_create (threads + 1, &ssl_test_client, &data); - BSON_ASSERT (r == 0); - - for (i = 0; i < 2; i++) { - r = mcommon_thread_join (threads[i]); - BSON_ASSERT (r == 0); - } + _ssl_test_run(&(ssl_test_data_t){ + .client = client, + .server = server, + .client_result = client_result, + .server_result = server_result, + .host = host, + }); +} - bson_mutex_destroy (&data.cond_mutex); - mongoc_cond_destroy (&data.cond); +void +ssl_test_with_client_stream_wrapper(mongoc_ssl_opt_t *client, + mongoc_ssl_opt_t *server, + const char *host, + ssl_test_result_t *client_result, + ssl_test_result_t *server_result, + mongoc_stream_t *(*wrapper)(mongoc_stream_t *stream)) +{ + _ssl_test_run(&(ssl_test_data_t){ + .client = client, + .server = server, + .client_result = client_result, + .server_result = server_result, + .host = host, + .client_stream_wrapper = wrapper, + }); } diff --git a/src/libmongoc/tests/ssl-test.h b/src/libmongoc/tests/ssl-test.h index 382a0a2..8151991 100644 --- a/src/libmongoc/tests/ssl-test.h +++ b/src/libmongoc/tests/ssl-test.h @@ -33,6 +33,7 @@ typedef struct ssl_test_data { bson_mutex_t cond_mutex; ssl_test_result_t *client_result; ssl_test_result_t *server_result; + mongoc_stream_t *(*client_stream_wrapper)(mongoc_stream_t *stream); /* optional */ } ssl_test_data_t; void @@ -41,3 +42,11 @@ ssl_test (mongoc_ssl_opt_t *client, const char *host, ssl_test_result_t *client_result, ssl_test_result_t *server_result); + +void +ssl_test_with_client_stream_wrapper(mongoc_ssl_opt_t *client, + mongoc_ssl_opt_t *server, + const char *host, + ssl_test_result_t *client_result, + ssl_test_result_t *server_result, + mongoc_stream_t *(*wrapper)(mongoc_stream_t *stream)); diff --git a/src/libmongoc/tests/test-mongoc-stream-tls.c b/src/libmongoc/tests/test-mongoc-stream-tls.c index 904af42..13ac7ac 100644 --- a/src/libmongoc/tests/test-mongoc-stream-tls.c +++ b/src/libmongoc/tests/test-mongoc-stream-tls.c @@ -1,5 +1,11 @@ #include +// is libbson-2 only + +#include +#include +#include + #ifdef MONGOC_ENABLE_SSL_OPENSSL #include @@ -387,6 +393,99 @@ test_mongoc_tls_trust_dir (void) } #endif +#if defined(MONGOC_ENABLE_SSL_OPENSSL) +typedef struct { + mongoc_stream_t base; + mongoc_stream_t *wrapped; + int eagain_remaining; +} _eagain_stream_t; + +static ssize_t +_eagain_readv(mongoc_stream_t *s, mongoc_iovec_t *iov, size_t iovcnt, size_t min_bytes, int32_t timeout_msec) +{ + _eagain_stream_t *const es = (_eagain_stream_t *)s; + if (es->eagain_remaining > 0) { + --es->eagain_remaining; + errno = EAGAIN; + return 0; + } + return mongoc_stream_readv(es->wrapped, iov, iovcnt, min_bytes, timeout_msec); +} + +static ssize_t +_eagain_writev(mongoc_stream_t *s, mongoc_iovec_t *iov, size_t iovcnt, int32_t timeout_msec) +{ + return mongoc_stream_writev(((_eagain_stream_t *)s)->wrapped, iov, iovcnt, timeout_msec); +} + +static int +_eagain_close(mongoc_stream_t *s) +{ + return mongoc_stream_close(((_eagain_stream_t *)s)->wrapped); +} + +static int +_eagain_flush(mongoc_stream_t *s) +{ + return mongoc_stream_flush(((_eagain_stream_t *)s)->wrapped); +} + +static void +_eagain_destroy(mongoc_stream_t *s) +{ + _eagain_stream_t *const es = (_eagain_stream_t *)s; + mongoc_stream_destroy(es->wrapped); + bson_free(es); +} + +static bool +_eagain_check_closed(mongoc_stream_t *s) +{ + return mongoc_stream_check_closed(((_eagain_stream_t *)s)->wrapped); +} + +static mongoc_stream_t * +_eagain_get_base_stream(mongoc_stream_t *s) +{ + return ((_eagain_stream_t *)s)->wrapped; +} + +static mongoc_stream_t * +_eagain_stream_new(mongoc_stream_t *base) +{ + _eagain_stream_t *const es = bson_malloc0(sizeof(*es)); + es->base.type = base->type; + es->base.destroy = _eagain_destroy; + es->base.close = _eagain_close; + es->base.flush = _eagain_flush; + es->base.writev = _eagain_writev; + es->base.readv = _eagain_readv; + es->base.get_base_stream = _eagain_get_base_stream; + es->base.check_closed = _eagain_check_closed; + es->wrapped = base; + es->eagain_remaining = 3; // Return EAGAIN three times, then succeed. + return (mongoc_stream_t *)es; +} + +static mongoc_stream_t * +_eagain_stream_wrapper(mongoc_stream_t *s) +{ + return _eagain_stream_new(s); +} + +static void +test_mongoc_tls_eagain(void) +{ + mongoc_ssl_opt_t sopt = {.ca_file = CERT_CA, .pem_file = CERT_SERVER}; + mongoc_ssl_opt_t copt = {.ca_file = CERT_CA, .pem_file = CERT_CLIENT}; + ssl_test_result_t sr; + ssl_test_result_t cr; + ssl_test_with_client_stream_wrapper(&copt, &sopt, "localhost", &cr, &sr, _eagain_stream_wrapper); + ASSERT_CMPINT((int)cr.result, ==, SSL_TEST_SUCCESS); + ASSERT_CMPINT((int)sr.result, ==, SSL_TEST_SUCCESS); +} +#endif // defined(MONGOC_ENABLE_SSL_OPENSSL) + #endif /* !MONGOC_ENABLE_SSL_SECURE_CHANNEL && !MONGOC_ENABLE_SSL_LIBRESSL */ void @@ -435,6 +534,7 @@ test_stream_tls_install (TestSuite *suite) TestSuite_Add (suite, "/TLS/bad_password", test_mongoc_tls_bad_password); TestSuite_Add (suite, "/TLS/weak_cert_validation", test_mongoc_tls_weak_cert_validation); TestSuite_Add (suite, "/TLS/crl", test_mongoc_tls_crl); + TestSuite_Add (suite, "/TLS/eagain", test_mongoc_tls_eagain); #endif #if !defined(__APPLE__) && !defined(_WIN32) && defined(MONGOC_ENABLE_SSL_OPENSSL) && \ @@ -443,5 +543,7 @@ test_stream_tls_install (TestSuite *suite) #endif TestSuite_AddLive (suite, "/TLS/insecure_nowarning", test_mongoc_tls_insecure_nowarning); -#endif + +#endif // !defined(MONGOC_ENABLE_SSL_SECURE_CHANNEL) && !defined(MONGOC_ENABLE_SSL_LIBRESSL) } + -- 2.56.0