[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