Skip to content

Commit 2b47ad4

Browse files
Merge pull request #8730 from apple/m_borsa/cherry-pick-9be8892
🍒 [asan] Fix GC of FakeFrames
2 parents 5345565 + c84d013 commit 2b47ad4

File tree

2 files changed

+111
-1
lines changed

2 files changed

+111
-1
lines changed

compiler-rt/lib/asan/asan_fake_stack.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,22 +133,40 @@ void FakeStack::HandleNoReturn() {
133133
needs_gc_ = true;
134134
}
135135

136+
// Hack: The statement below is not true if we take into account sigaltstack or
137+
// makecontext. It should be possible to make GC to discard wrong stack frame if
138+
// we use these tools. For now, let's support the simplest case and allow GC to
139+
// discard only frames from the default stack, assuming there is no buffer on
140+
// the stack which is used for makecontext or sigaltstack.
141+
//
136142
// When throw, longjmp or some such happens we don't call OnFree() and
137143
// as the result may leak one or more fake frames, but the good news is that
138144
// we are notified about all such events by HandleNoReturn().
139145
// If we recently had such no-return event we need to collect garbage frames.
140146
// We do it based on their 'real_stack' values -- everything that is lower
141147
// than the current real_stack is garbage.
142148
NOINLINE void FakeStack::GC(uptr real_stack) {
149+
AsanThread *curr_thread = GetCurrentThread();
150+
if (!curr_thread)
151+
return; // Try again when we have a thread.
152+
auto top = curr_thread->stack_top();
153+
auto bottom = curr_thread->stack_bottom();
154+
if (real_stack < top || real_stack > bottom)
155+
return; // Not the default stack.
156+
143157
for (uptr class_id = 0; class_id < kNumberOfSizeClasses; class_id++) {
144158
u8 *flags = GetFlags(stack_size_log(), class_id);
145159
for (uptr i = 0, n = NumberOfFrames(stack_size_log(), class_id); i < n;
146160
i++) {
147161
if (flags[i] == 0) continue; // not allocated.
148162
FakeFrame *ff = reinterpret_cast<FakeFrame *>(
149163
GetFrame(stack_size_log(), class_id, i));
150-
if (ff->real_stack < real_stack) {
164+
// GC only on the default stack.
165+
if (ff->real_stack < real_stack && ff->real_stack >= top) {
151166
flags[i] = 0;
167+
// Poison the frame, so the any access will be reported as UAR.
168+
SetShadow(reinterpret_cast<uptr>(ff), BytesInSizeClass(class_id),
169+
class_id, kMagic8);
152170
}
153171
}
154172
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// RUN: %clangxx_asan -O0 -pthread %s -o %t && %env_asan_opts=use_sigaltstack=0 not --crash %run %t 2>&1 | FileCheck %s
2+
3+
// Check that fake stack does not discard frames on the main stack, when GC is
4+
// triggered from high alt stack.
5+
6+
#include <algorithm>
7+
#include <assert.h>
8+
#include <csignal>
9+
#include <cstdint>
10+
#include <pthread.h>
11+
#include <signal.h>
12+
#include <stdio.h>
13+
#include <stdlib.h>
14+
15+
const size_t kStackSize = 0x100000;
16+
17+
int *on_thread;
18+
int *p;
19+
20+
template <size_t N> void Fn() {
21+
int t[N];
22+
p = t;
23+
if constexpr (N > 1)
24+
Fn<N - 1>();
25+
}
26+
27+
static void Handler(int signo) {
28+
fprintf(stderr, "Handler Frame:%p\n", __builtin_frame_address(0));
29+
30+
// Trigger GC and create a lot of frame to reuse "Thread" frame if it was
31+
// discarded.
32+
for (int i = 0; i < 1000; ++i)
33+
Fn<1000>();
34+
// If we discarder and reused "Thread" frame, the next line will crash with
35+
// false report.
36+
*on_thread = 10;
37+
fprintf(stderr, "SUCCESS\n");
38+
// CHECK: SUCCESS
39+
}
40+
41+
void *Thread(void *arg) {
42+
fprintf(stderr, "Thread Frame:%p\n", __builtin_frame_address(0));
43+
stack_t stack = {
44+
.ss_sp = arg,
45+
.ss_flags = 0,
46+
.ss_size = kStackSize,
47+
};
48+
assert(sigaltstack(&stack, nullptr) == 0);
49+
50+
struct sigaction sa = {};
51+
sa.sa_handler = Handler;
52+
sa.sa_flags = SA_ONSTACK;
53+
sigaction(SIGABRT, &sa, nullptr);
54+
55+
// Store pointer to the local var, so we can access this frame from the signal
56+
// handler when the frame is still alive.
57+
int n;
58+
on_thread = &n;
59+
60+
// Abort should schedule FakeStack GC and call handler on alt stack.
61+
abort();
62+
}
63+
64+
int main(void) {
65+
// Allocate main and alt stack for future thread.
66+
void *main_stack = malloc(kStackSize);
67+
void *alt_stack = malloc(kStackSize);
68+
69+
// Pick the lower stack as the main stack, as we want to trigger GC in
70+
// FakeStack from alt stack in a such way that main stack is allocated below.
71+
if ((uintptr_t)main_stack > (uintptr_t)alt_stack)
72+
std::swap(alt_stack, main_stack);
73+
74+
pthread_attr_t attr;
75+
assert(pthread_attr_init(&attr) == 0);
76+
assert(pthread_attr_setstack(&attr, main_stack, kStackSize) == 0);
77+
78+
fprintf(stderr, "main_stack: %p-%p\n", main_stack,
79+
(char *)main_stack + kStackSize);
80+
fprintf(stderr, "alt_stack: %p-%p\n", alt_stack,
81+
(char *)alt_stack + kStackSize);
82+
83+
pthread_t tid;
84+
assert(pthread_create(&tid, &attr, Thread, alt_stack) == 0);
85+
86+
pthread_join(tid, nullptr);
87+
88+
free(main_stack);
89+
free(alt_stack);
90+
91+
return 0;
92+
}

0 commit comments

Comments
 (0)