From aa18c3ed4ab4d01ffd40da0c8c68dae375c74489 Mon Sep 17 00:00:00 2001 From: Marcin Maliszkiewicz Date: Tue, 21 Apr 2026 17:12:48 +0200 Subject: [PATCH] table_helper: fix use-after-free on prepared-statement invalidation insert() held no local strong ref to the prepared modification_statement across the suspension in execute(). On a single shard: 1. Fiber A suspends inside _insert_stmt->execute(). 2. DROP TABLE / DROP KEYSPACE on the target, or LRU eviction, removes the prepared_statements_cache entry, releasing its strong ref. 3. Fiber B re-enters cache_table_info(), sees _prepared_stmt (checked_weak_ptr) invalidated, and runs _insert_stmt = nullptr, releasing the last strong ref. The modification_statement is freed. 4. Fiber A resumes inside execute() and touches freed *this. Pin strong ref to _insert_stmt locally before the suspension. --- table_helper.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/table_helper.cc b/table_helper.cc index b96e97c977..5fb1b5788f 100644 --- a/table_helper.cc +++ b/table_helper.cc @@ -152,9 +152,13 @@ future<> table_helper::insert(cql3::query_processor& qp, service::migration_mana break; } } + // Pin a strong ref locally: while we suspend in execute(), a concurrent + // insert() on this shard may reset _insert_stmt to nullptr if the + // prepared_statements_cache entry gets invalidated, freeing the object. + auto stmt = _insert_stmt; auto opts = opt_maker(); opts.prepare(_prepared_stmt->bound_names); - co_await _insert_stmt->execute(qp, qs, opts, std::nullopt); + co_await stmt->execute(qp, qs, opts, std::nullopt); } future<> table_helper::setup_keyspace(cql3::query_processor& qp, service::migration_manager& mm, std::string_view keyspace_name, sstring replication_strategy_name,