This patches exposed existing incorectness of swapcontext imlementation. swapcontext does not set oucp->uc_stack. Unpoisoning works if ucp is from makecontext, but may try to use garbage pointers if it's from previos swapcontext or from getcontext. Existing limit reduces probability of garbage pointers are used. I restore behavour which we had for years, and will look to improve swapcontext support. This reverts commit d0751c9725aab1dad3d86481e13a4628356e7422.
202 lines
6.4 KiB
C++
202 lines
6.4 KiB
C++
// Check that ASan plays well with annotated makecontext/swapcontext.
|
|
|
|
// RUN: %clangxx_asan -std=c++11 -lpthread -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
|
|
// RUN: %clangxx_asan -std=c++11 -lpthread -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
|
|
// RUN: %clangxx_asan -std=c++11 -lpthread -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
|
|
// RUN: %clangxx_asan -std=c++11 -lpthread -O3 %s -o %t && %run %t 2>&1 | FileCheck %s
|
|
// RUN: seq 60 | xargs -i -- grep LOOPCHECK %s > %t.checks
|
|
// RUN: %clangxx_asan -std=c++11 -lpthread -O0 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
|
|
// RUN: %clangxx_asan -std=c++11 -lpthread -O1 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
|
|
// RUN: %clangxx_asan -std=c++11 -lpthread -O2 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
|
|
// RUN: %clangxx_asan -std=c++11 -lpthread -O3 %s -o %t && %run %t 2>&1 | FileCheck %t.checks --check-prefix LOOPCHECK
|
|
|
|
//
|
|
// This test is too subtle to try on non-x86 arch for now.
|
|
// Android and musl do not support swapcontext.
|
|
// REQUIRES: x86-target-arch && glibc-2.27
|
|
|
|
#include <pthread.h>
|
|
#include <setjmp.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <sys/time.h>
|
|
#include <ucontext.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sanitizer/common_interface_defs.h>
|
|
|
|
ucontext_t orig_context;
|
|
ucontext_t child_context;
|
|
ucontext_t next_child_context;
|
|
|
|
char *next_child_stack;
|
|
|
|
const int kStackSize = 1 << 20;
|
|
|
|
const void *main_thread_stack;
|
|
size_t main_thread_stacksize;
|
|
|
|
const void *from_stack;
|
|
size_t from_stacksize;
|
|
|
|
__attribute__((noinline, noreturn)) void LongJump(jmp_buf env) {
|
|
longjmp(env, 1);
|
|
_exit(1);
|
|
}
|
|
|
|
// Simulate __asan_handle_no_return().
|
|
__attribute__((noinline)) void CallNoReturn() {
|
|
jmp_buf env;
|
|
if (setjmp(env) != 0)
|
|
return;
|
|
|
|
LongJump(env);
|
|
_exit(1);
|
|
}
|
|
|
|
void NextChild() {
|
|
CallNoReturn();
|
|
__sanitizer_finish_switch_fiber(nullptr, &from_stack, &from_stacksize);
|
|
|
|
printf("NextChild from: %p %zu\n", from_stack, from_stacksize);
|
|
|
|
char x[32] = {0}; // Stack gets poisoned.
|
|
printf("NextChild: %p\n", x);
|
|
|
|
CallNoReturn();
|
|
|
|
__sanitizer_start_switch_fiber(nullptr, main_thread_stack,
|
|
main_thread_stacksize);
|
|
CallNoReturn();
|
|
if (swapcontext(&next_child_context, &orig_context) < 0) {
|
|
perror("swapcontext");
|
|
_exit(1);
|
|
}
|
|
}
|
|
|
|
void Child(int mode) {
|
|
CallNoReturn();
|
|
__sanitizer_finish_switch_fiber(nullptr, &main_thread_stack,
|
|
&main_thread_stacksize);
|
|
char x[32] = {0}; // Stack gets poisoned.
|
|
printf("Child: %p\n", x);
|
|
CallNoReturn();
|
|
// (a) Do nothing, just return to parent function.
|
|
// (b) Jump into the original function. Stack remains poisoned unless we do
|
|
// something.
|
|
// (c) Jump to another function which will then jump back to the main function
|
|
if (mode == 0) {
|
|
__sanitizer_start_switch_fiber(nullptr, main_thread_stack,
|
|
main_thread_stacksize);
|
|
CallNoReturn();
|
|
} else if (mode == 1) {
|
|
__sanitizer_start_switch_fiber(nullptr, main_thread_stack,
|
|
main_thread_stacksize);
|
|
CallNoReturn();
|
|
if (swapcontext(&child_context, &orig_context) < 0) {
|
|
perror("swapcontext");
|
|
_exit(1);
|
|
}
|
|
} else if (mode == 2) {
|
|
printf("NextChild stack: %p\n", next_child_stack);
|
|
|
|
getcontext(&next_child_context);
|
|
next_child_context.uc_stack.ss_sp = next_child_stack;
|
|
next_child_context.uc_stack.ss_size = kStackSize / 2;
|
|
makecontext(&next_child_context, (void (*)())NextChild, 0);
|
|
__sanitizer_start_switch_fiber(nullptr, next_child_context.uc_stack.ss_sp,
|
|
next_child_context.uc_stack.ss_size);
|
|
CallNoReturn();
|
|
if (swapcontext(&child_context, &next_child_context) < 0) {
|
|
perror("swapcontext");
|
|
_exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
int Run(int arg, int mode, char *child_stack) {
|
|
printf("Child stack: %p\n", child_stack);
|
|
// Setup child context.
|
|
getcontext(&child_context);
|
|
child_context.uc_stack.ss_sp = child_stack;
|
|
child_context.uc_stack.ss_size = kStackSize / 2;
|
|
if (mode == 0) {
|
|
child_context.uc_link = &orig_context;
|
|
}
|
|
makecontext(&child_context, (void (*)())Child, 1, mode);
|
|
CallNoReturn();
|
|
void *fake_stack_save;
|
|
__sanitizer_start_switch_fiber(&fake_stack_save, child_context.uc_stack.ss_sp,
|
|
child_context.uc_stack.ss_size);
|
|
CallNoReturn();
|
|
if (swapcontext(&orig_context, &child_context) < 0) {
|
|
perror("swapcontext");
|
|
_exit(1);
|
|
}
|
|
CallNoReturn();
|
|
__sanitizer_finish_switch_fiber(fake_stack_save, &from_stack,
|
|
&from_stacksize);
|
|
CallNoReturn();
|
|
printf("Main context from: %p %zu\n", from_stack, from_stacksize);
|
|
|
|
// Touch childs's stack to make sure it's unpoisoned.
|
|
for (int i = 0; i < kStackSize; i++) {
|
|
child_stack[i] = i;
|
|
}
|
|
return child_stack[arg];
|
|
}
|
|
|
|
void handler(int sig) { CallNoReturn(); }
|
|
|
|
int main(int argc, char **argv) {
|
|
// set up a signal that will spam and trigger __asan_handle_no_return at
|
|
// tricky moments
|
|
struct sigaction act = {};
|
|
act.sa_handler = &handler;
|
|
if (sigaction(SIGPROF, &act, 0)) {
|
|
perror("sigaction");
|
|
_exit(1);
|
|
}
|
|
|
|
itimerval t;
|
|
t.it_interval.tv_sec = 0;
|
|
t.it_interval.tv_usec = 10;
|
|
t.it_value = t.it_interval;
|
|
if (setitimer(ITIMER_PROF, &t, 0)) {
|
|
perror("setitimer");
|
|
_exit(1);
|
|
}
|
|
|
|
char *heap = new char[kStackSize + 1];
|
|
next_child_stack = new char[kStackSize + 1];
|
|
char stack[kStackSize + 1];
|
|
// CHECK: WARNING: ASan doesn't fully support makecontext/swapcontext
|
|
int ret = 0;
|
|
// CHECK-NOT: ASan is ignoring requested __asan_handle_no_return
|
|
for (unsigned int i = 0; i < 30; ++i) {
|
|
ret += Run(argc - 1, 0, stack);
|
|
// LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
|
|
// LOOPCHECK: Main context from: [[CHILD_STACK]] 524288
|
|
ret += Run(argc - 1, 1, stack);
|
|
// LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
|
|
// LOOPCHECK: Main context from: [[CHILD_STACK]] 524288
|
|
ret += Run(argc - 1, 2, stack);
|
|
// LOOPCHECK: Child stack: [[CHILD_STACK:0x[0-9a-f]*]]
|
|
// LOOPCHECK: NextChild stack: [[NEXT_CHILD_STACK:0x[0-9a-f]*]]
|
|
// LOOPCHECK: NextChild from: [[CHILD_STACK]] 524288
|
|
// LOOPCHECK: Main context from: [[NEXT_CHILD_STACK]] 524288
|
|
ret += Run(argc - 1, 0, heap);
|
|
ret += Run(argc - 1, 1, heap);
|
|
ret += Run(argc - 1, 2, heap);
|
|
printf("Iteration %d passed\n", i);
|
|
}
|
|
|
|
// CHECK: Test passed
|
|
printf("Test passed\n");
|
|
|
|
delete[] heap;
|
|
delete[] next_child_stack;
|
|
|
|
return ret;
|
|
}
|