Skip to content

Conversation

augusto2112
Copy link
Contributor

This fixes a potential use after free where
ModuleList::RemoveSharedModuleIfOrphaned ->
SharedModuleList::RemoveIfOrphaned -> SharedModuleList::RemoveFromMap would potentially dereference a freed pointer. This fixes it by not calling ModuleList::RemoveSharedModuleIfOrphaned at all if the pointer was just freed.

This fixes a potential use after free where
ModuleList::RemoveSharedModuleIfOrphaned ->
SharedModuleList::RemoveIfOrphaned -> SharedModuleList::RemoveFromMap
would potentially dereference a freed pointer. This fixes it by not
calling ModuleList::RemoveSharedModuleIfOrphaned at all if the pointer
was just freed.
@llvmbot llvmbot added the lldb label Aug 26, 2025
@augusto2112 augusto2112 removed the request for review from JDevlieghere August 26, 2025 00:19
@llvmbot
Copy link
Member

llvmbot commented Aug 26, 2025

@llvm/pr-subscribers-lldb

Author: Augusto Noronha (augusto2112)

Changes

This fixes a potential use after free where
ModuleList::RemoveSharedModuleIfOrphaned ->
SharedModuleList::RemoveIfOrphaned -> SharedModuleList::RemoveFromMap would potentially dereference a freed pointer. This fixes it by not calling ModuleList::RemoveSharedModuleIfOrphaned at all if the pointer was just freed.


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

1 Files Affected:

  • (modified) lldb/source/Target/Target.cpp (+4-1)
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index fa98c24606492..d5406d88ec80a 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -2564,9 +2564,12 @@ ModuleSP Target::GetOrCreateModule(const ModuleSpec &orig_module_spec,
           m_images.Append(module_sp, notify);
 
         for (ModuleSP &old_module_sp : replaced_modules) {
+          auto use_count = old_module_sp.use_count();
           Module *old_module_ptr = old_module_sp.get();
           old_module_sp.reset();
-          ModuleList::RemoveSharedModuleIfOrphaned(old_module_ptr);
+          // If the use count was one, this was not in the shared module list.
+          if (use_count > 1)
+            ModuleList::RemoveSharedModuleIfOrphaned(old_module_ptr);
         }
       } else
         module_sp.reset();

@augusto2112
Copy link
Contributor Author

@JDevlieghere @felipepiovezan with the changes I made to SharedModuleList, I accidentally introduced a use after free, since the caller of ModuleList::RemoveSharedModuleIfOrphaned might potentially call it with an already freed pointer. This used to not be a problem because ModuleList::RemoveIfOrphaned would just compare the pointer with what was on the list, and not actually dereference it. I feel like the solution here is to simply not pass in an already potentially freed pointer. Let me know what you think.

Comment on lines +2567 to +2572
auto use_count = old_module_sp.use_count();
Module *old_module_ptr = old_module_sp.get();
old_module_sp.reset();
ModuleList::RemoveSharedModuleIfOrphaned(old_module_ptr);
// If the use count was one, this was not in the shared module list.
if (use_count > 1)
ModuleList::RemoveSharedModuleIfOrphaned(old_module_ptr);
Copy link
Member

Choose a reason for hiding this comment

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

Why does RemoveSharedModuleIfOrphaned take a raw pointer and not the ModuleSP? If it did, then we could just check if the SP is valid (like Remove does) and avoid yet another place where we check the use count.

Copy link
Member

Choose a reason for hiding this comment

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

I realize that this isn't enough to address the issue. But I think this shows what a bad idea it is to mix raw pointers, shared pointer references and references.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If RemoveSharedModuleIfOrphaned takes a shared pointer we have to account for the caller also having a shared pointer to decide whether the module is an orphan or not, which would probably be more confusing.

@JDevlieghere
Copy link
Member

I'm confused where the "free" in this "use-after-free" is actually occurring. RemoveIfOrphaned takes a raw pointer, checks the pointer and removes it from the map before actually removing it from the module list. So for this to cause a crash, the module already has to be pointing to invalid memory. Is the problem the fact that the loop is iterating over modules while holding a a reference to the SP and not bumping the use count?

@augusto2112
Copy link
Contributor Author

I'm confused where the "free" in this "use-after-free" is actually occurring. RemoveIfOrphaned takes a raw pointer, checks the pointer and removes it from the map before actually removing it from the module list. So for this to cause a crash, the module already has to be pointing to invalid memory. Is the problem the fact that the loop is iterating over modules while holding a a reference to the SP and not bumping the use count?

The caller of ModuleList::RemoveSharedModuleIfOrphaned first gets the raw pointer, then resets the shared pointer, then calls ModuleList::RemoveSharedModuleIfOrphaned. If that shared pointer was the only one to that memory address, the underlying pointer is now invalid.

Old code:

        for (ModuleSP &old_module_sp : replaced_modules) {
          auto use_count = old_module_sp.use_count();
          Module *old_module_ptr = old_module_sp.get();
          old_module_sp.reset();
          ModuleList::RemoveSharedModuleIfOrphaned(old_module_ptr);

SharedModuleList::RemoveFromMap will then try to read the filespec from that invalid pointer, which is the use after free.

@augusto2112
Copy link
Contributor Author

@JDevlieghere @felipepiovezan any suggestions on this, or can I merge it as is?

The other way to do this would be to allow already freed pointers, document it, and be careful to never dereference it. That feels weird and wasteful though.

I guess a third possibility is add a new function which takes a reference to the shared pointer (haha), have it check the count, free the pointer from shared pointer and remove the orphans if the reference count > 1.

@JDevlieghere
Copy link
Member

Can we solve this by handing down a weak pointer to the module? That way we can check if the ref count has hit zero.

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