Skip to content

Commit 38fdcf8

Browse files
committed
ocsp: replace stapling_mutex with bravo shared_mutex
Replace the ink_mutex in OCSP stapling with ts::bravo::shared_mutex to allow true reader concurrency on the TLS handshake hot path. BRAVO provides a lock-free fast path for readers via per-thread atomic slots, avoiding the shared counter bounce of std::shared_mutex. Skip the SSL_get_certificate() call in the OCSP stapling callback when only one certificate exists in the map, avoiding a DER re-parse on every handshake. Give certinfo a proper destructor to consolidate resource cleanup and fix two pre-existing leaks (cid and BoringSSL cert ref). Fix a pre-existing bug where the error path in ssl_stapling_init_cert could delete a certinfo_map still owned by the SSL_CTX.
1 parent 8e6b509 commit 38fdcf8

1 file changed

Lines changed: 110 additions & 95 deletions

File tree

src/iocore/net/OCSPStapling.cc

Lines changed: 110 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
#include "P_OCSPStapling.h"
2323

24+
#include "tsutil/Bravo.h"
25+
2426
#include <openssl/ssl.h>
2527
#include <openssl/x509v3.h>
2628
#include <openssl/asn1.h>
@@ -279,17 +281,38 @@ namespace
279281

280282
// Cached info stored in SSL_CTX ex_info
281283
struct certinfo {
282-
unsigned char idx[20]; // Index in session cache SHA1 hash of certificate
283-
TS_OCSP_CERTID *cid; // Certificate ID for OCSP requests or nullptr if ID cannot be determined
284-
char *uri; // Responder details
285-
char *certname;
286-
char *user_agent;
287-
ink_mutex stapling_mutex;
288-
unsigned char resp_der[MAX_STAPLING_DER];
289-
unsigned int resp_derlen;
290-
bool is_prefetched;
291-
bool is_expire;
292-
time_t expire_time;
284+
unsigned char idx[20] = {}; // Index in session cache SHA1 hash of certificate
285+
TS_OCSP_CERTID *cid = nullptr; // Certificate ID for OCSP requests
286+
char *uri = nullptr; // Responder details
287+
char *certname = nullptr;
288+
char *user_agent = nullptr;
289+
bool is_prefetched = false;
290+
291+
// OCSP response data, protected by resp_mutex.
292+
// Readers take a shared lock; the updater takes an exclusive lock.
293+
unsigned char resp_der[MAX_STAPLING_DER] = {};
294+
unsigned int resp_derlen = 0;
295+
bool is_expire = true;
296+
time_t expire_time = 0;
297+
mutable ts::bravo::shared_mutex resp_mutex;
298+
299+
~certinfo()
300+
{
301+
if (cid) {
302+
TS_OCSP_CERTID_free(cid);
303+
}
304+
if (uri) {
305+
OPENSSL_free(uri);
306+
}
307+
ats_free(certname);
308+
ats_free(user_agent);
309+
}
310+
311+
certinfo() = default;
312+
certinfo(const certinfo &) = delete;
313+
certinfo &operator=(const certinfo &) = delete;
314+
certinfo(certinfo &&) = delete;
315+
certinfo &operator=(certinfo &&) = delete;
293316
};
294317

