Skip to content

Conversation

@JDevlieghere
Copy link
Member

This fixes a use-after-free in SymbolFileCTF. Previously, we would remove the underlying CTF type as soon as we resolved it. However, it's possible that we're still holding onto the CTF type while we're parsing a dependent type, like a modifier, resulting in a use-after-free. This patch addresses the issue by delaying the removal of the CTF type until the type is fully resolved.

I have a XNU kernel binary that reproduces the issue and confirmed that this solves the memory issue using ASan. However I haven't been able to craft types by hand that reproduce this issue for a test case.

rdar://156660866

This fixes a use-after-free in SymbolFileCTF. Previously, we would
remove the underlying CTF type as soon as we resolved it. However, it's
possible that we're still holding onto the CTF type while we're parsing
a dependent type, like a modifier, resulting in a use-after-free. This
patch addresses the issue by delaying the removal of the CTF type until
the type is fully resolved.

I have a XNU kernel binary that reproduces the issue and confirmed that
this solves the memory issue using ASan. However I haven't been able to
craft types by hand that reproduce this issue for a test case.

rdar://156660866
@llvmbot llvmbot added the lldb label Jul 31, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 31, 2025

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

Changes

This fixes a use-after-free in SymbolFileCTF. Previously, we would remove the underlying CTF type as soon as we resolved it. However, it's possible that we're still holding onto the CTF type while we're parsing a dependent type, like a modifier, resulting in a use-after-free. This patch addresses the issue by delaying the removal of the CTF type until the type is fully resolved.

I have a XNU kernel binary that reproduces the issue and confirmed that this solves the memory issue using ASan. However I haven't been able to craft types by hand that reproduce this issue for a test case.

rdar://156660866


Full diff: https://github.com/llvm/llvm-project/pull/151586.diff

1 Files Affected:

  • (modified) lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.cpp (+23-6)
diff --git a/lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.cpp b/lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.cpp
index 81c6731cafcd1..591fdede70c26 100644
--- a/lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.cpp
+++ b/lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.cpp
@@ -738,9 +738,29 @@ size_t SymbolFileCTF::ParseTypes(CompileUnit &cu) {
 
   LLDB_LOG(log, "Parsed {0} CTF types", m_ctf_types.size());
 
-  for (lldb::user_id_t uid = 1; uid < type_uid; ++uid)
+  for (lldb::user_id_t uid = 1; uid < type_uid; ++uid) {
     ResolveTypeUID(uid);
 
+    // Remove the CTF type because we don't need it anymore, except for record
+    // types which we may need to complete later.
+    auto ctf_type_it = m_ctf_types.find(uid);
+    if (ctf_type_it != m_ctf_types.end()) {
+      CTFType *ctf_type = ctf_type_it->second.get();
+      if (!llvm::isa<CTFRecord>(ctf_type))
+        m_ctf_types.erase(uid);
+    }
+  }
+
+#ifndef NDEBUG
+  // Verify that the only CTF types left at this point are record types.
+  for (auto &t : m_ctf_types) {
+    CTFType *ctf_type = t.second.get();
+    assert(ctf_type && "invalid type in m_ctf_types");
+    assert(llvm::isa<CTFRecord>(ctf_type) && "leaking non record type");
+  }
+
+#endif
+
   LLDB_LOG(log, "Created {0} CTF types", m_types.size());
 
   return m_types.size();
@@ -994,6 +1014,8 @@ lldb_private::Type *SymbolFileCTF::ResolveTypeUID(lldb::user_id_t type_uid) {
 
   CTFType *ctf_type = ctf_type_it->second.get();
   assert(ctf_type && "m_ctf_types should only contain valid CTF types");
+  assert(ctf_type->uid == type_uid &&
+         "CTF type UID doesn't match UID in m_ctf_types");
 
   Log *log = GetLog(LLDBLog::Symbols);
 
@@ -1015,11 +1037,6 @@ lldb_private::Type *SymbolFileCTF::ResolveTypeUID(lldb::user_id_t type_uid) {
 
   m_types[type_uid] = type_sp;
 
-  // Except for record types which we'll need to complete later, we don't need
-  // the CTF type anymore.
-  if (!isa<CTFRecord>(ctf_type))
-    m_ctf_types.erase(type_uid);
-
   return type_sp.get();
 }
 

Copy link
Member

@bulbazord bulbazord left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm missing something. Instead of removing it at the end of ResolveTypeUID, you remove it right after the call to ResolveTypeUID. What's the difference?

@JDevlieghere
Copy link
Member Author

I think I'm missing something. Instead of removing it at the end of ResolveTypeUID, you remove it right after the call to ResolveTypeUID. What's the difference?

I should've mentioned that this function is (indirectly) recursive, because that's totally fine (e.g. a struct having a pointer to itself). Here's the stack trace from ASan that illustrates this.

lldb_private::SymbolFileCTF::ResolveTypeUID(unsigned long long) SymbolFileCTF.cpp:1022
lldb_private::SymbolFileCTF::CreateModifier(lldb_private::CTFModifier const&) SymbolFileCTF.cpp:378
lldb_private::SymbolFileCTF::CreateType(lldb_private::CTFType*) SymbolFileCTF.cpp:583
lldb_private::SymbolFileCTF::ResolveTypeUID(unsigned long long) SymbolFileCTF.cpp:1001
lldb_private::SymbolFileCTF::CompleteType(lldb_private::CompilerType&) SymbolFileCTF.cpp:531
lldb_private::Type::ResolveCompilerType(lldb_private::Type::ResolveState) Type.cpp:733
lldb_private::Type::GetFullCompilerType() Type.cpp:773
lldb_private::SymbolFileCTF::CreateTypedef(lldb_private::CTFTypedef const&) SymbolFileCTF.cpp:421
lldb_private::SymbolFileCTF::CreateType(lldb_private::CTFType*) SymbolFileCTF.cpp:585
lldb_private::SymbolFileCTF::ResolveTypeUID(unsigned long long) SymbolFileCTF.cpp:1001

In the process of resolving the type, we have to create another type, which includes a type modifier of the original type.

Copy link
Member

@bulbazord bulbazord left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me. Thanks!

@JDevlieghere JDevlieghere merged commit e1d45b1 into llvm:main Jul 31, 2025
11 checks passed
@JDevlieghere JDevlieghere deleted the use-after-free-symbolfile-ctf branch July 31, 2025 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants