Skip to content

Commit f1b6bd4

Browse files
committed
[lsan] Fix deadlock in dl_iterate_phdr.
Summary: Do not grab the allocator lock before calling dl_iterate_phdr. This may cause a lock order inversion with (valid) user code that uses malloc inside a dl_iterate_phdr callback. Reviewers: vitalybuka, hctim Subscribers: jfb, #sanitizers, llvm-commits Tags: #sanitizers, #llvm Differential Revision: https://reviews.llvm.org/D67738 llvm-svn: 372348
1 parent e6b2164 commit f1b6bd4

File tree

5 files changed

+69
-12
lines changed

5 files changed

+69
-12
lines changed

compiler-rt/lib/lsan/lsan_common.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -570,11 +570,7 @@ static bool CheckForLeaks() {
570570
EnsureMainThreadIDIsCorrect();
571571
CheckForLeaksParam param;
572572
param.success = false;
573-
LockThreadRegistry();
574-
LockAllocator();
575-
DoStopTheWorld(CheckForLeaksCallback, &param);
576-
UnlockAllocator();
577-
UnlockThreadRegistry();
573+
LockStuffAndStopTheWorld(CheckForLeaksCallback, &param);
578574

579575
if (!param.success) {
580576
Report("LeakSanitizer has encountered a fatal error.\n");

compiler-rt/lib/lsan/lsan_common.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,9 @@ struct RootRegion {
129129
InternalMmapVector<RootRegion> const *GetRootRegions();
130130
void ScanRootRegion(Frontier *frontier, RootRegion const &region,
131131
uptr region_begin, uptr region_end, bool is_readable);
132-
// Run stoptheworld while holding any platform-specific locks.
133-
void DoStopTheWorld(StopTheWorldCallback callback, void* argument);
132+
// Run stoptheworld while holding any platform-specific locks, as well as the
133+
// allocator and thread registry locks.
134+
void LockStuffAndStopTheWorld(StopTheWorldCallback callback, void* argument);
134135

135136
void ScanRangeForPointers(uptr begin, uptr end,
136137
Frontier *frontier,

compiler-rt/lib/lsan/lsan_common_linux.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,14 @@ void HandleLeaks() {
115115
if (common_flags()->exitcode) Die();
116116
}
117117

118-
static int DoStopTheWorldCallback(struct dl_phdr_info *info, size_t size,
119-
void *data) {
118+
static int LockStuffAndStopTheWorldCallback(struct dl_phdr_info *info,
119+
size_t size, void *data) {
120+
LockThreadRegistry();
121+
LockAllocator();
120122
DoStopTheWorldParam *param = reinterpret_cast<DoStopTheWorldParam *>(data);
121123
StopTheWorld(param->callback, param->argument);
124+
UnlockAllocator();
125+
UnlockThreadRegistry();
122126
return 1;
123127
}
124128

@@ -130,9 +134,9 @@ static int DoStopTheWorldCallback(struct dl_phdr_info *info, size_t size,
130134
// while holding the libdl lock in the parent thread, we can safely reenter it
131135
// in the tracer. The solution is to run stoptheworld from a dl_iterate_phdr()
132136
// callback in the parent thread.
133-
void DoStopTheWorld(StopTheWorldCallback callback, void *argument) {
137+
void LockStuffAndStopTheWorld(StopTheWorldCallback callback, void *argument) {
134138
DoStopTheWorldParam param = {callback, argument};
135-
dl_iterate_phdr(DoStopTheWorldCallback, &param);
139+
dl_iterate_phdr(LockStuffAndStopTheWorldCallback, &param);
136140
}
137141

138142
} // namespace __lsan

compiler-rt/lib/lsan/lsan_common_mac.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,12 @@ void ProcessPlatformSpecificAllocations(Frontier *frontier) {
193193
// causes rare race conditions.
194194
void HandleLeaks() {}
195195

196-
void DoStopTheWorld(StopTheWorldCallback callback, void *argument) {
196+
void LockStuffAndStopTheWorld(StopTheWorldCallback callback, void *argument) {
197+
LockThreadRegistry();
198+
LockAllocator();
197199
StopTheWorld(callback, argument);
200+
UnlockAllocator();
201+
UnlockThreadRegistry();
198202
}
199203

200204
} // namespace __lsan
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Regression test for a deadlock in leak detection,
2+
// where lsan would call dl_iterate_phdr while holding the allocator lock.
3+
// RUN: %clangxx_lsan %s -o %t && %run %t
4+
5+
#include <link.h>
6+
#include <mutex>
7+
#include <stdlib.h>
8+
#include <thread>
9+
#include <unistd.h>
10+
11+
std::mutex in, out;
12+
13+
int Callback(struct dl_phdr_info *info, size_t size, void *data) {
14+
for (int step = 0; step < 50; ++step) {
15+
void *p[1000];
16+
for (int i = 0; i < 1000; ++i)
17+
p[i] = malloc(10 * i);
18+
19+
if (step == 0)
20+
in.unlock();
21+
22+
for (int i = 0; i < 1000; ++i)
23+
free(p[i]);
24+
}
25+
out.unlock();
26+
return 1; // just once
27+
}
28+
29+
void Watchdog() {
30+
// This is just a fail-safe to turn a deadlock (in case the bug reappears)
31+
// into a (slow) test failure.
32+
usleep(10000000);
33+
if (!out.try_lock()) {
34+
write(2, "DEADLOCK\n", 9);
35+
exit(1);
36+
}
37+
}
38+
39+
int main() {
40+
in.lock();
41+
out.lock();
42+
43+
std::thread t([] { dl_iterate_phdr(Callback, nullptr); });
44+
t.detach();
45+
46+
std::thread w(Watchdog);
47+
w.detach();
48+
49+
// Wait for the malloc thread to preheat, then start leak detection (on exit)
50+
in.lock();
51+
return 0;
52+
}

0 commit comments

Comments
 (0)