Skip to content

Commit f5c049b

Browse files
committed
Fixed potential leaks in SSL_set_ocsp_response()
Signed-off-by: Ted Poole <tpoole@redhat.com>
1 parent b01902f commit f5c049b

2 files changed

Lines changed: 86 additions & 13 deletions

File tree

bssl-compat/source/SSL_set_ocsp_response.cc

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@
2121
#include "SSL_CTX_set_select_certificate_cb.h"
2222

2323

24-
typedef std::pair<void*,size_t> OcspResponse;
24+
typedef std::pair<std::unique_ptr<void, decltype(&OPENSSL_free)>,size_t> OcspResponse;
2525

2626
static int index() {
2727
static int index {ossl.ossl_SSL_get_ex_new_index(0, nullptr, nullptr, nullptr,
2828
+[](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);
29+
if(OcspResponse *resp = reinterpret_cast<OcspResponse*>(ptr)) {
3230
delete resp;
3331
}
3432
})};
@@ -44,10 +42,11 @@ static int ssl_apply_deferred_ocsp_response_cb(SSL *ssl, void *arg) {
4442

4543
if (resp) {
4644
ossl.ossl_SSL_set_ex_data(ssl, index(), nullptr);
47-
if (ossl.ossl_SSL_set_tlsext_status_ocsp_resp(ssl, resp->first, resp->second) == 0) {
48-
return ossl_SSL_TLSEXT_ERR_ALERT_FATAL;
45+
if (ossl.ossl_SSL_set_tlsext_status_ocsp_resp(ssl, resp->first.get(), resp->second) == 1) {
46+
resp->first.release(); // ossl_SSL_set_tlsext_status_ocsp_resp() took ownership
47+
return ossl_SSL_TLSEXT_ERR_OK;
4948
}
50-
return ossl_SSL_TLSEXT_ERR_OK;
49+
return ossl_SSL_TLSEXT_ERR_ALERT_FATAL;
5150
}
5251

5352
return ossl_SSL_TLSEXT_ERR_NOACK;
@@ -60,7 +59,12 @@ static int ssl_apply_deferred_ocsp_response_cb(SSL *ssl, void *arg) {
6059
* ossl_SSL_CTX_set_tlsext_status_cb() later on.
6160
*/
6261
extern "C" int SSL_set_ocsp_response(SSL *ssl, const uint8_t *response, size_t response_len) {
63-
if (void *response_copy {ossl.ossl_OPENSSL_memdup(response, response_len)}) {
62+
std::unique_ptr<void, decltype(&OPENSSL_free)> response_copy(
63+
ossl.ossl_OPENSSL_memdup(response, response_len),
64+
OPENSSL_free
65+
);
66+
67+
if (response_copy) {
6468
if (in_select_certificate_cb(ssl)) {
6569

6670
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
8488
// squirreled away already. If so, delete it first, so we don't just
8589
// overwrite it and create a leak.
8690
if(OcspResponse *prev = reinterpret_cast<OcspResponse*>(ossl.ossl_SSL_get_ex_data(ssl, index()))) {
87-
ossl.ossl_OPENSSL_free(prev->first);
8891
delete prev;
8992
}
9093

91-
// Store the OCSP response bytes for the callback to pick up later
92-
return ossl.ossl_SSL_set_ex_data(ssl, index(), new OcspResponse(response_copy, response_len));
94+
// Store the OcspResponse bytes for the callback to pick up later
95+
std::unique_ptr<OcspResponse> resp = std::make_unique<OcspResponse>(std::move(response_copy), response_len);
96+
if (ossl.ossl_SSL_set_ex_data(ssl, index(), resp.get()) == 1) {
97+
resp.release(); // ossl_SSL_set_ex_data() took ownership
98+
return 1;
99+
}
93100
}
94-
else {
95-
return ossl.ossl_SSL_set_tlsext_status_ocsp_resp(ssl, response_copy, response_len);
101+
else { // We're not in a select certificate callback, so we set it directly
102+
if (ossl.ossl_SSL_set_tlsext_status_ocsp_resp(ssl, response_copy.get(), response_len) == 1) {
103+
response_copy.release(); // ossl_SSL_set_tlsext_status_ocsp_resp() took ownership
104+
return 1;
105+
}
96106
}
97107
}
98108

bssl-compat/source/test/test_ssl.cc

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,69 @@ TEST(SSLTest, test_SSL_set_ocsp_response_leak_inside_select_certificate_cb) {
16711671
}
16721672

16731673

1674+
#ifdef BSSL_COMPAT
1675+
/**
1676+
* @brief This test exercises a leak that occurs in SSL_set_ocsp_response() if
1677+
* it returns early due to an error, when it is called from within a certificate
1678+
* selection callback.
1679+
*
1680+
* Without a fix for the leak, running this test under valgrind or similar
1681+
* memory checker tool will report the memory leak.
1682+
*
1683+
* Note that because this test uses knowledge of the internals of the
1684+
* SSL_set_ocsp_response() implementation, in bssl-compat, in order to provoke
1685+
* the leak, it will not work the same on BoringSSL proper.
1686+
*/
1687+
TEST(SSLTest, test_SSL_set_ocsp_response_early_return_leak_inside_select_certificate_cb) {
1688+
TempFile server_2_key_pem { server_2_key_pem_str };
1689+
TempFile server_2_cert_chain_pem { server_2_cert_chain_pem_str };
1690+
1691+
static const uint8_t OCSP_RESPONSE[] { 1, 2, 3, 4, 5 };
1692+
1693+
int sockets[2];
1694+
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sockets));
1695+
SocketCloser close[] { sockets[0], sockets[1] };
1696+
1697+
bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_server_method()));
1698+
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_client_method()));
1699+
1700+
// Register a dummy tlsext status callback. This will provoke the
1701+
// SSL_set_ocsp_response() call, inside the certificate selection callback, to
1702+
// fail and return early. This in turn will cause the leak to occur if the fix
1703+
// is not in place.
1704+
SSL_CTX_set_tlsext_status_cb(server_ctx.get(), [](SSL *ssl, void *arg) -> int {return 0;});
1705+
1706+
// Set up server with a select certificate callback that calls
1707+
// SSL_set_ocsp_response() - which will return early and leak because of the
1708+
// dummy status callback we installed above.
1709+
SSL_CTX_set_select_certificate_cb(server_ctx.get(), [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
1710+
if (SSL_set_ocsp_response(client_hello->ssl, OCSP_RESPONSE, sizeof(OCSP_RESPONSE)) == 1) {
1711+
return ssl_select_cert_success;
1712+
}
1713+
return ssl_select_cert_error;
1714+
});
1715+
ASSERT_TRUE(SSL_CTX_use_certificate_chain_file(server_ctx.get(), server_2_cert_chain_pem.path()));
1716+
ASSERT_TRUE(SSL_CTX_use_PrivateKey_file(server_ctx.get(), server_2_key_pem.path(), SSL_FILETYPE_PEM));
1717+
bssl::UniquePtr<SSL> server_ssl(SSL_new(server_ctx.get()));
1718+
ASSERT_TRUE(SSL_set_fd(server_ssl.get(), sockets[0]));
1719+
SSL_set_accept_state(server_ssl.get());
1720+
1721+
// Set up client with ocsp stapling enabled
1722+
SSL_CTX_set_verify(client_ctx.get(), SSL_VERIFY_NONE, nullptr);
1723+
bssl::UniquePtr<SSL> client_ssl(SSL_new(client_ctx.get()));
1724+
ASSERT_TRUE(SSL_set_fd(client_ssl.get(), sockets[1]));
1725+
SSL_set_connect_state(client_ssl.get());
1726+
SSL_enable_ocsp_stapling(client_ssl.get());
1727+
1728+
// We expect this to fail because the SSL_set_ocsp_response() call inside the
1729+
// certificate selection callback above will return early with an error,
1730+
// causing the certificate selection callback to fail, which in turn will
1731+
// cause the handshake to fail.
1732+
ASSERT_FALSE(CompleteHandshakes(client_ssl.get(), server_ssl.get()));
1733+
}
1734+
#endif // BSSL_COMPAT
1735+
1736+
16741737
/**
16751738
* Test that setting a TLS alert and returning ssl_verify_invalid, from a
16761739
* callback installed via SSL_CTX_set_custom_verify(), results in a handshake

0 commit comments

Comments
 (0)