[Lldb-commits] [lldb] 0d91c10 - [lldb] Fix use-after-free in jThreadsInfo stop info cache (#223488)
via lldb-commits
lldb-commits at lists.llvm.org
Mon Sep 14 18:08:23 PDT 2026
Author: Jonas Devlieghere
Date: 2026-09-14T18:08:17-07:00
New Revision: 0d91c104dde79e5f4467c90cf109d16aabe45c99
URL: https://github.com/llvm/llvm-project/commit/0d91c104dde79e5f4467c90cf109d16aabe45c99
DIFF: https://github.com/llvm/llvm-project/commit/0d91c104dde79e5f4467c90cf109d16aabe45c99.diff
LOG: [lldb] Fix use-after-free in jThreadsInfo stop info cache (#223488)
WillPublicStop iterates over the array borrowed from m_jthreadsinfo_sp
while WillResume concurrently resets the pointer on another thread,
freeing the array during the walk.
Protect the cached stop info with a mutex and copy the shared pointer
out before processing. Also modernize m_shared_cache_info to Guarded.
rdar://186776438
Added:
Modified:
lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
Removed:
################################################################################
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index ccc7ed26a8c9b..393f3dfb6e4b5 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -315,9 +315,8 @@ ProcessGDBRemote::ProcessGDBRemote(lldb::TargetSP target_sp,
m_async_listener_sp(
Listener::MakeListener("lldb.process.gdb-remote.async-listener")),
m_async_thread_state_mutex(), m_thread_ids(), m_thread_pcs(),
- m_jstopinfo_sp(), m_jthreadsinfo_sp(), m_shared_cache_info_sp(),
- m_shared_cache_info_mutex(), m_continue_c_tids(), m_continue_C_tids(),
- m_continue_s_tids(), m_continue_S_tids(), m_max_memory_size(0),
+ m_continue_c_tids(), m_continue_C_tids(), m_continue_s_tids(),
+ m_continue_S_tids(), m_max_memory_size(0),
m_remote_stub_max_memory_size(0), m_addr_to_mmap_size(),
m_thread_create_bp_sp(), m_waiting_for_attach(false), m_command_sp(),
m_breakpoint_pc_offset(0), m_initial_tid(LLDB_INVALID_THREAD_ID),
@@ -1358,9 +1357,9 @@ Status ProcessGDBRemote::WillResume() {
m_continue_C_tids.clear();
m_continue_s_tids.clear();
m_continue_S_tids.clear();
- m_jstopinfo_sp.reset();
- m_jthreadsinfo_sp.reset();
- m_shared_cache_info_sp.reset();
+ m_jstopinfo.Lock()->reset();
+ m_jthreadsinfo.Lock()->reset();
+ m_shared_cache_info.Lock()->reset();
return Status();
}
@@ -1685,9 +1684,10 @@ size_t ProcessGDBRemote::UpdateThreadPCsFromStopReplyThreadsValue(
bool ProcessGDBRemote::UpdateThreadIDList() {
std::lock_guard<std::recursive_mutex> guard(m_thread_list_real.GetMutex());
- if (m_jthreadsinfo_sp) {
+ StructuredData::ObjectSP threads_info_sp = *m_jthreadsinfo.Lock();
+ if (threads_info_sp) {
// If we have the JSON threads info, we can get the thread list from that
- StructuredData::Array *thread_infos = m_jthreadsinfo_sp->GetAsArray();
+ StructuredData::Array *thread_infos = threads_info_sp->GetAsArray();
if (thread_infos && thread_infos->GetSize() > 0) {
m_thread_ids.clear();
m_thread_pcs.clear();
@@ -1839,17 +1839,19 @@ bool ProcessGDBRemote::GetThreadStopInfoFromJSON(
bool ProcessGDBRemote::CalculateThreadStopInfo(ThreadGDBRemote *thread) {
// See if we got thread stop infos for all threads via the "jThreadsInfo"
// packet (we're at a public stop).
- if (GetThreadStopInfoFromJSON(thread, m_jthreadsinfo_sp))
+ StructuredData::ObjectSP threads_info_sp = *m_jthreadsinfo.Lock();
+ if (GetThreadStopInfoFromJSON(thread, threads_info_sp))
return true;
// See if the stop-reply packet (T05 etc) included a `jstopinfo` key
// with a mach exception description for any thread that has a stop reason.
- if (m_jstopinfo_sp) {
+ StructuredData::ObjectSP stop_info_sp = *m_jstopinfo.Lock();
+ if (stop_info_sp) {
// Any thread not described in `jstopinfo` has no stop reason.
// If a no-stop-reason thread is stopped at a breakpoint site (but
// hasn't yet hit the breakpoint instruction), note that in the
// Thread state so we will hit the breakpoint when we resume execution.
- if (!GetThreadStopInfoFromJSON(thread, m_jstopinfo_sp)) {
+ if (!GetThreadStopInfoFromJSON(thread, stop_info_sp)) {
addr_t pc = thread->GetRegisterContext()->GetPC();
BreakpointSiteSP bp_site_sp =
thread->GetProcess()->GetBreakpointSiteList().FindByAddress(pc);
@@ -2505,7 +2507,7 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
// This JSON contains thread IDs and thread stop info for all threads.
// It doesn't contain expedited registers, memory or queue info.
- m_jstopinfo_sp = StructuredData::ParseJSON(json);
+ *m_jstopinfo.Lock() = StructuredData::ParseJSON(json);
} else if (key.compare("hexname") == 0) {
StringExtractor name_extractor(value);
// Now convert the HEX bytes into a string value
@@ -2897,12 +2899,13 @@ void ProcessGDBRemote::WillPublicStop() {
// runtime queue information (iOS and MacOSX only), and more. Expediting
// memory will help stack backtracing be much faster. Expediting registers
// will make sure we don't have to read the thread registers for GPRs.
- m_jthreadsinfo_sp = m_gdb_comm.GetThreadsInfo();
+ StructuredData::ObjectSP threads_info_sp = m_gdb_comm.GetThreadsInfo();
+ *m_jthreadsinfo.Lock() = threads_info_sp;
- if (m_jthreadsinfo_sp) {
+ if (threads_info_sp) {
// Now set the stop info for each thread and also expedite any registers
// and memory that was in the jThreadsInfo response.
- StructuredData::Array *thread_infos = m_jthreadsinfo_sp->GetAsArray();
+ StructuredData::Array *thread_infos = threads_info_sp->GetAsArray();
if (thread_infos) {
const size_t n = thread_infos->GetSize();
for (size_t i = 0; i < n; ++i) {
@@ -4787,11 +4790,13 @@ StructuredData::ObjectSP ProcessGDBRemote::GetDynamicLoaderProcessState() {
}
StructuredData::ObjectSP ProcessGDBRemote::GetSharedCacheInfo() {
- std::lock_guard<std::mutex> guard(m_shared_cache_info_mutex);
+ // Held across the query so a second caller waits for the answer instead of
+ // sending the packet again.
+ auto shared_cache_info = m_shared_cache_info.Lock();
StructuredData::ObjectSP args_dict(new StructuredData::Dictionary());
- if (m_shared_cache_info_sp || !m_gdb_comm.GetSharedCacheInfoSupported())
- return m_shared_cache_info_sp;
+ if (*shared_cache_info || !m_gdb_comm.GetSharedCacheInfoSupported())
+ return *shared_cache_info;
StreamString packet;
packet << "jGetSharedCacheInfo:";
@@ -4836,10 +4841,10 @@ StructuredData::ObjectSP ProcessGDBRemote::GetSharedCacheInfo() {
HostInfo::SharedCacheIndexFiles(sc_path, uuid, sc_mode);
}
}
- m_shared_cache_info_sp = response_sp;
+ *shared_cache_info = response_sp;
}
}
- return m_shared_cache_info_sp;
+ return *shared_cache_info;
}
Status ProcessGDBRemote::ConfigureStructuredData(
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index 7a7857a12afbe..d23af7f41849f 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -29,6 +29,7 @@
#include "lldb/Utility/Broadcaster.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/GDBRemote.h"
+#include "lldb/Utility/Locked.h"
#include "lldb/Utility/RegisterType.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StreamString.h"
@@ -305,14 +306,19 @@ class ProcessGDBRemote : public Process,
tid_collection m_thread_ids; // Thread IDs for all threads. This list gets
// updated after stopping
std::vector<lldb::addr_t> m_thread_pcs; // PC values for all the threads.
- StructuredData::ObjectSP m_jstopinfo_sp; // Stop info only for any threads
- // that have valid stop infos
- StructuredData::ObjectSP m_jthreadsinfo_sp; // Full stop info, expedited
- // registers and memory for all
- // threads if "jThreadsInfo"
- // packet is supported
- StructuredData::ObjectSP m_shared_cache_info_sp;
- std::mutex m_shared_cache_info_mutex;
+ /// Stop info caches filled at a stop and reset by WillResume, which runs on
+ /// another thread. Hold the lock only long enough to copy the shared pointer
+ /// out. The copy keeps the JSON alive past a reset, and reading the JSON
+ /// takes the real thread list mutex, which a reader may already hold.
+ /// @{
+ /// Stop info for the threads that have one, from a stop reply's "jstopinfo".
+ Guarded<StructuredData::ObjectSP, std::mutex> m_jstopinfo;
+ /// Full stop info, expedited registers and memory for all threads, from the
+ /// "jThreadsInfo" packet.
+ Guarded<StructuredData::ObjectSP, std::mutex> m_jthreadsinfo;
+ /// @}
+ /// Shared cache image list from the "jGetSharedCacheInfo" packet.
+ Guarded<StructuredData::ObjectSP, std::mutex> m_shared_cache_info;
tid_collection m_continue_c_tids; // 'c' for continue
tid_sig_collection m_continue_C_tids; // 'C' for continue with signal
tid_collection m_continue_s_tids; // 's' for step
More information about the lldb-commits
mailing list