295318
class HTTPRequest : public Continuation
@@ -736,16 +759,10 @@ certinfo_map_free(void * /*parent*/, void *ptr, CRYPTO_EX_DATA * /*ad*/, int /*i
736759
}
737760

738761
for (auto &iter : *map) {
739-
certinfo *cinf = iter.second;
740-
if (cinf->uri) {
741-
OPENSSL_free(cinf->uri);
742-
}
743-
744-
ats_free(cinf->certname);
745-
ats_free(cinf->user_agent);
746-
747-
ink_mutex_destroy(&cinf->stapling_mutex);
748-
OPENSSL_free(cinf);
762+
#ifdef OPENSSL_IS_BORINGSSL
763+
X509_free(iter.first);
764+
#endif
765+
delete iter.second;
749766
}
750767
delete map;
751768
}
@@ -831,12 +848,13 @@ stapling_cache_response(TS_OCSP_RESPONSE *rsp, certinfo *cinf)
831848
return false;
832849
}
833850

834-
ink_mutex_acquire(&cinf->stapling_mutex);
835-
memcpy(cinf->resp_der, resp_der, resp_derlen);
836-
cinf->resp_derlen = resp_derlen;
837-
cinf->is_expire = false;
838-
cinf->expire_time = time(nullptr) + SSLConfigParams::ssl_ocsp_cache_timeout;
839-
ink_mutex_release(&cinf->stapling_mutex);
851+
{
852+
std::lock_guard lock(cinf->resp_mutex);
853+
memcpy(cinf->resp_der, resp_der, resp_derlen);
854+
cinf->resp_derlen = resp_derlen;
855+
cinf->is_expire = false;
856+
cinf->expire_time = time(nullptr) + SSLConfigParams::ssl_ocsp_cache_timeout;
857+
}
840858

841859
Dbg(dbg_ctl_ssl_ocsp, "stapling_cache_response: success to cache response");
842860
return true;
@@ -860,28 +878,20 @@ ssl_stapling_init_cert(SSL_CTX *ctx, X509 *cert, const char *certname, const cha
860878
return false;
861879
}
862880

881+
bool map_is_new = false;
863882
if (!map) {
864-
map = new certinfo_map;
865-
}
866-
certinfo *cinf = static_cast<certinfo *>(OPENSSL_malloc(sizeof(certinfo)));
867-
if (!cinf) {
868-
Error("error allocating memory for %s", certname);
869-
delete map;
870-
return false;
883+
map = new certinfo_map;
884+
map_is_new = true;
871885
}
886+
auto cinf_ptr = std::make_unique<certinfo>();
887+
certinfo *cinf = cinf_ptr.get();
872888

873889
// Initialize certinfo
874-
cinf->cid = nullptr;
875-
cinf->uri = nullptr;
876890
cinf->certname = ats_strdup(certname);
877891
if (SSLConfigParams::ssl_ocsp_user_agent != nullptr) {
878892
cinf->user_agent = ats_strdup(SSLConfigParams::ssl_ocsp_user_agent);
879893
}
880-
cinf->resp_derlen = 0;
881-
ink_mutex_init(&cinf->stapling_mutex);
882894
cinf->is_prefetched = rsp_file ? true : false;
883-
cinf->is_expire = true;
884-
cinf->expire_time = 0;
885895

886896
if (cinf->is_prefetched) {
887897
Dbg(dbg_ctl_ssl_ocsp, "using OCSP prefetched response file %s", rsp_file);
@@ -949,24 +959,15 @@ ssl_stapling_init_cert(SSL_CTX *ctx, X509 *cert, const char *certname, const cha
949959
X509_up_ref(cert);
950960
#endif
951961

952-
map->insert(std::make_pair(cert, cinf));
962+
map->insert(std::make_pair(cert, cinf_ptr.release()));
953963
SSL_CTX_set_ex_data(ctx, ssl_stapling_index, map);
954964

955965
Note("successfully initialized stapling for %s into SSL_CTX: %p uri=%s", certname, ctx, cinf->uri);
956966
return true;
957967

958968
err:
959-
if (cinf->cid) {
960-
TS_OCSP_CERTID_free(cinf->cid);
961-
}
962-
963-
ats_free(cinf->certname);
964-
ats_free(cinf->user_agent);
965-
966-
if (cinf) {
967-
OPENSSL_free(cinf);
968-
}
969-
if (map) {
969+
// cinf_ptr destructor handles certinfo member cleanup
970+
if (map_is_new) {
970971
delete map;
971972
}
972973

@@ -1327,20 +1328,21 @@ ocsp_update()
13271328
if (map) {
13281329
// Walk over all certs associated with this CTX
13291330
for (auto &iter : *map) {
1330-
cinf = iter.second;
1331-
ink_mutex_acquire(&cinf->stapling_mutex);
1331+
cinf = iter.second;
13321332
current_time = time(nullptr);
1333-
if (cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < current_time) {
1334-
ink_mutex_release(&cinf->stapling_mutex);
1333+
bool needs_refresh;
1334+
{
1335+
ts::bravo::shared_lock lock(cinf->resp_mutex);
1336+
needs_refresh = cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < current_time;
1337+
}
1338+
if (needs_refresh) {
13351339
if (stapling_refresh_response(cinf, &resp)) {
13361340
Dbg(dbg_ctl_ssl_ocsp, "Successfully refreshed OCSP for %s certificate. url=%s", cinf->certname, cinf->uri);
13371341
Metrics::Counter::increment(ssl_rsb.ocsp_refreshed_cert);
13381342
} else {
13391343
Error("Failed to refresh OCSP for %s certificate. url=%s", cinf->certname, cinf->uri);
13401344
Metrics::Counter::increment(ssl_rsb.ocsp_refresh_cert_failure);
13411345
}
1342-
} else {
1343-
ink_mutex_release(&cinf->stapling_mutex);
13441346
}
13451347
}
13461348
}
@@ -1370,56 +1372,69 @@ ssl_callback_ocsp_stapling(SSL *ssl, void *)
13701372
return SSL_TLSEXT_ERR_NOACK;
13711373
}
13721374

1373-
// Fetch the specific certificate used in this negotiation
1374-
X509 *cert = SSL_get_certificate(ssl);
1375-
if (!cert) {
1376-
Error("ssl_callback_ocsp_stapling: failed to get certificate");
1377-
return SSL_TLSEXT_ERR_NOACK;
1378-
}
1379-
13801375
certinfo *cinf = nullptr;
1381-
#if HAVE_NATIVE_DUAL_CERT_SUPPORT
1382-
certinfo_map::iterator iter = map->find(cert);
1383-
if (iter != map->end()) {
1384-
cinf = iter->second;
1385-
}
1386-
#else
1387-
for (certinfo_map::iterator iter = map->begin(); iter != map->end(); ++iter) {
1388-
X509 *key = iter->first;
1389-
if (key == nullptr) {
1390-
continue;
1376+
1377+
// Fast path: if only one certificate in the map, skip SSL_get_certificate() lookup
1378+
if (map->size() == 1) {
1379+
cinf = map->begin()->second;
1380+
} else {
1381+
// Fetch the specific certificate used in this negotiation
1382+
X509 *cert = SSL_get_certificate(ssl);
1383+
if (!cert) {
1384+
Error("ssl_callback_ocsp_stapling: failed to get certificate");
1385+
return SSL_TLSEXT_ERR_NOACK;
13911386
}
13921387

1393-
if (X509_cmp(key, cert) == 0) {
1388+
#if HAVE_NATIVE_DUAL_CERT_SUPPORT
1389+
certinfo_map::iterator iter = map->find(cert);
1390+
if (iter != map->end()) {
13941391
cinf = iter->second;
1395-
break;
13961392
}
1397-
}
1393+
#else
1394+
for (certinfo_map::iterator iter = map->begin(); iter != map->end(); ++iter) {
1395+
X509 *key = iter->first;
1396+
if (key == nullptr) {
1397+
continue;
1398+
}
1399+
1400+
if (X509_cmp(key, cert) == 0) {
1401+
cinf = iter->second;
1402+
break;
1403+
}
1404+
}
13981405
#endif
1406+
}
13991407

14001408
if (cinf == nullptr) {
14011409
Error("ssl_callback_ocsp_stapling: failed to get certificate information for ssl=%p", ssl);
14021410
return SSL_TLSEXT_ERR_NOACK;
14031411
}
14041412

1405-
ink_mutex_acquire(&cinf->stapling_mutex);
1406-
time_t current_time = time(nullptr);
1407-
if ((cinf->resp_derlen == 0 || cinf->is_expire) || (cinf->expire_time < current_time && !cinf->is_prefetched)) {
1408-
ink_mutex_release(&cinf->stapling_mutex);
1409-
Error("ssl_callback_ocsp_stapling: failed to get certificate status for %s", cinf->certname);
1410-
return SSL_TLSEXT_ERR_NOACK;
1411-
} else {
1412-
unsigned char *p = static_cast<unsigned char *>(OPENSSL_malloc(cinf->resp_derlen));
1413-
if (p == nullptr) {
1414-
ink_mutex_release(&cinf->stapling_mutex);
1415-
Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: failed to allocate memory for %s", cinf->certname);
1413+
unsigned char resp_copy[MAX_STAPLING_DER];
1414+
unsigned int resp_copylen;
1415+
1416+
{
1417+
ts::bravo::shared_lock lock(cinf->resp_mutex);
1418+
1419+
time_t current_time = time(nullptr);
1420+
if (cinf->resp_derlen == 0 || cinf->is_expire || (cinf->expire_time < current_time && !cinf->is_prefetched)) {
1421+
Error("ssl_callback_ocsp_stapling: failed to get certificate status for %s", cinf->certname);
14161422
return SSL_TLSEXT_ERR_NOACK;
14171423
}
1418-
memcpy(p, cinf->resp_der, cinf->resp_derlen);
1419-
ink_mutex_release(&cinf->stapling_mutex);
1420-
SSL_set_tlsext_status_ocsp_resp(ssl, p, cinf->resp_derlen);
1421-
Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: successfully got certificate status for %s", cinf->certname);
1422-
Dbg(dbg_ctl_ssl_ocsp, "is_prefetched:%d uri:%s", cinf->is_prefetched, cinf->uri);
1423-
return SSL_TLSEXT_ERR_OK;
1424+
1425+
resp_copylen = cinf->resp_derlen;
1426+
memcpy(resp_copy, cinf->resp_der, resp_copylen);
1427+
}
1428+
1429+
unsigned char *p = static_cast<unsigned char *>(OPENSSL_malloc(resp_copylen));
1430+
if (p == nullptr) {
1431+
Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: failed to allocate memory for %s", cinf->certname);
1432+
return SSL_TLSEXT_ERR_NOACK;
14241433
}
1434+
memcpy(p, resp_copy, resp_copylen);
1435+
SSL_set_tlsext_status_ocsp_resp(ssl, p, resp_copylen);
1436+
1437+
Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: successfully got certificate status for %s", cinf->certname);
1438+
Dbg(dbg_ctl_ssl_ocsp, "is_prefetched:%d uri:%s", cinf->is_prefetched, cinf->uri);
1439+
return SSL_TLSEXT_ERR_OK;
14251440
}

0 commit comments

Comments
 (0)