From c2d2f647611e26800dc573b77c406644716cc623 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Sun, 22 Mar 2026 21:34:54 -0700 Subject: [PATCH 1/5] Fix memory leak in client_context_dump plugin The results array allocated via malloc() in CB_context_dump was never freed after iterating over the context names. --- example/plugins/c-api/client_context_dump/client_context_dump.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/example/plugins/c-api/client_context_dump/client_context_dump.cc b/example/plugins/c-api/client_context_dump/client_context_dump.cc index d416c72884b..f8572d775ce 100644 --- a/example/plugins/c-api/client_context_dump/client_context_dump.cc +++ b/example/plugins/c-api/client_context_dump/client_context_dump.cc @@ -163,6 +163,7 @@ CB_context_dump(TSCont, TSEvent, void *edata) for (int i = 0; i < count; i += 2) { dump_context(results[i], results[i + 1]); } + free(results); } } TSTextLogObjectFlush(context_dump_log); From eb7e9fd1b836c3b8aa1bc943146ead617f1066b8 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Sun, 22 Mar 2026 21:35:19 -0700 Subject: [PATCH 2/5] Fix memory leak in ConfigCache destructor The ConfigCache class stores raw S3Config pointers in its _cache map but lacks a destructor, leaking all configs on shutdown. Add a destructor that deletes all remaining S3Config pointers. --- plugins/origin_server_auth/origin_server_auth.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/origin_server_auth/origin_server_auth.cc b/plugins/origin_server_auth/origin_server_auth.cc index 583ca0f785a..e2eaaa9a1c9 100644 --- a/plugins/origin_server_auth/origin_server_auth.cc +++ b/plugins/origin_server_auth/origin_server_auth.cc @@ -154,6 +154,8 @@ class S3Config; class ConfigCache { public: + ~ConfigCache(); + S3Config *get(const char *fname); private: @@ -601,6 +603,13 @@ class S3Config int _invalid_file_count = 0; }; +ConfigCache::~ConfigCache() +{ + for (auto &[key, data] : _cache) { + delete data.config.load(); + } +} + bool S3Config::parse_config(const std::string &config_fname) { From b1a0a7893fe9c20918ed00586ebbe51e3f73a70a Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Sun, 22 Mar 2026 21:47:32 -0700 Subject: [PATCH 3/5] Fix memory leak of HCFileInfo linked list in healthchecks The global g_config linked list of HCFileInfo structs (and their ok/miss buffers and HCFileData) were never freed. Add a shutdown handler via TS_LIFECYCLE_SHUTDOWN_HOOK to walk the list and free all allocations when the plugin shuts down. --- plugins/healthchecks/healthchecks.cc | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/plugins/healthchecks/healthchecks.cc b/plugins/healthchecks/healthchecks.cc index f31a43d0cc1..e6ff01cd931 100644 --- a/plugins/healthchecks/healthchecks.cc +++ b/plugins/healthchecks/healthchecks.cc @@ -287,6 +287,32 @@ hc_thread(void *data ATS_UNUSED) return nullptr; /* Yeah, that never happens */ } +/* Free all HCFileInfo nodes and their associated allocations */ +static void +free_config(HCFileInfo *head) +{ + while (head) { + HCFileInfo *next = head->_next; + + TSfree(const_cast(head->ok)); + TSfree(const_cast(head->miss)); + TSfree(head->data.load()); + TSfree(head); + head = next; + } +} + +/* Shutdown handler to clean up the global config */ +static int +hc_shutdown_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* edata ATS_UNUSED */) +{ + if (event == TS_EVENT_LIFECYCLE_SHUTDOWN) { + free_config(g_config); + g_config = nullptr; + } + return 0; +} + /* Config file parsing */ static const char HEADER_TEMPLATE[] = "HTTP/1.1 %d %s\r\nContent-Type: %s\r\nCache-Control: no-cache\r\n"; @@ -622,6 +648,9 @@ TSPluginInit(int argc, const char *argv[]) return; } + /* Register shutdown handler to free config memory */ + TSLifecycleHookAdd(TS_LIFECYCLE_SHUTDOWN_HOOK, TSContCreate(hc_shutdown_handler, nullptr)); + /* Create a continuation with a mutex as there is a shared global structure containing the headers to add */ Dbg(dbg_ctl, "Started %s plugin", PLUGIN_NAME); From c1fa557ae042b34b55fb8bfaed6233e497122706 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Sun, 22 Mar 2026 21:48:06 -0700 Subject: [PATCH 4/5] Fix memory leak of log_info.filename in stale_response The ConfigInfo destructor frees body_data and body_data_mutex but never frees log_info.filename, which is allocated via strdup() when the -d option is parsed. This adds a free() call in the destructor, guarded by a check against the default static PLUGIN_TAG pointer. --- plugins/experimental/stale_response/stale_response.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/experimental/stale_response/stale_response.h b/plugins/experimental/stale_response/stale_response.h index 6c0454a8aff..a0a9071ea58 100644 --- a/plugins/experimental/stale_response/stale_response.h +++ b/plugins/experimental/stale_response/stale_response.h @@ -23,6 +23,8 @@ #pragma once +#include + #include "ts/apidefs.h" #include "ts_wrap.h" #include "ts/ts.h" @@ -63,6 +65,9 @@ struct ConfigInfo { if (this->body_data_mutex) { TSMutexDestroy(this->body_data_mutex); } + if (this->log_info.filename != PLUGIN_TAG) { + free(const_cast(this->log_info.filename)); + } } UintBodyMap *body_data = nullptr; TSMutex body_data_mutex; From c5e336eb76e7607d7e78629dd44e73bdcaf104ce Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Thu, 26 Mar 2026 22:54:07 -0700 Subject: [PATCH 5/5] =?UTF-8?q?Revert=20healthchecks=20shutdown=20handler?= =?UTF-8?q?=20=E2=80=94=20races=20with=20hc=5Fthread=20The=20shutdown=20ho?= =?UTF-8?q?ok=20frees=20g=5Fconfig=20while=20hc=5Fthread=20is=20still=20ru?= =?UTF-8?q?nning=20its=20infinite=20inotify=20loop,=20causing=20a=20use-af?= =?UTF-8?q?ter-free.=20Since=20this=20leak=20only=20occurs=20at=20process?= =?UTF-8?q?=20exit,=20let=20the=20OS=20reclaim=20the=20memory.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/healthchecks/healthchecks.cc | 29 ---------------------------- 1 file changed, 29 deletions(-) diff --git a/plugins/healthchecks/healthchecks.cc b/plugins/healthchecks/healthchecks.cc index e6ff01cd931..f31a43d0cc1 100644 --- a/plugins/healthchecks/healthchecks.cc +++ b/plugins/healthchecks/healthchecks.cc @@ -287,32 +287,6 @@ hc_thread(void *data ATS_UNUSED) return nullptr; /* Yeah, that never happens */ } -/* Free all HCFileInfo nodes and their associated allocations */ -static void -free_config(HCFileInfo *head) -{ - while (head) { - HCFileInfo *next = head->_next; - - TSfree(const_cast(head->ok)); - TSfree(const_cast(head->miss)); - TSfree(head->data.load()); - TSfree(head); - head = next; - } -} - -/* Shutdown handler to clean up the global config */ -static int -hc_shutdown_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* edata ATS_UNUSED */) -{ - if (event == TS_EVENT_LIFECYCLE_SHUTDOWN) { - free_config(g_config); - g_config = nullptr; - } - return 0; -} - /* Config file parsing */ static const char HEADER_TEMPLATE[] = "HTTP/1.1 %d %s\r\nContent-Type: %s\r\nCache-Control: no-cache\r\n"; @@ -648,9 +622,6 @@ TSPluginInit(int argc, const char *argv[]) return; } - /* Register shutdown handler to free config memory */ - TSLifecycleHookAdd(TS_LIFECYCLE_SHUTDOWN_HOOK, TSContCreate(hc_shutdown_handler, nullptr)); - /* Create a continuation with a mutex as there is a shared global structure containing the headers to add */ Dbg(dbg_ctl, "Started %s plugin", PLUGIN_NAME);