Skip to content

Commit 7641c1a

Browse files
committed
Fix leak in bssl-compat SSL_set_ocsp_response()
Signed-off-by: Ted Poole <tpoole@redhat.com>
1 parent ad523c1 commit 7641c1a

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

bssl-compat/source/SSL_set_ocsp_response.cc

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@
2424
typedef std::pair<void*,size_t> OcspResponse;
2525

2626
static int index() {
27-
static int index {ossl.ossl_SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr)};
27+
static int index {ossl.ossl_SSL_get_ex_new_index(0, nullptr, nullptr, nullptr,
28+
+[](void *, void *ptr, CRYPTO_EX_DATA *, int, long, void*) {
29+
if(ptr) {
30+
OcspResponse *resp {reinterpret_cast<OcspResponse*>(ptr)};
31+
ossl.ossl_OPENSSL_free(resp->first);
32+
delete resp;
33+
}
34+
})};
2835
return index;
2936
}
3037

@@ -72,6 +79,15 @@ extern "C" int SSL_set_ocsp_response(SSL *ssl, const uint8_t *response, size_t r
7279
return 0;
7380
}
7481

82+
// If we have been called previously, from within the same select
83+
// certificate callback invocation, there will be an OcspReponse object
84+
// squirreled away already. If so, delete it first, so we don't just
85+
// overwrite it and create a leak.
86+
if(OcspResponse *prev = reinterpret_cast<OcspResponse*>(ossl.ossl_SSL_get_ex_data(ssl, index()))) {
87+
ossl.ossl_OPENSSL_free(prev->first);
88+
delete prev;
89+
}
90+
7591
// Store the OCSP response bytes for the callback to pick up later
7692
return ossl.ossl_SSL_set_ex_data(ssl, index(), new OcspResponse(response_copy, response_len));
7793
}

bssl-compat/source/test/test_ssl.cc

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,66 @@ TEST(SSLTest, test_SSL_set_ocsp_response_inside_select_certificate_cb) {
16121612
}
16131613

16141614

1615+
/**
1616+
* @brief This test exercises a leak in SSL_set_ocsp_response()
1617+
*
1618+
* This test exercises a leak in SSL_set_ocsp_response() that occurs when it is
1619+
* invoked multiple times from within the same certificate selection callback
1620+
* i.e. without the fix, running this test under valgrind or similar memory
1621+
* checker tool will report the memory leak.
1622+
*
1623+
* Note that the leak does _not_ occur if SSL_set_ocsp_response() is only called
1624+
* _once_ from within the same certificate selection callback. It is only the
1625+
* additional calls that leak.
1626+
*/
1627+
TEST(SSLTest, test_SSL_set_ocsp_response_leak_inside_select_certificate_cb) {
1628+
TempFile server_2_key_pem { server_2_key_pem_str };
1629+
TempFile server_2_cert_chain_pem { server_2_cert_chain_pem_str };
1630+
1631+
static const uint8_t OCSP_RESPONSE[] { 1, 2, 3, 4, 5 };
1632+
1633+
int sockets[2];
1634+
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sockets));
1635+
SocketCloser close[] { sockets[0], sockets[1] };
1636+
1637+
bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_server_method()));
1638+
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_client_method()));
1639+
1640+
// Set up server with a select certificate callback that calls
1641+
// SSL_set_ocsp_response() 5 times. This will result in 4 leaks if the
1642+
// SSL_set_ocsp_response() fix is not in place.
1643+
SSL_CTX_set_select_certificate_cb(server_ctx.get(), [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
1644+
SSL_set_ocsp_response(client_hello->ssl, OCSP_RESPONSE, sizeof(OCSP_RESPONSE));
1645+
SSL_set_ocsp_response(client_hello->ssl, OCSP_RESPONSE, sizeof(OCSP_RESPONSE));
1646+
SSL_set_ocsp_response(client_hello->ssl, OCSP_RESPONSE, sizeof(OCSP_RESPONSE));
1647+
SSL_set_ocsp_response(client_hello->ssl, OCSP_RESPONSE, sizeof(OCSP_RESPONSE));
1648+
SSL_set_ocsp_response(client_hello->ssl, OCSP_RESPONSE, sizeof(OCSP_RESPONSE));
1649+
return ssl_select_cert_success;
1650+
});
1651+
ASSERT_TRUE(SSL_CTX_use_certificate_chain_file(server_ctx.get(), server_2_cert_chain_pem.path()));
1652+
ASSERT_TRUE(SSL_CTX_use_PrivateKey_file(server_ctx.get(), server_2_key_pem.path(), SSL_FILETYPE_PEM));
1653+
bssl::UniquePtr<SSL> server_ssl(SSL_new(server_ctx.get()));
1654+
ASSERT_TRUE(SSL_set_fd(server_ssl.get(), sockets[0]));
1655+
SSL_set_accept_state(server_ssl.get());
1656+
1657+
// Set up client with ocsp stapling enabled
1658+
SSL_CTX_set_verify(client_ctx.get(), SSL_VERIFY_NONE, nullptr);
1659+
bssl::UniquePtr<SSL> client_ssl(SSL_new(client_ctx.get()));
1660+
ASSERT_TRUE(SSL_set_fd(client_ssl.get(), sockets[1]));
1661+
SSL_set_connect_state(client_ssl.get());
1662+
SSL_enable_ocsp_stapling(client_ssl.get());
1663+
1664+
ASSERT_TRUE(CompleteHandshakes(client_ssl.get(), server_ssl.get()));
1665+
1666+
// Check that the client received the OCSP response ok
1667+
const uint8_t *ocsp_resp_data{};
1668+
size_t ocsp_resp_len{};
1669+
SSL_get0_ocsp_response(client_ssl.get(), &ocsp_resp_data, &ocsp_resp_len);
1670+
ASSERT_EQ(sizeof(OCSP_RESPONSE), ocsp_resp_len);
1671+
ASSERT_EQ(0, memcmp(OCSP_RESPONSE, ocsp_resp_data, ocsp_resp_len));
1672+
}
1673+
1674+
16151675
/**
16161676
* Test that setting a TLS alert and returning ssl_verify_invalid, from a
16171677
* callback installed via SSL_CTX_set_custom_verify(), results in a handshake

0 commit comments

Comments
 (0)