Skip to content

Commit 596d061

Browse files
committed
[GWP-ASan] enable/disable and fork support.
Summary: * Implement enable() and disable() in GWP-ASan. * Setup atfork handler. * Improve test harness sanity and re-enable GWP-ASan in Scudo. Scudo_standalone disables embedded GWP-ASan as necessary around fork(). Standalone GWP-ASan sets the atfork handler in init() if asked to. This requires a working malloc(), therefore GWP-ASan initialization in Scudo is delayed to the post-init callback. Test harness changes are about setting up a single global instance of the GWP-ASan allocator so that pthread_atfork() does not create dangling pointers. Test case shamelessly stolen from D72470. Reviewers: cryptoad, hctim, jfb Subscribers: mgorny, jfb, #sanitizers, llvm-commits Tags: #sanitizers, #llvm Differential Revision: https://reviews.llvm.org/D73294
1 parent da8bada commit 596d061

File tree

13 files changed

+238
-23
lines changed

13 files changed

+238
-23
lines changed

compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ void defaultPrintStackTrace(uintptr_t *Trace, size_t TraceLength,
5858

5959
// Gets the singleton implementation of this class. Thread-compatible until
6060
// init() is called, thread-safe afterwards.
61-
GuardedPoolAllocator *getSingleton() { return SingletonPtr; }
61+
GuardedPoolAllocator *GuardedPoolAllocator::getSingleton() {
62+
return SingletonPtr;
63+
}
6264

6365
void GuardedPoolAllocator::AllocationMetadata::RecordAllocation(
6466
uintptr_t AllocAddr, size_t AllocSize, options::Backtrace_t Backtrace) {
@@ -156,9 +158,9 @@ void GuardedPoolAllocator::init(const options::Options &Opts) {
156158
// Multiply the sample rate by 2 to give a good, fast approximation for (1 /
157159
// SampleRate) chance of sampling.
158160
if (Opts.SampleRate != 1)
159-
AdjustedSampleRate = static_cast<uint32_t>(Opts.SampleRate) * 2;
161+
AdjustedSampleRatePlusOne = static_cast<uint32_t>(Opts.SampleRate) * 2 + 1;
160162
else
161-
AdjustedSampleRate = 1;
163+
AdjustedSampleRatePlusOne = 2;
162164

163165
GuardedPagePool = reinterpret_cast<uintptr_t>(GuardedPoolMemory);
164166
GuardedPagePoolEnd =
@@ -169,6 +171,31 @@ void GuardedPoolAllocator::init(const options::Options &Opts) {
169171
// race to members if received during init().
170172
if (Opts.InstallSignalHandlers)
171173
installSignalHandlers();
174+
175+
if (Opts.InstallForkHandlers)
176+
installAtFork();
177+
}
178+
179+
void GuardedPoolAllocator::disable() { PoolMutex.lock(); }
180+
181+
void GuardedPoolAllocator::enable() { PoolMutex.unlock(); }
182+
183+
void GuardedPoolAllocator::uninitTestOnly() {
184+
if (GuardedPagePool) {
185+
unmapMemory(reinterpret_cast<void *>(GuardedPagePool),
186+
GuardedPagePoolEnd - GuardedPagePool);
187+
GuardedPagePool = 0;
188+
GuardedPagePoolEnd = 0;
189+
}
190+
if (Metadata) {
191+
unmapMemory(Metadata, MaxSimultaneousAllocations * sizeof(*Metadata));
192+
Metadata = nullptr;
193+
}
194+
if (FreeSlots) {
195+
unmapMemory(FreeSlots, MaxSimultaneousAllocations * sizeof(*FreeSlots));
196+
FreeSlots = nullptr;
197+
}
198+
uninstallSignalHandlers();
172199
}
173200

174201
void *GuardedPoolAllocator::allocate(size_t Size) {

compiler-rt/lib/gwp_asan/guarded_pool_allocator.h

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,22 @@ class GuardedPoolAllocator {
9898
// pool using the provided options. See options.inc for runtime configuration
9999
// options.
100100
void init(const options::Options &Opts);
101+
void uninitTestOnly();
102+
103+
void disable();
104+
void enable();
101105

102106
// Return whether the allocation should be randomly chosen for sampling.
103107
GWP_ASAN_ALWAYS_INLINE bool shouldSample() {
104108
// NextSampleCounter == 0 means we "should regenerate the counter".
105109
// == 1 means we "should sample this allocation".
110+
// AdjustedSampleRatePlusOne is designed to intentionally underflow. This
111+
// class must be valid when zero-initialised, and we wish to sample as
112+
// infrequently as possible when this is the case, hence we underflow to
113+
// UINT32_MAX.
106114
if (GWP_ASAN_UNLIKELY(ThreadLocals.NextSampleCounter == 0))
107115
ThreadLocals.NextSampleCounter =
108-
(getRandomUnsigned32() % AdjustedSampleRate) + 1;
116+
(getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1;
109117

110118
return GWP_ASAN_UNLIKELY(--ThreadLocals.NextSampleCounter == 0);
111119
}
@@ -114,7 +122,7 @@ class GuardedPoolAllocator {
114122
// is owned by this pool.
115123
GWP_ASAN_ALWAYS_INLINE bool pointerIsMine(const void *Ptr) const {
116124
uintptr_t P = reinterpret_cast<uintptr_t>(Ptr);
117-
return GuardedPagePool <= P && P < GuardedPagePoolEnd;
125+
return P < GuardedPagePoolEnd && GuardedPagePool <= P;
118126
}
119127

120128
// Allocate memory in a guarded slot, and return a pointer to the new
@@ -156,6 +164,7 @@ class GuardedPoolAllocator {
156164
// mappings, call mapMemory() followed by markReadWrite() on the returned
157165
// pointer.
158166
void *mapMemory(size_t Size) const;
167+
void unmapMemory(void *Addr, size_t Size) const;
159168
void markReadWrite(void *Ptr, size_t Size) const;
160169
void markInaccessible(void *Ptr, size_t Size) const;
161170

@@ -169,6 +178,7 @@ class GuardedPoolAllocator {
169178
// signal(), we have to use platform-specific signal handlers to obtain the
170179
// address that caused the SIGSEGV exception.
171180
static void installSignalHandlers();
181+
static void uninstallSignalHandlers();
172182

173183
// Returns the index of the slot that this pointer resides in. If the pointer
174184
// is not owned by this pool, the result is undefined.
@@ -210,6 +220,11 @@ class GuardedPoolAllocator {
210220

211221
void reportErrorInternal(uintptr_t AccessPtr, Error E);
212222

223+
static GuardedPoolAllocator *getSingleton();
224+
225+
// Install a pthread_atfork handler.
226+
void installAtFork();
227+
213228
// Cached page size for this system in bytes.
214229
size_t PageSize = 0;
215230

@@ -223,7 +238,7 @@ class GuardedPoolAllocator {
223238
size_t NumSampledAllocations = 0;
224239
// Pointer to the pool of guarded slots. Note that this points to the start of
225240
// the pool (which is a guard page), not a pointer to the first guarded page.
226-
uintptr_t GuardedPagePool = UINTPTR_MAX;
241+
uintptr_t GuardedPagePool = 0;
227242
uintptr_t GuardedPagePoolEnd = 0;
228243
// Pointer to the allocation metadata (allocation/deallocation stack traces),
229244
// if any.
@@ -250,7 +265,7 @@ class GuardedPoolAllocator {
250265
// where we would calculate modulo zero. This value is set UINT32_MAX, as when
251266
// GWP-ASan is disabled, we wish to never spend wasted cycles recalculating
252267
// the sample rate.
253-
uint32_t AdjustedSampleRate = UINT32_MAX;
268+
uint32_t AdjustedSampleRatePlusOne = 0;
254269

255270
// Pack the thread local variables into a struct to ensure that they're in
256271
// the same cache line for performance reasons. These are the most touched

compiler-rt/lib/gwp_asan/options.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,7 @@ GWP_ASAN_OPTION(
3939
"programs that install further signal handlers should make sure they do "
4040
"the same. Note, if the previously installed SIGSEGV handler is SIG_IGN, "
4141
"we terminate the process after dumping the error report.")
42+
43+
GWP_ASAN_OPTION(bool, InstallForkHandlers, true,
44+
"Install GWP-ASan atfork handlers to acquire internal locks "
45+
"before fork and release them after.")

compiler-rt/lib/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88

99
#include "gwp_asan/guarded_pool_allocator.h"
1010

11-
#include <stdlib.h>
1211
#include <errno.h>
1312
#include <signal.h>
13+
#include <stdlib.h>
14+
#include <string.h>
1415
#include <sys/mman.h>
1516
#include <sys/syscall.h>
1617
#include <sys/types.h>
@@ -30,6 +31,16 @@ void *GuardedPoolAllocator::mapMemory(size_t Size) const {
3031
return Ptr;
3132
}
3233

34+
void GuardedPoolAllocator::unmapMemory(void *Addr, size_t Size) const {
35+
int Res = munmap(Addr, Size);
36+
37+
if (Res != 0) {
38+
Printf("Failed to unmap guarded pool allocator memory, errno: %d\n", errno);
39+
Printf(" unmmap(%p, %zu, ...) failed.\n", Addr, Size);
40+
exit(EXIT_FAILURE);
41+
}
42+
}
43+
3344
void GuardedPoolAllocator::markReadWrite(void *Ptr, size_t Size) const {
3445
if (mprotect(Ptr, Size, PROT_READ | PROT_WRITE) != 0) {
3546
Printf("Failed to set guarded pool allocator memory at as RW, errno: %d\n",
@@ -58,6 +69,7 @@ size_t GuardedPoolAllocator::getPlatformPageSize() {
5869
}
5970

6071
struct sigaction PreviousHandler;
72+
bool SignalHandlerInstalled;
6173

6274
static void sigSegvHandler(int sig, siginfo_t *info, void *ucontext) {
6375
gwp_asan::GuardedPoolAllocator::reportError(
@@ -78,11 +90,31 @@ static void sigSegvHandler(int sig, siginfo_t *info, void *ucontext) {
7890
}
7991
}
8092

93+
void GuardedPoolAllocator::installAtFork() {
94+
auto Disable = []() {
95+
if (auto *S = getSingleton())
96+
S->disable();
97+
};
98+
auto Enable = []() {
99+
if (auto *S = getSingleton())
100+
S->enable();
101+
};
102+
pthread_atfork(Disable, Enable, Enable);
103+
}
104+
81105
void GuardedPoolAllocator::installSignalHandlers() {
82106
struct sigaction Action;
83107
Action.sa_sigaction = sigSegvHandler;
84108
Action.sa_flags = SA_SIGINFO;
85109
sigaction(SIGSEGV, &Action, &PreviousHandler);
110+
SignalHandlerInstalled = true;
111+
}
112+
113+
void GuardedPoolAllocator::uninstallSignalHandlers() {
114+
if (SignalHandlerInstalled) {
115+
sigaction(SIGSEGV, &PreviousHandler, nullptr);
116+
SignalHandlerInstalled = false;
117+
}
86118
}
87119

88120
uint64_t GuardedPoolAllocator::getThreadID() {

compiler-rt/lib/gwp_asan/tests/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ set(GWP_ASAN_UNITTESTS
1717
driver.cpp
1818
mutex_test.cpp
1919
slot_reuse.cpp
20-
thread_contention.cpp)
20+
thread_contention.cpp
21+
harness.cpp
22+
enable_disable.cpp)
2123

2224
set(GWP_ASAN_UNIT_TEST_HEADERS
2325
${GWP_ASAN_HEADERS}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//===-- enable_disable.cpp --------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "gwp_asan/tests/harness.h"
10+
11+
constexpr size_t Size = 100;
12+
13+
TEST_F(DefaultGuardedPoolAllocator, Fork) {
14+
void *P;
15+
pid_t Pid = fork();
16+
EXPECT_GE(Pid, 0);
17+
if (Pid == 0) {
18+
P = GPA.allocate(Size);
19+
EXPECT_NE(P, nullptr);
20+
memset(P, 0x42, Size);
21+
GPA.deallocate(P);
22+
_exit(0);
23+
}
24+
waitpid(Pid, nullptr, 0);
25+
P = GPA.allocate(Size);
26+
EXPECT_NE(P, nullptr);
27+
memset(P, 0x42, Size);
28+
GPA.deallocate(P);
29+
30+
// fork should stall if the allocator has been disabled.
31+
EXPECT_DEATH(
32+
{
33+
GPA.disable();
34+
alarm(1);
35+
Pid = fork();
36+
EXPECT_GE(Pid, 0);
37+
},
38+
"");
39+
}
40+
41+
namespace {
42+
pthread_mutex_t Mutex;
43+
pthread_cond_t Conditional = PTHREAD_COND_INITIALIZER;
44+
bool ThreadReady = false;
45+
46+
void *enableMalloc(void *arg) {
47+
auto &GPA = *reinterpret_cast<gwp_asan::GuardedPoolAllocator *>(arg);
48+
49+
// Signal the main thread we are ready.
50+
pthread_mutex_lock(&Mutex);
51+
ThreadReady = true;
52+
pthread_cond_signal(&Conditional);
53+
pthread_mutex_unlock(&Mutex);
54+
55+
// Wait for the malloc_disable & fork, then enable the allocator again.
56+
sleep(1);
57+
GPA.enable();
58+
59+
return nullptr;
60+
}
61+
62+
TEST_F(DefaultGuardedPoolAllocator, DisableForkEnable) {
63+
pthread_t ThreadId;
64+
EXPECT_EQ(pthread_create(&ThreadId, nullptr, &enableMalloc, &GPA), 0);
65+
66+
// Do not lock the allocator right away, the other thread may need it to start
67+
// up.
68+
pthread_mutex_lock(&Mutex);
69+
while (!ThreadReady)
70+
pthread_cond_wait(&Conditional, &Mutex);
71+
pthread_mutex_unlock(&Mutex);
72+
73+
// Disable the allocator and fork. fork should succeed after malloc_enable.
74+
GPA.disable();
75+
pid_t Pid = fork();
76+
EXPECT_GE(Pid, 0);
77+
if (Pid == 0) {
78+
void *P = GPA.allocate(Size);
79+
EXPECT_NE(P, nullptr);
80+
GPA.deallocate(P);
81+
_exit(0);
82+
}
83+
waitpid(Pid, nullptr, 0);
84+
EXPECT_EQ(pthread_join(ThreadId, 0), 0);
85+
}
86+
} // namespace
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include "harness.h"
2+
3+
namespace gwp_asan {
4+
namespace test {
5+
bool OnlyOnce() {
6+
static int x = 0;
7+
return !x++;
8+
}
9+
} // namespace test
10+
} // namespace gwp_asan

compiler-rt/lib/gwp_asan/tests/harness.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,27 @@ namespace test {
2424
// `optional/printf_sanitizer_common.cpp` which supplies the __sanitizer::Printf
2525
// for this purpose.
2626
options::Printf_t getPrintfFunction();
27+
28+
// First call returns true, all the following calls return false.
29+
bool OnlyOnce();
30+
2731
}; // namespace test
2832
}; // namespace gwp_asan
2933

3034
class DefaultGuardedPoolAllocator : public ::testing::Test {
3135
public:
32-
DefaultGuardedPoolAllocator() {
36+
void SetUp() override {
3337
gwp_asan::options::Options Opts;
3438
Opts.setDefaults();
3539
MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations;
3640

3741
Opts.Printf = gwp_asan::test::getPrintfFunction();
42+
Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce();
3843
GPA.init(Opts);
3944
}
4045

46+
void TearDown() override { GPA.uninitTestOnly(); }
47+
4148
protected:
4249
gwp_asan::GuardedPoolAllocator GPA;
4350
decltype(gwp_asan::options::Options::MaxSimultaneousAllocations)
@@ -56,9 +63,12 @@ class CustomGuardedPoolAllocator : public ::testing::Test {
5663
MaxSimultaneousAllocations = MaxSimultaneousAllocationsArg;
5764

5865
Opts.Printf = gwp_asan::test::getPrintfFunction();
66+
Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce();
5967
GPA.init(Opts);
6068
}
6169

70+
void TearDown() override { GPA.uninitTestOnly(); }
71+
6272
protected:
6373
gwp_asan::GuardedPoolAllocator GPA;
6474
decltype(gwp_asan::options::Options::MaxSimultaneousAllocations)
@@ -67,16 +77,19 @@ class CustomGuardedPoolAllocator : public ::testing::Test {
6777

6878
class BacktraceGuardedPoolAllocator : public ::testing::Test {
6979
public:
70-
BacktraceGuardedPoolAllocator() {
80+
void SetUp() override {
7181
gwp_asan::options::Options Opts;
7282
Opts.setDefaults();
7383

7484
Opts.Printf = gwp_asan::test::getPrintfFunction();
7585
Opts.Backtrace = gwp_asan::options::getBacktraceFunction();
7686
Opts.PrintBacktrace = gwp_asan::options::getPrintBacktraceFunction();
87+
Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce();
7788
GPA.init(Opts);
7889
}
7990

91+
void TearDown() override { GPA.uninitTestOnly(); }
92+
8093
protected:
8194
gwp_asan::GuardedPoolAllocator GPA;
8295
};

compiler-rt/lib/scudo/standalone/CMakeLists.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
add_compiler_rt_component(scudo_standalone)
2-
# FIXME: GWP-ASan is temporarily disabled, re-enable once issues are fixed.
3-
if (FALSE AND COMPILER_RT_HAS_GWP_ASAN)
2+
if (COMPILER_RT_HAS_GWP_ASAN)
43
add_dependencies(scudo_standalone gwp_asan)
54
endif()
65

@@ -107,7 +106,7 @@ set(SCUDO_SOURCES_CXX_WRAPPERS
107106

108107
set(SCUDO_OBJECT_LIBS)
109108

110-
if (FALSE AND COMPILER_RT_HAS_GWP_ASAN)
109+
if (COMPILER_RT_HAS_GWP_ASAN)
111110
list(APPEND SCUDO_OBJECT_LIBS RTGwpAsan)
112111
list(APPEND SCUDO_CFLAGS -DGWP_ASAN_HOOKS)
113112
endif()

0 commit comments

Comments
 (0)