diff --git a/bssl-compat/source/SSL_CTX_set_select_certificate_cb.cc b/bssl-compat/source/SSL_CTX_set_select_certificate_cb.cc index f3b7e1a87e..e577cd0224 100644 --- a/bssl-compat/source/SSL_CTX_set_select_certificate_cb.cc +++ b/bssl-compat/source/SSL_CTX_set_select_certificate_cb.cc @@ -15,9 +15,9 @@ typedef enum ssl_select_cert_result_t (*select_certificate_cb_t)(const SSL_CLIEN * in_select_certificate_cb(ssl) function to query whether or not we are * executing within a SSL_CTX_set_select_certificate_cb() callback for that SSL * object, or not. - * - * This mechanism is used by the SSL_get_servername() function to provide a - * different implementation depending on it's invocation context. + * + * This mechanism is used by SSL_get_servername() & SSL_set_ocsp_response() + * to provide different behavior depending on invocation context. */ class ActiveSelectCertificateCb { public: @@ -27,14 +27,14 @@ class ActiveSelectCertificateCb { ~ActiveSelectCertificateCb() { SSL_set_ex_data(ssl_, index(), nullptr); } + static bool isActive(const SSL *ssl) { + return SSL_get_ex_data(ssl, index()) != nullptr; + } + private: static int index() { - static int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, - +[](void *, void *ptr, CRYPTO_EX_DATA *, int, long, void*) { - if (ptr) ossl_OPENSSL_free(ptr); - }); + static int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); return index; } - private: SSL *ssl_; }; @@ -43,7 +43,7 @@ class ActiveSelectCertificateCb { * callback invocation for the specified SSL object. */ bool in_select_certificate_cb(const SSL *ssl) { - return SSL_get_ex_data(ssl, ActiveSelectCertificateCb::index()) != nullptr; + return ActiveSelectCertificateCb::isActive(ssl); } @@ -101,6 +101,12 @@ static int ssl_ctx_client_hello_cb(SSL *ssl, int *alert, void *arg) { return ossl_SSL_CLIENT_HELLO_ERROR; } + // Ensure extensions are freed even if the callback throws + std::unique_ptr cleanup( + const_cast(client_hello.extensions), + OPENSSL_free + ); + enum ssl_select_cert_result_t result; { @@ -108,8 +114,6 @@ static int ssl_ctx_client_hello_cb(SSL *ssl, int *alert, void *arg) { result = callback(&client_hello); } - OPENSSL_free((void*)client_hello.extensions); - switch (result) { case ssl_select_cert_success: return ossl_SSL_CLIENT_HELLO_SUCCESS; case ssl_select_cert_retry: return ossl_SSL_CLIENT_HELLO_RETRY; diff --git a/bssl-compat/source/test/test_ssl.cc b/bssl-compat/source/test/test_ssl.cc index 4c97a1bc01..845457b7c4 100644 --- a/bssl-compat/source/test/test_ssl.cc +++ b/bssl-compat/source/test/test_ssl.cc @@ -1671,6 +1671,49 @@ TEST(SSLTest, test_SSL_set_ocsp_response_leak_inside_select_certificate_cb) { } +/** + * @brief This test exercises a leak in SSL_CTX_set_select_certificate_cb() when + * the certificate selection callback throws an exception. + * + * Without a fix for the leak, running this test under valgrind or similar + * memory checker tool will report the memory leak. + */ +TEST(SSLTest, test_SSL_CTX_set_select_certificate_cb_leak_from_callback_exception) { + TempFile server_2_key_pem { server_2_key_pem_str }; + TempFile server_2_cert_chain_pem { server_2_cert_chain_pem_str }; + + int sockets[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sockets)); + SocketCloser close[] { sockets[0], sockets[1] }; + + bssl::UniquePtr server_ctx(SSL_CTX_new(TLS_server_method())); + bssl::UniquePtr client_ctx(SSL_CTX_new(TLS_client_method())); + + // Set up server with a select certificate callback that raises an exception + SSL_CTX_set_select_certificate_cb(server_ctx.get(), [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t { + throw std::runtime_error("Intentional exception to test for memory leaks"); + }); + + ASSERT_TRUE(SSL_CTX_use_certificate_chain_file(server_ctx.get(), server_2_cert_chain_pem.path())); + ASSERT_TRUE(SSL_CTX_use_PrivateKey_file(server_ctx.get(), server_2_key_pem.path(), SSL_FILETYPE_PEM)); + bssl::UniquePtr server_ssl(SSL_new(server_ctx.get())); + ASSERT_TRUE(SSL_set_fd(server_ssl.get(), sockets[0])); + SSL_set_accept_state(server_ssl.get()); + + // Set up client + SSL_CTX_set_verify(client_ctx.get(), SSL_VERIFY_NONE, nullptr); + bssl::UniquePtr client_ssl(SSL_new(client_ctx.get())); + ASSERT_TRUE(SSL_set_fd(client_ssl.get(), sockets[1])); + SSL_set_connect_state(client_ssl.get()); + + // Handshake will fail because of the exception in the callback + EXPECT_THROW( + CompleteHandshakes(client_ssl.get(), server_ssl.get()), + std::runtime_error + ); +} + + /** * Test that setting a TLS alert and returning ssl_verify_invalid, from a * callback installed via SSL_CTX_set_custom_verify(), results in a handshake