1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
// 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 -fexceptions -O0 %s -o %t -pthread
// RUN: %env_asan_opts=detect_stack_use_after_return=0 %run %t
// XFAIL: !rdar109379358
#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<size_t>(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);
}
|