From d33cb616da87756cee8e6cefd22dacac1488c3de Mon Sep 17 00:00:00 2001 From: Ross Tyler Date: Fri, 19 Dec 2025 07:57:52 -0800 Subject: [PATCH 1/2] 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 c408a1682c..5b1f978c98 100644 --- a/components/asio/port/mbedtls/include/mbedtls_bio.hpp +++ b/components/asio/port/mbedtls/include/mbedtls_bio.hpp @@ -100,6 +100,17 @@ class bio { 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 8563accdb4..9e0f664a90 100644 --- a/components/asio/port/mbedtls/include/mbedtls_engine.hpp +++ b/components/asio/port/mbedtls/include/mbedtls_engine.hpp @@ -62,6 +62,11 @@ class engine { 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 @@ class engine { 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_); From 9590aecc1503bffba9bc4ade3120822f1d185874 Mon Sep 17 00:00:00 2001 From: Ross Tyler Date: Fri, 19 Dec 2025 09:19:12 -0800 Subject: [PATCH 2/2] change mbedtls_*_free destruction order --- components/asio/port/mbedtls/include/mbedtls_engine.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/asio/port/mbedtls/include/mbedtls_engine.hpp b/components/asio/port/mbedtls/include/mbedtls_engine.hpp index 9e0f664a90..4594bcee51 100644 --- a/components/asio/port/mbedtls/include/mbedtls_engine.hpp +++ b/components/asio/port/mbedtls/include/mbedtls_engine.hpp @@ -239,13 +239,13 @@ class engine { ~impl() { + mbedtls_ssl_free(&ssl_); + mbedtls_ssl_config_free(&conf_); + mbedtls_ctr_drbg_free(&ctr_drbg_); + mbedtls_entropy_free(&entropy_); 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)