Skip to content

Commit 3cb33a0

Browse files
authored
Merge pull request #453 from tedjpoole/fix_ssl_set_ocsp_response_exception_safety_1_34
[bp/1.34] Fixed potential bssl-compat leaks
2 parents b01902f + 12df805 commit 3cb33a0

File tree

3 files changed

+144
-24
lines changed

3 files changed

+144
-24
lines changed

bssl-compat/source/SSL_CTX_set_select_certificate_cb.cc

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ typedef enum ssl_select_cert_result_t (*select_certificate_cb_t)(const SSL_CLIEN
1515
* in_select_certificate_cb(ssl) function to query whether or not we are
1616
* executing within a SSL_CTX_set_select_certificate_cb() callback for that SSL
1717
* object, or not.
18-
*
19-
* This mechanism is used by the SSL_get_servername() function to provide a
20-
* different implementation depending on it's invocation context.
18+
*
19+
* This mechanism is used by SSL_get_servername() & SSL_set_ocsp_response()
20+
* to provide different behavior depending on invocation context.
2121
*/
2222
class ActiveSelectCertificateCb {
2323
public:
@@ -27,14 +27,14 @@ class ActiveSelectCertificateCb {
2727
~ActiveSelectCertificateCb() {
2828
SSL_set_ex_data(ssl_, index(), nullptr);
2929
}
30+
static bool isActive(const SSL *ssl) {
31+
return SSL_get_ex_data(ssl, index()) != nullptr;
32+
}
33+
private:
3034
static int index() {
31-
static int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr,
32-
+[](void *, void *ptr, CRYPTO_EX_DATA *, int, long, void*) {
33-
if (ptr) ossl_OPENSSL_free(ptr);
34-
});
35+
static int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
3536
return index;
3637
}
37-
private:
3838
SSL *ssl_;
3939
};
4040

@@ -43,7 +43,7 @@ class ActiveSelectCertificateCb {
4343
* callback invocation for the specified SSL object.
4444
*/
4545
bool in_select_certificate_cb(const SSL *ssl) {
46-
return SSL_get_ex_data(ssl, ActiveSelectCertificateCb::index()) != nullptr;
46+
return ActiveSelectCertificateCb::isActive(ssl);
4747
}
4848

4949

@@ -101,15 +101,19 @@ static int ssl_ctx_client_hello_cb(SSL *ssl, int *alert, void *arg) {
101101
return ossl_SSL_CLIENT_HELLO_ERROR;
102102
}
103103

104+
// Ensure extensions are freed even if the callback throws
105+
std::unique_ptr<uint8_t, decltype(&OPENSSL_free)> cleanup(
106+
const_cast<uint8_t*>(client_hello.extensions),
107+
OPENSSL_free
108+
);
109+
104110
enum ssl_select_cert_result_t result;
105111

106112
{
107113
ActiveSelectCertificateCb active(ssl);
108114
result = callback(&client_hello);
109115
}
110116

111-
OPENSSL_free((void*)client_hello.extensions);
112-
113117
switch (result) {
114118
case ssl_select_cert_success: return ossl_SSL_CLIENT_HELLO_SUCCESS;
115119
case ssl_select_cert_retry: return ossl_SSL_CLIENT_HELLO_RETRY;

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: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,112 @@ 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+
1737+
/**
1738+
* @brief This test exercises a leak in SSL_CTX_set_select_certificate_cb() when
1739+
* the certificate selection callback throws an exception.
1740+
*
1741+
* Without a fix for the leak, running this test under valgrind or similar
1742+
* memory checker tool will report the memory leak.
1743+
*/
1744+
TEST(SSLTest, test_SSL_CTX_set_select_certificate_cb_leak_from_callback_exception) {
1745+
TempFile server_2_key_pem { server_2_key_pem_str };
1746+
TempFile server_2_cert_chain_pem { server_2_cert_chain_pem_str };
1747+
1748+
int sockets[2];
1749+
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sockets));
1750+
SocketCloser close[] { sockets[0], sockets[1] };
1751+
1752+
bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_server_method()));
1753+
bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_client_method()));
1754+
1755+
// Set up server with a select certificate callback that raises an exception
1756+
SSL_CTX_set_select_certificate_cb(server_ctx.get(), [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
1757+
throw std::runtime_error("Intentional exception to test for memory leaks");
1758+
});
1759+
1760+
ASSERT_TRUE(SSL_CTX_use_certificate_chain_file(server_ctx.get(), server_2_cert_chain_pem.path()));
1761+
ASSERT_TRUE(SSL_CTX_use_PrivateKey_file(server_ctx.get(), server_2_key_pem.path(), SSL_FILETYPE_PEM));
1762+
bssl::UniquePtr<SSL> server_ssl(SSL_new(server_ctx.get()));
1763+
ASSERT_TRUE(SSL_set_fd(server_ssl.get(), sockets[0]));
1764+
SSL_set_accept_state(server_ssl.get());
1765+
1766+
// Set up client
1767+
SSL_CTX_set_verify(client_ctx.get(), SSL_VERIFY_NONE, nullptr);
1768+
bssl::UniquePtr<SSL> client_ssl(SSL_new(client_ctx.get()));
1769+
ASSERT_TRUE(SSL_set_fd(client_ssl.get(), sockets[1]));
1770+
SSL_set_connect_state(client_ssl.get());
1771+
1772+
// Handshake will fail because of the exception in the callback
1773+
EXPECT_THROW(
1774+
CompleteHandshakes(client_ssl.get(), server_ssl.get()),
1775+
std::runtime_error
1776+
);
1777+
}
1778+
1779+
16741780
/**
16751781
* Test that setting a TLS alert and returning ssl_verify_invalid, from a
16761782
* callback installed via SSL_CTX_set_custom_verify(), results in a handshake

0 commit comments

Comments
 (0)