From 008b2aade44e29bc298924c5b4afebfc52a1843c Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Sun, 22 Mar 2026 20:38:41 -0700 Subject: [PATCH 1/6] Fix heap-buffer-overflow in stats_over_http Use sized format specifier %.*s with string_view to avoid reading past the end of the buffer. --- plugins/stats_over_http/stats_over_http.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/stats_over_http/stats_over_http.cc b/plugins/stats_over_http/stats_over_http.cc index b02b17c44e2..e927cc68499 100644 --- a/plugins/stats_over_http/stats_over_http.cc +++ b/plugins/stats_over_http/stats_over_http.cc @@ -777,7 +777,8 @@ stats_origin(TSCont contp, TSEvent /* event ATS_UNUSED */, void *edata) icontp = TSContCreate(stats_dostuff, TSMutexCreate()); if (path_had_explicit_format) { - Dbg(dbg_ctl, "Path had explicit format, ignoring any Accept header: %s", request_path_suffix.data()); + Dbg(dbg_ctl, "Path had explicit format, ignoring any Accept header: %.*s", static_cast(request_path_suffix.size()), + request_path_suffix.data()); my_state->output_format = format_per_path; } else { // Check for an Accept header to determine response type. From d529c480911be26caf3fb67aff5b833709fc0297 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Sun, 22 Mar 2026 20:38:43 -0700 Subject: [PATCH 2/6] Fix delete type mismatch in ja4_fingerprint Cast to JA4_data* instead of std::string* before delete to match the actual allocated type and invoke the correct destructor. --- plugins/experimental/ja4_fingerprint/plugin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index 216171280fa..1b109716435 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -444,7 +444,7 @@ handle_vconn_close(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) return TS_SUCCESS; } TSVConn const ssl_vc{static_cast(edata)}; - delete static_cast(TSUserArgGet(ssl_vc, *get_user_arg_index())); + delete static_cast(TSUserArgGet(ssl_vc, *get_user_arg_index())); TSUserArgSet(ssl_vc, *get_user_arg_index(), nullptr); TSVConnReenable(ssl_vc); return TS_SUCCESS; From 3d7101f52bbfb83dbbbfd356ff964b43948d9359 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Sun, 22 Mar 2026 20:38:50 -0700 Subject: [PATCH 3/6] Fix use-after-free in txn_box Config destructor Move _arena declaration before _roots so the arena outlives the directives that reference its memory. C++ destroys members in reverse declaration order, so _arena must be declared first. --- .../experimental/txn_box/plugin/include/txn_box/Config.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/experimental/txn_box/plugin/include/txn_box/Config.h b/plugins/experimental/txn_box/plugin/include/txn_box/Config.h index 277fd09e186..6461b4c5b5d 100644 --- a/plugins/experimental/txn_box/plugin/include/txn_box/Config.h +++ b/plugins/experimental/txn_box/plugin/include/txn_box/Config.h @@ -559,6 +559,11 @@ class Config /// Set of named configuration storage objects. std::unordered_map, std::hash> _named_objects; + /// For localizing data at a configuration level, primarily strings. + /// Declared before _roots so it is destroyed after _roots, since directives + /// in _roots reference memory allocated from this arena. + swoc::MemArena _arena; + /// Top level directives for each hook. Always invoked. std::array, std::tuple_size::value> _roots; @@ -566,9 +571,6 @@ class Config /// directive load, if needed. This includes the top level directives. std::array::value> _directive_count{0}; - /// For localizing data at a configuration level, primarily strings. - swoc::MemArena _arena; - /// Additional clean up to perform when @a this is destroyed. swoc::IntrusiveDList _finalizers; From 22bff30e9f6a1b320157dca681893810efc32b1a Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Sun, 22 Mar 2026 20:38:52 -0700 Subject: [PATCH 4/6] Fix memory leak in slice plugin Free urlstr before early return on invalid content length. The string was allocated by TSHttpTxnEffectiveUrlStringGet but leaked on the error path. --- plugins/slice/server.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/slice/server.cc b/plugins/slice/server.cc index f723af38d0f..3ef6be9c7cc 100644 --- a/plugins/slice/server.cc +++ b/plugins/slice/server.cc @@ -96,6 +96,7 @@ update_object_size(TSHttpTxn txnp, int64_t size, Config &config) if (urlstr != nullptr) { if (size <= 0) { DEBUG_LOG("Ignoring invalid content length for %.*s: %" PRId64, urllen, urlstr, size); + TSfree(urlstr); return; } From f362f328c859fd5be3db002f8dc4cb4a4e0b20f4 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Sun, 22 Mar 2026 20:38:55 -0700 Subject: [PATCH 5/6] Fix use-after-free in async_engine test plugin Save writefd value before freeing its backing memory with OPENSSL_free, then use the saved value for close() and the debug message. --- tests/tools/plugins/async_engine.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/tools/plugins/async_engine.c b/tests/tools/plugins/async_engine.c index 80322934494..4624f20dd1d 100644 --- a/tests/tools/plugins/async_engine.c +++ b/tests/tools/plugins/async_engine.c @@ -173,11 +173,12 @@ async_destroy(ENGINE *e ATS_UNUSED) static void wait_cleanup(ASYNC_WAIT_CTX *ctx ATS_UNUSED, const void *key ATS_UNUSED, OSSL_ASYNC_FD readfd, void *pvwritefd) { - OSSL_ASYNC_FD *pwritefd = (OSSL_ASYNC_FD *)pvwritefd; + OSSL_ASYNC_FD *pwritefd = (OSSL_ASYNC_FD *)pvwritefd; + OSSL_ASYNC_FD writefd_v = *pwritefd; close(readfd); - close(*((OSSL_ASYNC_FD *)pwritefd)); + close(writefd_v); OPENSSL_free(pwritefd); - fprintf(stderr, "Cleanup %d and %d\n", readfd, *pwritefd); + fprintf(stderr, "Cleanup %d and %d\n", readfd, writefd_v); } #define DUMMY_CHAR 'X' From 593693ab0d39ecc85fff524e6581613cc887afcb Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Tue, 24 Mar 2026 07:26:03 -0700 Subject: [PATCH 6/6] Fix JA4 type mismatch in handle_read_request_hdr TSUserArgSet stores a JA4_data* but handle_read_request_hdr retrieved it as std::string*. This worked by accident since fingerprint is the first member, but is technically undefined behavior. Cast to JA4_data* consistently and access the fingerprint member explicitly. --- plugins/experimental/ja4_fingerprint/plugin.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/experimental/ja4_fingerprint/plugin.cc b/plugins/experimental/ja4_fingerprint/plugin.cc index 1b109716435..fc3768997a6 100644 --- a/plugins/experimental/ja4_fingerprint/plugin.cc +++ b/plugins/experimental/ja4_fingerprint/plugin.cc @@ -358,9 +358,9 @@ handle_read_request_hdr(TSCont cont, TSEvent event, void *edata) return TS_SUCCESS; } - std::string *fingerprint{static_cast(TSUserArgGet(vconn, *get_user_arg_index()))}; - if (fingerprint) { - append_JA4_headers(cont, txnp, fingerprint); + JA4_data *data{static_cast(TSUserArgGet(vconn, *get_user_arg_index()))}; + if (data) { + append_JA4_headers(cont, txnp, &data->fingerprint); } else { Dbg(dbg_ctl, "No JA4 fingerprint attached to vconn!"); }