From 332d3c5e9a508c4f6bc9d1c208bc85cf7e64d9ff Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sat, 20 Jun 2026 12:06:10 +0000 Subject: [PATCH] ltc: serialize NodeRPC::Send under a mutex to fix RPC-client UAF NodeRPC holds a single shared m_http_request + m_stream and Send() drove them with no synchronization. The asio thread_pool worker path (clean_tracker -> ShareTracker::think -> score -> score-callback -> NodeRPC::getblockheader -> Send) issues a blocking RPC concurrently with the main thread's own RPC traffic on the same client. ASan (firewall-isolated sibling, BuildId 9a12b6bc) caught the resulting heap-use-after-free: while one thread is mid http::write serializing the request fields (read_iovec), the other enters Send -> prepare_payload -> set_content_length_impl -> delete_element and frees the Content-Length basic_fields element the first thread is still reading. In the non-ASan build this surfaces as the .157 SEGV-inside-malloc crash-loop during reorg/clean churn (the heap corruption was the downstream symptom). Guard the full write+read cycle with a per-client mutex. A single HTTP connection cannot interleave two request/response pairs anyway, so this adds no stall beyond what correctness already requires. Follow-up (separate change): keep think()/score() off the synchronous RPC-on-pool-thread path entirely (cache header height instead of a live getblockheader in the score callback). --- src/impl/ltc/coin/rpc.cpp | 1 + src/impl/ltc/coin/rpc.hpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/impl/ltc/coin/rpc.cpp b/src/impl/ltc/coin/rpc.cpp index 777259302..2588436a8 100644 --- a/src/impl/ltc/coin/rpc.cpp +++ b/src/impl/ltc/coin/rpc.cpp @@ -132,6 +132,7 @@ void NodeRPC::sync_reconnect() std::string NodeRPC::Send(const std::string &request) { + std::lock_guard _rpc_lock(m_rpc_mutex); // Retry once after synchronous reconnect on write/read failure for (int attempt = 0; attempt < 2; ++attempt) { diff --git a/src/impl/ltc/coin/rpc.hpp b/src/impl/ltc/coin/rpc.hpp index d4f7b5c1f..df1f64e6c 100644 --- a/src/impl/ltc/coin/rpc.hpp +++ b/src/impl/ltc/coin/rpc.hpp @@ -5,6 +5,7 @@ #include "node_interface.hpp" #include +#include #include #include @@ -38,6 +39,11 @@ class NodeRPC : public jsonrpccxx::IClientConnector beast::tcp_stream m_stream; boost::asio::ip::tcp::resolver m_resolver; http::request m_http_request; + // Serializes Send(): m_http_request + m_stream are NOT thread-safe. The asio + // thread_pool worker (clean_tracker->think->score->getblockheader) and the main + // thread both drive Send() on this single shared client; without this lock T0's + // prepare_payload() frees the Content-Length field element mid-write on T8 -> UAF. + std::mutex m_rpc_mutex; std::unique_ptr m_auth; jsonrpccxx::JsonRpcClient m_client;