Skip to content

Commit c5880c9

Browse files
committed
ocsp: replace stapling_mutex with std::shared_mutex and fix leaks
Replace the per-certinfo ink_mutex with a std::shared_mutex so readers on the TLS handshake hot path (ssl_callback_ocsp_stapling) and the refresh scan (ocsp_update) can run concurrently; only the cache update takes an exclusive lock. The handshake reader now copies the DER staple to a stack buffer under the shared lock and allocates outside the critical section. Because the mutex is no longer a C type, certinfo is allocated with new/delete instead of OPENSSL_malloc. Also fix three pre-existing bugs: - certinfo_map_free never freed cinf->cid (leak on every CTX teardown). - On BoringSSL the X509 key ref taken via X509_up_ref was never released; free it in certinfo_map_free. - ssl_stapling_init_cert's error path could delete a certinfo_map still owned by the SSL_CTX. Only delete a map this call created.
1 parent 464c613 commit c5880c9

1 file changed

Lines changed: 66 additions & 49 deletions

File tree

src/iocore/net/OCSPStapling.cc

Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121

2222
#include "P_OCSPStapling.h"
2323

24+
#include <mutex>
25+
#include <shared_mutex>
26+
2427
#include <openssl/ssl.h>
2528
#include <openssl/x509v3.h>
2629
#include <openssl/asn1.h>
@@ -284,12 +287,15 @@ struct certinfo {
284287
char *uri; // Responder details
285288
char *certname;
286289
char *user_agent;
287-
ink_mutex stapling_mutex;
288-
unsigned char resp_der[MAX_STAPLING_DER];
289-
unsigned int resp_derlen;
290290
bool is_prefetched;
291-
bool is_expire;
292-
time_t expire_time;
291+
292+
// OCSP response data, protected by resp_mutex.
293+
// Readers take a shared lock; the updater takes an exclusive lock.
294+
unsigned char resp_der[MAX_STAPLING_DER];
295+
unsigned int resp_derlen;
296+
bool is_expire;
297+
time_t expire_time;
298+
mutable std::shared_mutex resp_mutex;
293299
};
294300

