Skip to content

Commit dbcd2e9

Browse files
itrofimowtstellar
authored andcommitted
[runtimes][asan] Fix swapcontext interception
Resetting oucp's stack to zero in swapcontext interception is incorrect, since it breaks ucp cleanup after swapcontext returns in some cases: Say we have two contexts, A and B, and we swapcontext from A to B, do some work on Bs stack and then swapcontext back from B to A. At this point shadow memory of Bs stack is in arbitrary state, but since we can't know whether B will ever swapcontext-ed to again we clean up it's shadow memory, because otherwise it remains poisoned and blows in completely unrelated places when heap-allocated memory of Bs context gets reused later (see #58633 for example). swapcontext prototype is swapcontext(ucontext* oucp, ucontext* ucp), so in this example A is oucp and B is ucp, and i refer to the process of cleaning up Bs shadow memory as ucp cleanup. About how it breaks: Take the same example with A and B: when we swapcontext back from B to A the oucp parameter of swapcontext is actually B, and current trunk resets its stack in a way that it becomes "uncleanupable" later. It works fine if we do A->B->A, but if we do A->B->A->B->A no cleanup is performed for Bs stack after B "returns" to A second time. That's exactly what happens in the test i provided, and it's actually a pretty common real world scenario. Instead of resetting oucp's we make use of uc_stack.ss_flags to mark context as "cleanup-able" by storing stack specific hash. It should be safe since this field is not used in [get|make|swap]context functions and is hopefully never meaningfully used in real-world scenarios (and i haven't seen any). Fixes #58633 Reviewed By: vitalybuka Differential Revision: https://reviews.llvm.org/D137654 (cherry picked from commit b380e8b)
1 parent ec006fb commit dbcd2e9

File tree

4 files changed

+112
-39
lines changed

4 files changed

+112
-39
lines changed

compiler-rt/lib/asan/asan_interceptors.cpp

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -257,12 +257,36 @@ static void ClearShadowMemoryForContextStack(uptr stack, uptr ssize) {
257257
PoisonShadow(bottom, ssize, 0);
258258
}
259259

260-
INTERCEPTOR(int, getcontext, struct ucontext_t *ucp) {
261-
// API does not requires to have ucp clean, and sets only part of fields. We
262-
// use ucp->uc_stack to unpoison new stack. We prefer to have zeroes then
263-
// uninitialized bytes.
264-
ResetContextStack(ucp);
265-
return REAL(getcontext)(ucp);
260+
INTERCEPTOR(void, makecontext, struct ucontext_t *ucp, void (*func)(), int argc,
261+
...) {
262+
va_list ap;
263+
uptr args[64];
264+
// We don't know a better way to forward ... into REAL function. We can
265+
// increase args size if neccecary.
266+
CHECK_LE(argc, ARRAY_SIZE(args));
267+
internal_memset(args, 0, sizeof(args));
268+
va_start(ap, argc);
269+
for (int i = 0; i < argc; ++i) args[i] = va_arg(ap, uptr);
270+
va_end(ap);
271+
272+
# define ENUMERATE_ARRAY_4(start) \
273+
args[start], args[start + 1], args[start + 2], args[start + 3]
274+
# define ENUMERATE_ARRAY_16(start) \
275+
ENUMERATE_ARRAY_4(start), ENUMERATE_ARRAY_4(start + 4), \
276+
ENUMERATE_ARRAY_4(start + 8), ENUMERATE_ARRAY_4(start + 12)
277+
# define ENUMERATE_ARRAY_64() \
278+
ENUMERATE_ARRAY_16(0), ENUMERATE_ARRAY_16(16), ENUMERATE_ARRAY_16(32), \
279+
ENUMERATE_ARRAY_16(48)
280+
281+
REAL(makecontext)
282+
((struct ucontext_t *)ucp, func, argc, ENUMERATE_ARRAY_64());
283+
284+
# undef ENUMERATE_ARRAY_4
285+
# undef ENUMERATE_ARRAY_16
286+
# undef ENUMERATE_ARRAY_64
287+
288+
// Sign the stack so we can identify it for unpoisoning.
289+
SignContextStack(ucp);
266290
}
267291

268292
INTERCEPTOR(int, swapcontext, struct ucontext_t *oucp,
@@ -279,9 +303,6 @@ INTERCEPTOR(int, swapcontext, struct ucontext_t *oucp,
279303
ReadContextStack(ucp, &stack, &ssize);
280304
ClearShadowMemoryForContextStack(stack, ssize);
281305

282-
// See getcontext interceptor.
283-
ResetContextStack(oucp);
284-
285306
# if __has_attribute(__indirect_return__) && \
286307
(defined(__x86_64__) || defined(__i386__))
287308
int (*real_swapcontext)(struct ucontext_t *, struct ucontext_t *)
@@ -658,11 +679,11 @@ void InitializeAsanInterceptors() {
658679
// Intecept jump-related functions.
659680
ASAN_INTERCEPT_FUNC(longjmp);
660681

661-
#if ASAN_INTERCEPT_SWAPCONTEXT
662-
ASAN_INTERCEPT_FUNC(getcontext);
682+
# if ASAN_INTERCEPT_SWAPCONTEXT
663683
ASAN_INTERCEPT_FUNC(swapcontext);
664-
#endif
665-
#if ASAN_INTERCEPT__LONGJMP
684+
ASAN_INTERCEPT_FUNC(makecontext);
685+
# endif
686+
# if ASAN_INTERCEPT__LONGJMP
666687
ASAN_INTERCEPT_FUNC(_longjmp);
667688
#endif
668689
#if ASAN_INTERCEPT___LONGJMP_CHK

compiler-rt/lib/asan/asan_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ void AsanApplyToGlobals(globals_op_fptr op, const void *needle);
105105

106106
void AsanOnDeadlySignal(int, void *siginfo, void *context);
107107

108+
void SignContextStack(void *context);
108109
void ReadContextStack(void *context, uptr *stack, uptr *ssize);
109-
void ResetContextStack(void *context);
110110
void StopInitOrderChecking();
111111

112112
// Wrapper for TLS/TSD.

compiler-rt/lib/asan/asan_linux.cpp

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
# include "asan_thread.h"
3535
# include "sanitizer_common/sanitizer_flags.h"
3636
# include "sanitizer_common/sanitizer_freebsd.h"
37+
# include "sanitizer_common/sanitizer_hash.h"
3738
# include "sanitizer_common/sanitizer_libc.h"
3839
# include "sanitizer_common/sanitizer_procmaps.h"
3940

@@ -211,16 +212,29 @@ void AsanCheckIncompatibleRT() {
211212
# endif // SANITIZER_ANDROID
212213

213214
# if ASAN_INTERCEPT_SWAPCONTEXT
214-
void ReadContextStack(void *context, uptr *stack, uptr *ssize) {
215-
ucontext_t *ucp = (ucontext_t *)context;
216-
*stack = (uptr)ucp->uc_stack.ss_sp;
217-
*ssize = ucp->uc_stack.ss_size;
215+
constexpr u32 kAsanContextStackFlagsMagic = 0x51260eea;
216+
217+
static int HashContextStack(const ucontext_t &ucp) {
218+
MurMur2Hash64Builder hash(kAsanContextStackFlagsMagic);
219+
hash.add(reinterpret_cast<uptr>(ucp.uc_stack.ss_sp));
220+
hash.add(ucp.uc_stack.ss_size);
221+
return static_cast<int>(hash.get());
222+
}
223+
224+
void SignContextStack(void *context) {
225+
ucontext_t *ucp = reinterpret_cast<ucontext_t *>(context);
226+
ucp->uc_stack.ss_flags = HashContextStack(*ucp);
218227
}
219228

220-
void ResetContextStack(void *context) {
221-
ucontext_t *ucp = (ucontext_t *)context;
222-
ucp->uc_stack.ss_sp = nullptr;
223-
ucp->uc_stack.ss_size = 0;
229+
void ReadContextStack(void *context, uptr *stack, uptr *ssize) {
230+
const ucontext_t *ucp = reinterpret_cast<const ucontext_t *>(context);
231+
if (HashContextStack(*ucp) == ucp->uc_stack.ss_flags) {
232+
*stack = reinterpret_cast<uptr>(ucp->uc_stack.ss_sp);
233+
*ssize = ucp->uc_stack.ss_size;
234+
return;
235+
}
236+
*stack = 0;
237+
*ssize = 0;
224238
}
225239
# endif // ASAN_INTERCEPT_SWAPCONTEXT
226240

compiler-rt/test/asan/TestCases/Linux/swapcontext_test.cpp

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,23 @@ ucontext_t child_context;
2020

2121
const int kStackSize = 1 << 20;
2222

23-
__attribute__((noinline))
24-
void Throw() {
25-
throw 1;
26-
}
23+
__attribute__((noinline)) void Throw() { throw 1; }
2724

28-
__attribute__((noinline))
29-
void ThrowAndCatch() {
25+
__attribute__((noinline)) void ThrowAndCatch() {
3026
try {
3127
Throw();
32-
} catch(int a) {
28+
} catch (int a) {
3329
printf("ThrowAndCatch: %d\n", a);
3430
}
3531
}
3632

37-
void Child(int mode) {
38-
assert(orig_context.uc_stack.ss_size == 0);
39-
char x[32] = {0}; // Stack gets poisoned.
40-
printf("Child: %p\n", x);
41-
ThrowAndCatch(); // Simulate __asan_handle_no_return().
33+
void Child(int mode, int a, int b, int c) {
34+
char x[32] = {0}; // Stack gets poisoned.
35+
printf("Child: %d\n", x);
36+
assert(a == 'a');
37+
assert(b == 'b');
38+
assert(c == 'c');
39+
ThrowAndCatch(); // Simulate __asan_handle_no_return().
4240
// (a) Do nothing, just return to parent function.
4341
// (b) Jump into the original function. Stack remains poisoned unless we do
4442
// something.
@@ -53,16 +51,13 @@ void Child(int mode) {
5351
int Run(int arg, int mode, char *child_stack) {
5452
printf("Child stack: %p\n", child_stack);
5553
// Setup child context.
56-
memset(&child_context, 0xff, sizeof(child_context));
5754
getcontext(&child_context);
58-
assert(child_context.uc_stack.ss_size == 0);
5955
child_context.uc_stack.ss_sp = child_stack;
6056
child_context.uc_stack.ss_size = kStackSize / 2;
6157
if (mode == 0) {
6258
child_context.uc_link = &orig_context;
6359
}
64-
makecontext(&child_context, (void (*)())Child, 1, mode);
65-
memset(&orig_context, 0xff, sizeof(orig_context));
60+
makecontext(&child_context, (void (*)())Child, 4, mode, 'a', 'b', 'c');
6661
if (swapcontext(&orig_context, &child_context) < 0) {
6762
perror("swapcontext");
6863
return 0;
@@ -74,6 +69,47 @@ int Run(int arg, int mode, char *child_stack) {
7469
return child_stack[arg];
7570
}
7671

72+
ucontext_t poll_context;
73+
ucontext_t poller_context;
74+
75+
void Poll() {
76+
swapcontext(&poll_context, &poller_context);
77+
78+
{
79+
char x = 0;
80+
printf("POLL: %p\n", &x);
81+
}
82+
83+
swapcontext(&poll_context, &poller_context);
84+
}
85+
86+
void DoRunPoll(char *poll_stack) {
87+
getcontext(&poll_context);
88+
poll_context.uc_stack.ss_sp = poll_stack;
89+
poll_context.uc_stack.ss_size = kStackSize / 2;
90+
makecontext(&poll_context, Poll, 0);
91+
92+
getcontext(&poller_context);
93+
94+
swapcontext(&poller_context, &poll_context);
95+
swapcontext(&poller_context, &poll_context);
96+
97+
// Touch poll's stack to make sure it's unpoisoned.
98+
for (int i = 0; i < kStackSize; i++) {
99+
poll_stack[i] = i;
100+
}
101+
}
102+
103+
void RunPoll() {
104+
char *poll_stack = new char[kStackSize];
105+
106+
for (size_t i = 0; i < 2; ++i) {
107+
DoRunPoll(poll_stack);
108+
}
109+
110+
delete[] poll_stack;
111+
}
112+
77113
int main(int argc, char **argv) {
78114
char stack[kStackSize + 1];
79115
int ret = 0;
@@ -82,6 +118,8 @@ int main(int argc, char **argv) {
82118
char *heap = new char[kStackSize + 1];
83119
ret += Run(argc - 1, 0, heap);
84120
ret += Run(argc - 1, 1, heap);
85-
delete [] heap;
121+
122+
RunPoll();
123+
delete[] heap;
86124
return ret;
87125
}

0 commit comments

Comments
 (0)