Skip to content

Commit 1992f1e

Browse files
jiminghamaokblast
authored andcommitted
Fix a potential use-after-free in StopInfoBreakpoint. (llvm#163471)
StopInfoBreakpoint keeps a BreakpointLocationCollection for all the breakpoint locations at the BreakpointSite that was hit. It is also lives through the time a given thread is stopped, so there are plenty of opportunities for one of the owning breakpoints to get deleted. But BreakpointLocations don't keep their owner Breakpoints alive, so if the BreakpointLocationCollection can live past when some code gets a chance to delete an owner breakpoint, and then you ask that location for some breakpoint information, it will access freed memory. This wasn't a problem before PR llvm#158128 because the StopInfoBreakpoint just kept the BreakpointSite that was hit, and when you asked it questions, it relooked up that list. That was not great, however, because if you hit breakpoints 5 & 6, deleted 5 and then asked which breakpoints got hit, you would just get 6. For that and other reasons that PR changed to storing a BreakpointLocationCollection of the breakpoints that were hit. That's better from a UI perspective but caused this potential problem. I fix it by adding a variant of the BreakpointLocationCollection that also holds onto a shared pointer to the Breakpoints that own the locations that were hit, thus keeping them alive till the StopInfoBreakpoint goes away. This fixed the ASAN assertion. I also added a test that works harder to cause trouble by deleting breakpoints during a stop.
1 parent ad94f0a commit 1992f1e

File tree

6 files changed

+131
-6
lines changed

6 files changed

+131
-6
lines changed

lldb/include/lldb/Breakpoint/BreakpointLocationCollection.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef LLDB_BREAKPOINT_BREAKPOINTLOCATIONCOLLECTION_H
1010
#define LLDB_BREAKPOINT_BREAKPOINTLOCATIONCOLLECTION_H
1111

12+
#include <map>
1213
#include <mutex>
1314
#include <vector>
1415

@@ -19,7 +20,15 @@ namespace lldb_private {
1920

2021
class BreakpointLocationCollection {
2122
public:
22-
BreakpointLocationCollection();
23+
/// Breakpoint locations don't keep their breakpoint owners alive, so neither
24+
/// will a collection of breakpoint locations. However, if you need to
25+
/// use this collection in a context where some of the breakpoints whose
26+
/// locations are in the collection might get deleted during its lifespan,
27+
/// then you need to make sure the breakpoints don't get deleted out from
28+
/// under you. To do that, pass true for preserving, and so long as there is
29+
/// a location for a given breakpoint in the collection, the breakpoint will
30+
/// not get destroyed.
31+
BreakpointLocationCollection(bool preserving = false);
2332

2433
~BreakpointLocationCollection();
2534

@@ -164,6 +173,10 @@ class BreakpointLocationCollection {
164173

165174
collection m_break_loc_collection;
166175
mutable std::mutex m_collection_mutex;
176+
/// These are used if we're preserving breakpoints in this list:
177+
const bool m_preserving_bkpts = false;
178+
std::map<std::pair<lldb::break_id_t, lldb::break_id_t>, lldb::BreakpointSP>
179+
m_preserved_bps;
167180

168181
public:
169182
typedef llvm::iterator_range<collection::const_iterator>
@@ -172,7 +185,6 @@ class BreakpointLocationCollection {
172185
return BreakpointLocationCollectionIterable(m_break_loc_collection);
173186
}
174187
};
175-
176188
} // namespace lldb_private
177189

178190
#endif // LLDB_BREAKPOINT_BREAKPOINTLOCATIONCOLLECTION_H

lldb/source/Breakpoint/BreakpointLocationCollection.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ using namespace lldb;
1717
using namespace lldb_private;
1818

1919
// BreakpointLocationCollection constructor
20-
BreakpointLocationCollection::BreakpointLocationCollection() = default;
20+
BreakpointLocationCollection::BreakpointLocationCollection(bool preserving)
21+
: m_preserving_bkpts(preserving) {}
2122

2223
// Destructor
2324
BreakpointLocationCollection::~BreakpointLocationCollection() = default;
@@ -26,15 +27,35 @@ void BreakpointLocationCollection::Add(const BreakpointLocationSP &bp_loc) {
2627
std::lock_guard<std::mutex> guard(m_collection_mutex);
2728
BreakpointLocationSP old_bp_loc =
2829
FindByIDPair(bp_loc->GetBreakpoint().GetID(), bp_loc->GetID());
29-
if (!old_bp_loc.get())
30+
if (!old_bp_loc.get()) {
3031
m_break_loc_collection.push_back(bp_loc);
32+
if (m_preserving_bkpts) {
33+
lldb::break_id_t bp_loc_id = bp_loc->GetID();
34+
Breakpoint &bkpt = bp_loc->GetBreakpoint();
35+
lldb::break_id_t bp_id = bkpt.GetID();
36+
std::pair<lldb::break_id_t, lldb::break_id_t> key =
37+
std::make_pair(bp_id, bp_loc_id);
38+
auto entry = m_preserved_bps.find(key);
39+
if (entry == m_preserved_bps.end())
40+
m_preserved_bps.emplace(key, bkpt.shared_from_this());
41+
}
42+
}
3143
}
3244

3345
bool BreakpointLocationCollection::Remove(lldb::break_id_t bp_id,
3446
lldb::break_id_t bp_loc_id) {
3547
std::lock_guard<std::mutex> guard(m_collection_mutex);
3648
collection::iterator pos = GetIDPairIterator(bp_id, bp_loc_id); // Predicate
3749
if (pos != m_break_loc_collection.end()) {
50+
if (m_preserving_bkpts) {
51+
std::pair<lldb::break_id_t, lldb::break_id_t> key =
52+
std::make_pair(bp_id, bp_loc_id);
53+
auto entry = m_preserved_bps.find(key);
54+
if (entry == m_preserved_bps.end())
55+
assert(0 && "Breakpoint added to collection but not preserving map.");
56+
else
57+
m_preserved_bps.erase(entry);
58+
}
3859
m_break_loc_collection.erase(pos);
3960
return true;
4061
}

lldb/source/Target/StopInfo.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,24 @@ bool StopInfo::HasTargetRunSinceMe() {
8787
namespace lldb_private {
8888
class StopInfoBreakpoint : public StopInfo {
8989
public:
90+
// We use a "breakpoint preserving BreakpointLocationCollection because we
91+
// may need to hand out the "breakpoint hit" list as any point, potentially
92+
// after the breakpoint has been deleted. But we still need to refer to them.
9093
StopInfoBreakpoint(Thread &thread, break_id_t break_id)
9194
: StopInfo(thread, break_id), m_should_stop(false),
9295
m_should_stop_is_valid(false), m_should_perform_action(true),
9396
m_address(LLDB_INVALID_ADDRESS), m_break_id(LLDB_INVALID_BREAK_ID),
94-
m_was_all_internal(false), m_was_one_shot(false) {
97+
m_was_all_internal(false), m_was_one_shot(false),
98+
m_async_stopped_locs(true) {
9599
StoreBPInfo();
96100
}
97101

98102
StopInfoBreakpoint(Thread &thread, break_id_t break_id, bool should_stop)
99103
: StopInfo(thread, break_id), m_should_stop(should_stop),
100104
m_should_stop_is_valid(true), m_should_perform_action(true),
101105
m_address(LLDB_INVALID_ADDRESS), m_break_id(LLDB_INVALID_BREAK_ID),
102-
m_was_all_internal(false), m_was_one_shot(false) {
106+
m_was_all_internal(false), m_was_one_shot(false),
107+
m_async_stopped_locs(true) {
103108
StoreBPInfo();
104109
}
105110

@@ -699,6 +704,10 @@ class StopInfoBreakpoint : public StopInfo {
699704
lldb::break_id_t m_break_id;
700705
bool m_was_all_internal;
701706
bool m_was_one_shot;
707+
/// The StopInfoBreakpoint lives after the stop, and could get queried
708+
/// at any time so we need to make sure that it keeps the breakpoints for
709+
/// each of the locations it records alive while it is around. That's what
710+
/// The BreakpointPreservingLocationCollection does.
702711
BreakpointLocationCollection m_async_stopped_locs;
703712
};
704713

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
C_SOURCES := main.c
2+
CFLAGS_EXTRAS := -std=c99
3+
4+
include Makefile.rules
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""
2+
Make sure that deleting breakpoints in another breakpoint
3+
callback doesn't cause problems.
4+
"""
5+
6+
7+
import lldb
8+
import lldbsuite.test.lldbutil as lldbutil
9+
from lldbsuite.test.lldbtest import *
10+
11+
12+
class TestBreakpointDeletionInCallback(TestBase):
13+
NO_DEBUG_INFO_TESTCASE = True
14+
15+
def test_breakpoint_deletion_in_callback(self):
16+
self.build()
17+
self.main_source_file = lldb.SBFileSpec("main.c")
18+
self.delete_others_test()
19+
20+
def delete_others_test(self):
21+
"""You might use the test implementation in several ways, say so here."""
22+
23+
# This function starts a process, "a.out" by default, sets a source
24+
# breakpoint, runs to it, and returns the thread, process & target.
25+
# It optionally takes an SBLaunchOption argument if you want to pass
26+
# arguments or environment variables.
27+
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
28+
self, "Set a breakpoint here", self.main_source_file
29+
)
30+
31+
# Now set a breakpoint on "I did something" several times
32+
#
33+
bkpt_numbers = []
34+
for idx in range(0, 5):
35+
bkpt_numbers.append(
36+
lldbutil.run_break_set_by_source_regexp(self, "// Deletable location")
37+
)
38+
39+
# And add commands to the third one to delete two others:
40+
deleter = target.FindBreakpointByID(bkpt_numbers[2])
41+
self.assertTrue(deleter.IsValid(), "Deleter is a good breakpoint")
42+
commands = lldb.SBStringList()
43+
deleted_ids = [bkpt_numbers[0], bkpt_numbers[3]]
44+
for idx in deleted_ids:
45+
commands.AppendString(f"break delete {idx}")
46+
47+
deleter.SetCommandLineCommands(commands)
48+
49+
thread_list = lldbutil.continue_to_breakpoint(process, deleter)
50+
self.assertEqual(len(thread_list), 1)
51+
stop_data = thread.stop_reason_data
52+
# There are 5 breakpoints so 10 break_id, break_loc_id.
53+
self.assertEqual(len(stop_data), 10)
54+
# We should have been able to get break ID's and locations for all the
55+
# breakpoints that we originally hit, but some won't be around anymore:
56+
for idx in range(0, 5):
57+
bkpt_id = stop_data[idx * 2]
58+
print(f"{idx}: {bkpt_id}")
59+
self.assertIn(bkpt_id, bkpt_numbers, "Found breakpoints are right")
60+
loc_id = stop_data[idx * 2 + 1]
61+
self.assertEqual(loc_id, 1, "All breakpoints have one location")
62+
bkpt = target.FindBreakpointByID(bkpt_id)
63+
if bkpt_id in deleted_ids:
64+
# Looking these up should be an error:
65+
self.assertFalse(bkpt.IsValid(), "Deleted breakpoints are deleted")
66+
else:
67+
self.assertTrue(bkpt.IsValid(), "The rest are still valid")
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include <stdio.h>
2+
3+
int do_something(int input) {
4+
return input % 5; // Deletable location
5+
}
6+
7+
int main() {
8+
printf("Set a breakpoint here.\n");
9+
do_something(100);
10+
do_something(200);
11+
return 0;
12+
}

0 commit comments

Comments
 (0)