This is an automated email from the ASF dual-hosted git repository.
moonchen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new b85ebd8709 TLS: Fix memory leaks in cert load and OCSP stapling
(#13318)
b85ebd8709 is described below
commit b85ebd8709ac16ca8cdae5372bab38fc7b90e148
Author: Mo Chen <[email protected]>
AuthorDate: Wed Jun 24 23:13:31 2026 -0500
TLS: Fix memory leaks in cert load and OCSP stapling (#13318)
Several error and early-return paths in the TLS server certificate loading
and OCSP stapling code omit cleanup and leak native objects. Most are gated
on misconfiguration (bad or unreadable cert files) or allocation failure, so
they accumulate slowly rather than on every handshake.
1. setClientCertCACerts leaked the X509_STORE when X509_STORE_load_locations
failed; the store is only adopted by SSL_set0_verify_cert_store on
success.
This fires per handshake on the sni.yaml verify_client path when the CA
file or directory is bad. It also now skips the load when X509_STORE_new
returns null, which would otherwise crash on allocation failure.
2. SSLNetVConnection::populate leaked the migrated SSL object when the base
populate failed: the source VC has already relinquished ownership, so the
SSL must be freed when this VC does not adopt it.
3. load_certs leaked the leaf certificate on the mid-chain failure returns
(bad intermediate chain, private key, or CA file). The leaf is now held
in a scoped_X509 so every path releases it.
4. _prep_ssl_ctx and SSLCreateServerContext leaked already-parsed leaves
when
load_certs_and_cross_reference_names failed partway, because the
X509_free
drain was skipped on the early return.
5. set_session_id_context leaked the client CA name stack on the digest
error
paths that run before ownership is handed to the SSL_CTX.
6. ssl_callback_ocsp_stapling ignored the OCSP-response set result and
leaked
the buffer on failure. On BoringSSL it now uses SSL_set_ocsp_response,
which copies the cached buffer and needs no per-handshake allocation; on
OpenSSL it checks SSL_set_tlsext_status_ocsp_resp and frees on failure.
7. stapling_refresh_response leaked the duplicated OCSP CERTID when
TS_OCSP_request_add0_id failed.
---
src/iocore/net/OCSPStapling.cc | 17 +++++++++-
src/iocore/net/SSLNetVConnection.cc | 4 +++
src/iocore/net/SSLUtils.cc | 63 ++++++++++++++++++++-----------------
3 files changed, 55 insertions(+), 29 deletions(-)
diff --git a/src/iocore/net/OCSPStapling.cc b/src/iocore/net/OCSPStapling.cc
index bce21e159f..948bb4f834 100644
--- a/src/iocore/net/OCSPStapling.cc
+++ b/src/iocore/net/OCSPStapling.cc
@@ -1241,6 +1241,8 @@ stapling_refresh_response(certinfo *cinf,
TS_OCSP_RESPONSE **prsp)
goto err;
}
if (!TS_OCSP_request_add0_id(req, id)) {
+ // add0 only takes ownership of id on success.
+ TS_OCSP_CERTID_free(id);
goto err;
}
@@ -1427,6 +1429,11 @@ ssl_callback_ocsp_stapling(SSL *ssl, void *)
Error("ssl_callback_ocsp_stapling: failed to get certificate status for
%s", cinf->certname);
return SSL_TLSEXT_ERR_NOACK;
} else {
+#ifdef OPENSSL_IS_BORINGSSL
+ // SSL_set_ocsp_response copies the response, so hand it the cached buffer
directly.
+ int set_ok = SSL_set_ocsp_response(ssl, cinf->resp_der, cinf->resp_derlen);
+ ink_mutex_release(&cinf->stapling_mutex);
+#else
unsigned char *p = static_cast<unsigned char
*>(OPENSSL_malloc(cinf->resp_derlen));
if (p == nullptr) {
ink_mutex_release(&cinf->stapling_mutex);
@@ -1435,7 +1442,15 @@ ssl_callback_ocsp_stapling(SSL *ssl, void *)
}
memcpy(p, cinf->resp_der, cinf->resp_derlen);
ink_mutex_release(&cinf->stapling_mutex);
- SSL_set_tlsext_status_ocsp_resp(ssl, p, cinf->resp_derlen);
+ // Takes ownership of p and frees it on success; on failure it does not.
+ int set_ok = SSL_set_tlsext_status_ocsp_resp(ssl, p, cinf->resp_derlen);
+ if (set_ok == 0) {
+ OPENSSL_free(p);
+ }
+#endif
+ if (set_ok == 0) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
Dbg(dbg_ctl_ssl_ocsp, "ssl_callback_ocsp_stapling: successfully got
certificate status for %s", cinf->certname);
Dbg(dbg_ctl_ssl_ocsp, "is_prefetched:%d uri:%s", cinf->is_prefetched,
cinf->uri);
return SSL_TLSEXT_ERR_OK;
diff --git a/src/iocore/net/SSLNetVConnection.cc
b/src/iocore/net/SSLNetVConnection.cc
index ed7bb03cd3..5ad9f97b56 100644
--- a/src/iocore/net/SSLNetVConnection.cc
+++ b/src/iocore/net/SSLNetVConnection.cc
@@ -1745,6 +1745,10 @@ SSLNetVConnection::populate(Connection &con,
Continuation *c, void *arg)
{
int retval = super::populate(con, c, arg);
if (retval != EVENT_DONE) {
+ // arg is the migrated SSL this VC would adopt below; release it since we
won't.
+ if (arg != nullptr) {
+ SSL_free(static_cast<SSL *>(arg));
+ }
return retval;
}
// Add in the SSL data
diff --git a/src/iocore/net/SSLUtils.cc b/src/iocore/net/SSLUtils.cc
index 5928042d41..7c7637affa 100644
--- a/src/iocore/net/SSLUtils.cc
+++ b/src/iocore/net/SSLUtils.cc
@@ -1165,8 +1165,11 @@ setClientCertCACerts(SSL *ssl, const char *file, const
char *dir)
#if TS_HAS_VERIFY_CERT_STORE
// The set0 version will take ownership of the X509_STORE object
X509_STORE *ctx = X509_STORE_new();
- if (X509_STORE_load_locations(ctx, file && file[0] != '\0' ? file :
nullptr, dir && dir[0] != '\0' ? dir : nullptr)) {
+ if (ctx != nullptr &&
+ X509_STORE_load_locations(ctx, file && file[0] != '\0' ? file :
nullptr, dir && dir[0] != '\0' ? dir : nullptr)) {
SSL_set0_verify_cert_store(ssl, ctx);
+ } else {
+ X509_STORE_free(ctx);
}
// SSL_set_client_CA_list takes ownership of the STACK_OF(X509) structure
@@ -1552,13 +1555,15 @@ SSLCreateServerContext(const SSLConfigParams *params,
const SSLMultiCertConfigPa
std::unordered_map<int, std::set<std::string>> unique_names;
SSLMultiCertConfigLoader::CertLoadData data;
SSLCertContextType cert_type;
- if (!loader.load_certs_and_cross_reference_names(cert_list, data, params,
sslMultiCertSettings, common_names, unique_names,
- &cert_type)) {
- return nullptr;
- }
+ bool loaded = loader.load_certs_and_cross_reference_names(cert_list, data,
params, sslMultiCertSettings, common_names,
+ unique_names,
&cert_type);
+ // cert_list may hold parsed leaves even when loading failed partway.
for (auto &i : cert_list) {
X509_free(i);
}
+ if (!loaded) {
+ return nullptr;
+ }
std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)> ctx(nullptr,
&SSL_CTX_free);
@@ -1603,24 +1608,24 @@ SSLMultiCertConfigLoader::_prep_ssl_ctx(const
shared_SSLMultiCertConfigParams &
const SSLConfigParams *params = this->_params;
SSLCertContextType cert_type;
- if (!this->load_certs_and_cross_reference_names(cert_list, data, params,
sslMultCertSettings.get(), common_names, unique_names,
- &cert_type)) {
- return false;
- }
-
- int i = 0;
- bool good_certs = true;
- for (auto const &cert : cert_list) {
- const char *current_cert_name = data.cert_names_list[i].c_str();
- if (0 > SSLMultiCertConfigLoader::check_server_cert_now(cert,
current_cert_name)) {
- /* At this point, we know cert is bad, and we've already printed a
- descriptive reason as to why cert is bad to the log file */
- Dbg(this->_dbg_ctl(), "Marking certificate as NOT VALID: %s",
current_cert_name);
- good_certs = false;
+ bool good_certs = this->load_certs_and_cross_reference_names(cert_list,
data, params, sslMultCertSettings.get(), common_names,
+ unique_names,
&cert_type);
+
+ if (good_certs) {
+ int i = 0;
+ for (auto const &cert : cert_list) {
+ const char *current_cert_name = data.cert_names_list[i].c_str();
+ if (0 > SSLMultiCertConfigLoader::check_server_cert_now(cert,
current_cert_name)) {
+ /* At this point, we know cert is bad, and we've already printed a
+ descriptive reason as to why cert is bad to the log file */
+ Dbg(this->_dbg_ctl(), "Marking certificate as NOT VALID: %s",
current_cert_name);
+ good_certs = false;
+ }
+ i++;
}
- i++;
}
+ // cert_list may hold parsed leaves even when loading failed partway.
for (auto &cert : cert_list) {
X509_free(cert);
}
@@ -2304,10 +2309,10 @@ SSLMultiCertConfigLoader::load_certs(SSL_CTX *ctx,
const std::vector<std::string
keyPath.empty() ? "[empty key path]" : keyPath.c_str());
return false;
}
- scoped_BIO bio(BIO_new_mem_buf(secret_data.data(), secret_data.size()));
- X509 *cert = nullptr;
+ scoped_BIO bio(BIO_new_mem_buf(secret_data.data(), secret_data.size()));
+ scoped_X509 cert;
if (bio) {
- cert = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr);
+ cert.reset(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
} else {
SSLError("failed to create bio for certificate secret %s of length %ld",
data.cert_names_list[i].c_str(), secret_data.size());
return false;
@@ -2319,9 +2324,8 @@ SSLMultiCertConfigLoader::load_certs(SSL_CTX *ctx, const
std::vector<std::string
}
Dbg(dbg_ctl_ssl_load, "for ctx=%p, using certificate %s", ctx,
cert_names_list[i].c_str());
- if (!SSL_CTX_use_certificate(ctx, cert)) {
+ if (!SSL_CTX_use_certificate(ctx, cert.get())) {
SSLError("Failed to assign cert from %s to SSL_CTX",
cert_names_list[i].c_str());
- X509_free(cert);
return false;
}
@@ -2375,16 +2379,15 @@ SSLMultiCertConfigLoader::load_certs(SSL_CTX *ctx,
const std::vector<std::string
if (sslMultCertSettings->ocsp_response) {
const char *ocsp_response_name = data.ocsp_list[i].c_str();
std::string
completeOCSPResponsePath(Layout::relative_to(params->ssl_ocsp_response_path_only,
ocsp_response_name));
- if (!ssl_stapling_init_cert(ctx, cert, cert_names_list[i].c_str(),
completeOCSPResponsePath.c_str())) {
+ if (!ssl_stapling_init_cert(ctx, cert.get(),
cert_names_list[i].c_str(), completeOCSPResponsePath.c_str())) {
Warning("failed to configure SSL_CTX for OCSP Stapling info for
certificate at %s", cert_names_list[i].c_str());
}
} else {
- if (!ssl_stapling_init_cert(ctx, cert, cert_names_list[i].c_str(),
nullptr)) {
+ if (!ssl_stapling_init_cert(ctx, cert.get(),
cert_names_list[i].c_str(), nullptr)) {
Warning("failed to configure SSL_CTX for OCSP Stapling info for
certificate at %s", cert_names_list[i].c_str());
}
}
}
- X509_free(cert);
}
return true;
}
@@ -2435,6 +2438,7 @@ SSLMultiCertConfigLoader::set_session_id_context(SSL_CTX
*ctx, const SSLConfigPa
// Set the list of CA's to send to client if we ask for a client
certificate
SSL_CTX_set_client_CA_list(ctx, ca_list);
+ ca_list = nullptr; // ownership transferred to ctx
}
if (EVP_DigestFinal_ex(digest, hash_buf, &hash_len) == 0) {
@@ -2451,6 +2455,9 @@ SSLMultiCertConfigLoader::set_session_id_context(SSL_CTX
*ctx, const SSLConfigPa
fail:
EVP_MD_CTX_free(digest);
+ if (ca_list != nullptr) {
+ sk_X509_NAME_pop_free(ca_list, X509_NAME_free);
+ }
return result;
}