295301
class HTTPRequest : public Continuation
@@ -737,15 +743,20 @@ certinfo_map_free(void * /*parent*/, void *ptr, CRYPTO_EX_DATA * /*ad*/, int /*i
737743

738744
for (auto &iter : *map) {
739745
certinfo *cinf = iter.second;
746+
if (cinf->cid) {
747+
TS_OCSP_CERTID_free(cinf->cid);
748+
}
740749
if (cinf->uri) {
741750
OPENSSL_free(cinf->uri);
742751
}
743752

744753
ats_free(cinf->certname);
745754
ats_free(cinf->user_agent);
746755

747-
ink_mutex_destroy(&cinf->stapling_mutex);
748-
OPENSSL_free(cinf);
756+
#ifdef OPENSSL_IS_BORINGSSL
757+
X509_free(iter.first);
758+
#endif
759+
delete cinf;
749760
}
750761
delete map;
751762
}
@@ -831,12 +842,13 @@ stapling_cache_response(TS_OCSP_RESPONSE *rsp, certinfo *cinf)
831842
return false;
832843
}
833844

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);
845+
{
846+
std::lock_guard lock(cinf->resp_mutex);
847+
memcpy(cinf->resp_der, resp_der, resp_derlen);
848+
cinf->resp_derlen = resp_derlen;
849+
cinf->is_expire = false;
850+
cinf->expire_time = time(nullptr) + SSLConfigParams::ssl_ocsp_cache_timeout;
851+
}
840852

841853
Dbg(dbg_ctl_ssl_ocsp, "stapling_cache_response: success to cache response");
842854
return true;
@@ -860,15 +872,12 @@ ssl_stapling_init_cert(SSL_CTX *ctx, X509 *cert, const char *certname, const cha
860872
return false;
861873
}
862874

875+
bool map_is_new = false;
863876
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;
877+
map = new certinfo_map;
878+
map_is_new = true;
871879
}
880+
certinfo *cinf = new certinfo();
872881

873882
// Initialize certinfo
874883
cinf->cid = nullptr;
@@ -877,8 +886,7 @@ ssl_stapling_init_cert(SSL_CTX *ctx, X509 *cert, const char *certname, const cha
877886
if (SSLConfigParams::ssl_ocsp_user_agent != nullptr) {
878887
cinf->user_agent = ats_strdup(SSLConfigParams::ssl_ocsp_user_agent);
879888
}
880-
cinf->resp_derlen = 0;
881-
ink_mutex_init(&cinf->stapling_mutex);
889+
cinf->resp_derlen = 0;
882890
cinf->is_prefetched = rsp_file ? true : false;
883891
cinf->is_expire = true;
884892
cinf->expire_time = 0;
@@ -963,10 +971,11 @@ ssl_stapling_init_cert(SSL_CTX *ctx, X509 *cert, const char *certname, const cha
963971
ats_free(cinf->certname);
964972
ats_free(cinf->user_agent);
965973

966-
if (cinf) {
967-
OPENSSL_free(cinf);
968-
}
969-
if (map) {
974+
delete cinf;
975+
976+
// Only tear down the map if this call created it; an existing map is still
977+
// owned by the SSL_CTX.
978+
if (map_is_new) {
970979
delete map;
971980
}
972981

@@ -1327,20 +1336,21 @@ ocsp_update()
13271336
if (map) {
13281337
// Walk over all certs associated with this CTX
13291338
for (auto &iter : *map) {
1330-
cinf = iter.second;
1331-
ink_mutex_acquire(&cinf->stapling_mutex);
1339+
cinf = iter.second;
13321340
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);
1341+
bool needs_refresh;
1342+
{
1343+
std::shared_lock lock(cinf->resp_mutex);
1344+
needs_refresh = cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < current_time;
1345+
}
1346+
if (needs_refresh) {
13351347
if (stapling_refresh_response(cinf, &resp)) {
13361348
Dbg(dbg_ctl_ssl_ocsp, "Successfully refreshed OCSP for %s certificate. url=%s", cinf->certname, cinf->uri);
13371349
Metrics::Counter::increment(ssl_rsb.ocsp_refreshed_cert);
13381350
} else {
13391351
Error("Failed to refresh OCSP for %s certificate. url=%s", cinf->certname, cinf->uri);
13401352
Metrics::Counter::increment(ssl_rsb.ocsp_refresh_cert_failure);
13411353
}
1342-
} else {
1343-
ink_mutex_release(&cinf->stapling_mutex);
13441354
}
13451355
}
13461356
}
@@ -1402,24 +1412,31 @@ ssl_callback_ocsp_stapling(SSL *ssl, void *)
14021412
return SSL_TLSEXT_ERR_NOACK;
14031413
}
14041414

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);
1415+
unsigned char resp_copy[MAX_STAPLING_DER];
1416+
unsigned int resp_copylen;
1417+
1418+
{
1419+
std::shared_lock lock(cinf->resp_mutex);
1420+
1421+
time_t current_time = time(nullptr);
1422+
if (cinf->resp_derlen == 0 || cinf->is_expire || (cinf->expire_time < current_time && !cinf->is_prefetched)) {
1423+
Error("ssl_callback_ocsp_stapling: failed to get certificate status for %s", cinf->certname);
14161424
return SSL_TLSEXT_ERR_NOACK;
14171425
}
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;
1426+
1427+
resp_copylen = cinf->resp_derlen;
1428+
memcpy(resp_copy, cinf->resp_der, resp_copylen);
1429+
}
1430+
1431+
unsigned char *p = static_cast<unsigned char *>(OPENSSL_malloc(resp_copylen));
1432+
if (p == nullptr) {
1433+
Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: failed to allocate memory for %s", cinf->certname);
1434+
return SSL_TLSEXT_ERR_NOACK;
14241435
}
1436+
memcpy(p, resp_copy, resp_copylen);
1437+
SSL_set_tlsext_status_ocsp_resp(ssl, p, resp_copylen);
1438+
1439+
Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: successfully got certificate status for %s", cinf->certname);
1440+
Dbg(dbg_ctl_ssl_ocsp, "is_prefetched:%d uri:%s", cinf->is_prefetched, cinf->uri);
1441+
return SSL_TLSEXT_ERR_OK;
14251442
}

0 commit comments

Comments
 (0)