unpoison-alternate-stack.cpp
5.32 KB
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
169
170
// 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);
}