From d33cb616da87756cee8e6cefd22dacac1488c3de Mon Sep 17 00:00:00 2001 From: Ross Tyler Date: Fri, 19 Dec 2025 07:57:52 -0800 Subject: [PATCH] fix(asio): memory leaks over ssl::stream lifetime asio::ssl::stream leaks a significant amount (>20KB) of memory over its lifetime. asio::ssl::mbedtls::engine::impl has these members ... mbedtls_ssl_context ssl_{}; mbedtls_entropy_context entropy_{}; mbedtls_ctr_drbg_context ctr_drbg_{}; mbedtls_ssl_config conf_{}; mbedtls_x509_crt public_cert_{}; mbedtls_pk_context pk_key_{}; mbedtls_x509_crt ca_cert_{}; ... which are properly init'ed in its constructor ... mbedtls_ssl_init(&ssl_); mbedtls_ssl_config_init(&conf_); mbedtls_ctr_drbg_init(&ctr_drbg_); mbedtls_entropy_init(&entropy_); mbedtls_x509_crt_init(&public_cert_); mbedtls_pk_init(&pk_key_); mbedtls_x509_crt_init(&ca_cert_); ... but are never free'd ... until now ~impl() { mbedtls_x509_crt_free(&ca_cert_); mbedtls_pk_free(&pk_key_); mbedtls_x509_crt_free(&public_cert_); mbedtls_entropy_free(&entropy_); mbedtls_ctr_drbg_free(&ctr_drbg_); mbedtls_ssl_config_free(&conf_); mbedtls_ssl_free(&ssl_); } asio::ssl::mbedtls::engine::impl::configure calls ... mbedtls_x509_crt_init(&public_cert_); mbedtls_pk_init(&pk_key_); mbedtls_x509_crt_init(&ca_cert_); ... again without first free'ing any resources that might be held by such. now this is done first: mbedtls_x509_crt_free(&ca_cert_); mbedtls_pk_free(&pk_key_); mbedtls_x509_crt_free(&public_cert_); asio::ssl::engine has this member std::pair, std::shared_ptr> bio_; which is made in its constructor explicit engine(std::shared_ptr ctx): ctx_(std::move(ctx)), bio_(bio::new_pair("mbedtls-engine")), state_(IDLE), verify_mode_(0) {} asio::ssl::mbedtls::bio::new_pair creates a cyclic reference between its paired elements static std::pair, std::shared_ptr> new_pair(const char *error_location) { auto b1 = std::shared_ptr(new (std::nothrow) bio); auto b2 = std::shared_ptr(new (std::nothrow) bio); if (b1 == nullptr || b2 == nullptr) { throw_alloc_failure(error_location); } else { b1->peer_ = b2; b2->peer_ = b1; } return std::make_pair(b1, b2); } there is no asio::ssl::engine destructor to untie this cycle so when the pair member is destroyed, its elements will leak. a destructor is needed to fix this ... ~engine() { bio::untie_pair(bio_); } ... along with untie_pair ... // untie cyclic shared_ptr references made by new_pair in preparation for destruction static void untie_pair(std::pair, std::shared_ptr>& pair) { if (pair.first) { pair.first->peer_.reset(); } if (pair.second) { pair.second->peer_.reset(); } } --- .../asio/port/mbedtls/include/mbedtls_bio.hpp | 11 +++++++++++ .../port/mbedtls/include/mbedtls_engine.hpp | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/components/asio/port/mbedtls/include/mbedtls_bio.hpp b/components/asio/port/mbedtls/include/mbedtls_bio.hpp index c408a1682..5b1f978c9 100644 --- a/components/asio/port/mbedtls/include/mbedtls_bio.hpp +++ b/components/asio/port/mbedtls/include/mbedtls_bio.hpp @@ -100,6 +100,17 @@ public: return std::make_pair(b1, b2); } + // untie cyclic shared_ptr references made by new_pair in preparation for destruction + static void untie_pair(std::pair, std::shared_ptr>& pair) + { + if (pair.first) { + pair.first->peer_.reset(); + } + if (pair.second) { + pair.second->peer_.reset(); + } + } + private: std::array data_ {}; size_t size_ {BIO_SIZE}; diff --git a/components/asio/port/mbedtls/include/mbedtls_engine.hpp b/components/asio/port/mbedtls/include/mbedtls_engine.hpp index 8563accdb..9e0f664a9 100644 --- a/components/asio/port/mbedtls/include/mbedtls_engine.hpp +++ b/components/asio/port/mbedtls/include/mbedtls_engine.hpp @@ -62,6 +62,11 @@ public: explicit engine(std::shared_ptr ctx): ctx_(std::move(ctx)), bio_(bio::new_pair("mbedtls-engine")), state_(IDLE), verify_mode_(0) {} + ~engine() + { + bio::untie_pair(bio_); + } + void set_verify_mode(asio::ssl::verify_mode mode) { verify_mode_ = mode; @@ -232,8 +237,22 @@ private: mbedtls_x509_crt_init(&ca_cert_); } + ~impl() + { + mbedtls_x509_crt_free(&ca_cert_); + mbedtls_pk_free(&pk_key_); + mbedtls_x509_crt_free(&public_cert_); + mbedtls_entropy_free(&entropy_); + mbedtls_ctr_drbg_free(&ctr_drbg_); + mbedtls_ssl_config_free(&conf_); + mbedtls_ssl_free(&ssl_); + } + bool configure(context *ctx, bool is_client_not_server, int mbedtls_verify_mode) { + mbedtls_x509_crt_free(&ca_cert_); + mbedtls_pk_free(&pk_key_); + mbedtls_x509_crt_free(&public_cert_); mbedtls_x509_crt_init(&public_cert_); mbedtls_pk_init(&pk_key_); mbedtls_x509_crt_init(&ca_cert_);