On subtargets that have a red zone, we will copy the stack pointer to the base pointer in the prologue prior to updating the stack pointer. There are no other updates to the base pointer after that. This suggests that we should be able to restore the stack pointer from the base pointer rather than loading it from the back chain or adding the frame size back to either the stack pointer or the frame pointer. This came about because functions that call setjmp need to restore the SP from the FP because the back chain might have been clobbered (see https://reviews.llvm.org/D92906). However, if the stack is realigned, the restored SP might be incorrect (which is what caused the failures in the two ASan test cases). This patch was tested quite extensivelly both with sanitizer runtimes and general code. Differential revision: https://reviews.llvm.org/D93327
171 lines
5.3 KiB
C++
171 lines
5.3 KiB
C++
// Tests that __asan_handle_no_return properly unpoisons the signal alternate
|
|
// stack.
|
|
|
|
// Don't optimize, otherwise the variables which create redzones might be
|
|
// dropped.
|
|
// RUN: %clangxx_asan -std=c++20 -fexceptions -O0 %s -o %t -pthread
|
|
// RUN: %run %t
|
|
|
|
// XFAIL: ios && !iossim
|
|
// longjmp from signal handler is unportable.
|
|
// XFAIL: solaris
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cerrno>
|
|
#include <csetjmp>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include <limits.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sanitizer/asan_interface.h>
|
|
|
|
namespace {
|
|
|
|
struct TestContext {
|
|
char *LeftRedzone;
|
|
char *RightRedzone;
|
|
std::jmp_buf JmpBuf;
|
|
};
|
|
|
|
TestContext defaultStack;
|
|
TestContext signalStack;
|
|
|
|
// Create a new stack frame to ensure that logically, the stack frame should be
|
|
// unpoisoned when the function exits. Exit is performed via jump, not return,
|
|
// such that we trigger __asan_handle_no_return and not ordinary unpoisoning.
|
|
template <class Jump>
|
|
void __attribute__((noinline)) poisonStackAndJump(TestContext &c, Jump jump) {
|
|
char Blob[100]; // This variable must not be optimized out, because we use it
|
|
// to create redzones.
|
|
|
|
c.LeftRedzone = Blob - 1;
|
|
c.RightRedzone = Blob + sizeof(Blob);
|
|
|
|
assert(__asan_address_is_poisoned(c.LeftRedzone));
|
|
assert(__asan_address_is_poisoned(c.RightRedzone));
|
|
|
|
// Jump to avoid normal cleanup of redzone markers. Instead,
|
|
// __asan_handle_no_return is called which unpoisons the stacks.
|
|
jump();
|
|
}
|
|
|
|
void testOnCurrentStack() {
|
|
TestContext c;
|
|
|
|
if (0 == setjmp(c.JmpBuf))
|
|
poisonStackAndJump(c, [&] { longjmp(c.JmpBuf, 1); });
|
|
|
|
assert(0 == __asan_region_is_poisoned(c.LeftRedzone,
|
|
c.RightRedzone - c.LeftRedzone));
|
|
}
|
|
|
|
bool isOnSignalStack() {
|
|
stack_t Stack;
|
|
sigaltstack(nullptr, &Stack);
|
|
return Stack.ss_flags == SS_ONSTACK;
|
|
}
|
|
|
|
void signalHandler(int, siginfo_t *, void *) {
|
|
assert(isOnSignalStack());
|
|
|
|
// test on signal alternate stack
|
|
testOnCurrentStack();
|
|
|
|
// test unpoisoning when jumping between stacks
|
|
poisonStackAndJump(signalStack, [] { longjmp(defaultStack.JmpBuf, 1); });
|
|
}
|
|
|
|
void setSignalAlternateStack(void *AltStack) {
|
|
sigaltstack((stack_t const *)AltStack, nullptr);
|
|
|
|
struct sigaction Action = {};
|
|
Action.sa_sigaction = signalHandler;
|
|
Action.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
|
|
sigemptyset(&Action.sa_mask);
|
|
|
|
sigaction(SIGUSR1, &Action, nullptr);
|
|
}
|
|
|
|
// Main test function.
|
|
// Must be run on another thread to be able to control memory placement between
|
|
// default stack and alternate signal stack.
|
|
// If the alternate signal stack is placed in close proximity before the
|
|
// default stack, __asan_handle_no_return might unpoison both, even without
|
|
// being aware of the signal alternate stack.
|
|
// We want to test reliably that __asan_handle_no_return can properly unpoison
|
|
// the signal alternate stack.
|
|
void *threadFun(void *AltStack) {
|
|
// first test on default stack (sanity check), no signal alternate stack set
|
|
testOnCurrentStack();
|
|
|
|
setSignalAlternateStack(AltStack);
|
|
|
|
// test on default stack again, but now the signal alternate stack is set
|
|
testOnCurrentStack();
|
|
|
|
// set up jump to test unpoisoning when jumping between stacks
|
|
if (0 == setjmp(defaultStack.JmpBuf))
|
|
// Test on signal alternate stack, via signalHandler
|
|
poisonStackAndJump(defaultStack, [] { raise(SIGUSR1); });
|
|
|
|
assert(!isOnSignalStack());
|
|
|
|
assert(0 == __asan_region_is_poisoned(
|
|
defaultStack.LeftRedzone,
|
|
defaultStack.RightRedzone - defaultStack.LeftRedzone));
|
|
|
|
assert(0 == __asan_region_is_poisoned(
|
|
signalStack.LeftRedzone,
|
|
signalStack.RightRedzone - signalStack.LeftRedzone));
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Check that __asan_handle_no_return properly unpoisons a signal alternate
|
|
// stack.
|
|
// __asan_handle_no_return tries to determine the stack boundaries and
|
|
// unpoisons all memory inside those. If this is not done properly, redzones for
|
|
// variables on can remain in shadow memory which might lead to false positive
|
|
// reports when the stack is reused.
|
|
int main() {
|
|
size_t const PageSize = sysconf(_SC_PAGESIZE);
|
|
// The Solaris defaults of 4k (32-bit) and 8k (64-bit) are too small.
|
|
size_t const MinStackSize = std::max(PTHREAD_STACK_MIN, 16 * 1024);
|
|
// To align the alternate stack, we round this up to page_size.
|
|
size_t const DefaultStackSize =
|
|
(MinStackSize - 1 + PageSize) & ~(PageSize - 1);
|
|
// The alternate stack needs a certain size, or the signal handler segfaults.
|
|
size_t const AltStackSize = 10 * PageSize;
|
|
size_t const MappingSize = DefaultStackSize + AltStackSize;
|
|
// Using mmap guarantees proper alignment.
|
|
void *const Mapping = mmap(nullptr, MappingSize,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS,
|
|
-1, 0);
|
|
|
|
stack_t AltStack = {};
|
|
AltStack.ss_sp = (char *)Mapping + DefaultStackSize;
|
|
AltStack.ss_flags = 0;
|
|
AltStack.ss_size = AltStackSize;
|
|
|
|
pthread_t Thread;
|
|
pthread_attr_t ThreadAttr;
|
|
pthread_attr_init(&ThreadAttr);
|
|
pthread_attr_setstack(&ThreadAttr, Mapping, DefaultStackSize);
|
|
pthread_create(&Thread, &ThreadAttr, &threadFun, (void *)&AltStack);
|
|
|
|
pthread_join(Thread, nullptr);
|
|
|
|
munmap(Mapping, MappingSize);
|
|
}
|