diff --git a/bssl-compat/source/SSL_set_ocsp_response.cc b/bssl-compat/source/SSL_set_ocsp_response.cc index 299716e8a2..8f3df92116 100644 --- a/bssl-compat/source/SSL_set_ocsp_response.cc +++ b/bssl-compat/source/SSL_set_ocsp_response.cc @@ -21,14 +21,12 @@ #include "SSL_CTX_set_select_certificate_cb.h" -typedef std::pair OcspResponse; +typedef std::pair,size_t> OcspResponse; static int index() { static int index {ossl.ossl_SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, +[](void *, void *ptr, CRYPTO_EX_DATA *, int, long, void*) { - if(ptr) { - OcspResponse *resp {reinterpret_cast(ptr)}; - ossl.ossl_OPENSSL_free(resp->first); + if(OcspResponse *resp = reinterpret_cast(ptr)) { delete resp; } })}; @@ -44,10 +42,11 @@ static int ssl_apply_deferred_ocsp_response_cb(SSL *ssl, void *arg) { if (resp) { ossl.ossl_SSL_set_ex_data(ssl, index(), nullptr); - if (ossl.ossl_SSL_set_tlsext_status_ocsp_resp(ssl, resp->first, resp->second) == 0) { - return ossl_SSL_TLSEXT_ERR_ALERT_FATAL; + if (ossl.ossl_SSL_set_tlsext_status_ocsp_resp(ssl, resp->first.get(), resp->second) == 1) { + resp->first.release(); // ossl_SSL_set_tlsext_status_ocsp_resp() took ownership + return ossl_SSL_TLSEXT_ERR_OK; } - return ossl_SSL_TLSEXT_ERR_OK; + return ossl_SSL_TLSEXT_ERR_ALERT_FATAL; } return ossl_SSL_TLSEXT_ERR_NOACK; @@ -60,7 +59,12 @@ static int ssl_apply_deferred_ocsp_response_cb(SSL *ssl, void *arg) { * ossl_SSL_CTX_set_tlsext_status_cb() later on. */ extern "C" int SSL_set_ocsp_response(SSL *ssl, const uint8_t *response, size_t response_len) { - if (void *response_copy {ossl.ossl_OPENSSL_memdup(response, response_len)}) { + std::unique_ptr response_copy( + ossl.ossl_OPENSSL_memdup(response, response_len), + OPENSSL_free + ); + + if (response_copy) { if (in_select_certificate_cb(ssl)) { SSL_CTX *ctx {ossl.ossl_SSL_get_SSL_CTX(ssl)}; @@ -84,15 +88,21 @@ extern "C" int SSL_set_ocsp_response(SSL *ssl, const uint8_t *response, size_t r // squirreled away already. If so, delete it first, so we don't just // overwrite it and create a leak. if(OcspResponse *prev = reinterpret_cast(ossl.ossl_SSL_get_ex_data(ssl, index()))) { - ossl.ossl_OPENSSL_free(prev->first); delete prev; } - // Store the OCSP response bytes for the callback to pick up later - return ossl.ossl_SSL_set_ex_data(ssl, index(), new OcspResponse(response_copy, response_len)); + // Store the OcspResponse bytes for the callback to pick up later + std::unique_ptr resp = std::make_unique(std::move(response_copy), response_len); + if (ossl.ossl_SSL_set_ex_data(ssl, index(), resp.get()) == 1) { + resp.release(); // ossl_SSL_set_ex_data() took ownership + return 1; + } } - else { - return ossl.ossl_SSL_set_tlsext_status_ocsp_resp(ssl, response_copy, response_len); + else { // We're not in a select certificate callback, so we set it directly + if (ossl.ossl_SSL_set_tlsext_status_ocsp_resp(ssl, response_copy.get(), response_len) == 1) { + response_copy.release(); // ossl_SSL_set_tlsext_status_ocsp_resp() took ownership + return 1; + } } } diff --git a/bssl-compat/source/test/test_ssl.cc b/bssl-compat/source/test/test_ssl.cc index 4c97a1bc01..b736c0e5fe 100644 --- a/bssl-compat/source/test/test_ssl.cc +++ b/bssl-compat/source/test/test_ssl.cc @@ -1671,6 +1671,69 @@ TEST(SSLTest, test_SSL_set_ocsp_response_leak_inside_select_certificate_cb) { } +#ifdef BSSL_COMPAT +/** + * @brief This test exercises a leak that occurs in SSL_set_ocsp_response() if + * it returns early due to an error, when it is called from within a certificate + * selection callback. + * + * Without a fix for the leak, running this test under valgrind or similar + * memory checker tool will report the memory leak. + * + * Note that because this test uses knowledge of the internals of the + * SSL_set_ocsp_response() implementation, in bssl-compat, in order to provoke + * the leak, it will not work the same on BoringSSL proper. + */ +TEST(SSLTest, test_SSL_set_ocsp_response_early_return_leak_inside_select_certificate_cb) { + TempFile server_2_key_pem { server_2_key_pem_str }; + TempFile server_2_cert_chain_pem { server_2_cert_chain_pem_str }; + + static const uint8_t OCSP_RESPONSE[] { 1, 2, 3, 4, 5 }; + + 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())); + + // Register a dummy tlsext status callback. This will provoke the + // SSL_set_ocsp_response() call, inside the certificate selection callback, to + // fail and return early. This in turn will cause the leak to occur if the fix + // is not in place. + SSL_CTX_set_tlsext_status_cb(server_ctx.get(), [](SSL *ssl, void *arg) -> int {return 0;}); + + // Set up server with a select certificate callback that calls + // SSL_set_ocsp_response() - which will return early and leak because of the + // dummy status callback we installed above. + SSL_CTX_set_select_certificate_cb(server_ctx.get(), [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t { + if (SSL_set_ocsp_response(client_hello->ssl, OCSP_RESPONSE, sizeof(OCSP_RESPONSE)) == 1) { + return ssl_select_cert_success; + } + return ssl_select_cert_error; + }); + 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 with ocsp stapling enabled + 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()); + SSL_enable_ocsp_stapling(client_ssl.get()); + + // We expect this to fail because the SSL_set_ocsp_response() call inside the + // certificate selection callback above will return early with an error, + // causing the certificate selection callback to fail, which in turn will + // cause the handshake to fail. + ASSERT_FALSE(CompleteHandshakes(client_ssl.get(), server_ssl.get())); +} +#endif // BSSL_COMPAT + + /** * 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