safestack.cpp
11 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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
//===-- safestack.cpp -----------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements the runtime support for the safe stack protection
// mechanism. The runtime manages allocation/deallocation of the unsafe stack
// for the main thread, as well as all pthreads that are created/destroyed
// during program execution.
//
//===----------------------------------------------------------------------===//
#include "safestack_platform.h"
#include "safestack_util.h"
#include <errno.h>
#include <sys/resource.h>
#include "interception/interception.h"
using namespace safestack;
// TODO: To make accessing the unsafe stack pointer faster, we plan to
// eventually store it directly in the thread control block data structure on
// platforms where this structure is pointed to by %fs or %gs. This is exactly
// the same mechanism as currently being used by the traditional stack
// protector pass to store the stack guard (see getStackCookieLocation()
// function above). Doing so requires changing the tcbhead_t struct in glibc
// on Linux and tcb struct in libc on FreeBSD.
//
// For now, store it in a thread-local variable.
extern "C" {
__attribute__((visibility(
"default"))) __thread void *__safestack_unsafe_stack_ptr = nullptr;
}
namespace {
// TODO: The runtime library does not currently protect the safe stack beyond
// relying on the system-enforced ASLR. The protection of the (safe) stack can
// be provided by three alternative features:
//
// 1) Protection via hardware segmentation on x86-32 and some x86-64
// architectures: the (safe) stack segment (implicitly accessed via the %ss
// segment register) can be separated from the data segment (implicitly
// accessed via the %ds segment register). Dereferencing a pointer to the safe
// segment would result in a segmentation fault.
//
// 2) Protection via software fault isolation: memory writes that are not meant
// to access the safe stack can be prevented from doing so through runtime
// instrumentation. One way to do it is to allocate the safe stack(s) in the
// upper half of the userspace and bitmask the corresponding upper bit of the
// memory addresses of memory writes that are not meant to access the safe
// stack.
//
// 3) Protection via information hiding on 64 bit architectures: the location
// of the safe stack(s) can be randomized through secure mechanisms, and the
// leakage of the stack pointer can be prevented. Currently, libc can leak the
// stack pointer in several ways (e.g. in longjmp, signal handling, user-level
// context switching related functions, etc.). These can be fixed in libc and
// in other low-level libraries, by either eliminating the escaping/dumping of
// the stack pointer (i.e., %rsp) when that's possible, or by using
// encryption/PTR_MANGLE (XOR-ing the dumped stack pointer with another secret
// we control and protect better, as is already done for setjmp in glibc.)
// Furthermore, a static machine code level verifier can be ran after code
// generation to make sure that the stack pointer is never written to memory,
// or if it is, its written on the safe stack.
//
// Finally, while the Unsafe Stack pointer is currently stored in a thread
// local variable, with libc support it could be stored in the TCB (thread
// control block) as well, eliminating another level of indirection and making
// such accesses faster. Alternatively, dedicating a separate register for
// storing it would also be possible.
/// Minimum stack alignment for the unsafe stack.
const unsigned kStackAlign = 16;
/// Default size of the unsafe stack. This value is only used if the stack
/// size rlimit is set to infinity.
const unsigned kDefaultUnsafeStackSize = 0x2800000;
// Per-thread unsafe stack information. It's not frequently accessed, so there
// it can be kept out of the tcb in normal thread-local variables.
__thread void *unsafe_stack_start = nullptr;
__thread size_t unsafe_stack_size = 0;
__thread size_t unsafe_stack_guard = 0;
inline void *unsafe_stack_alloc(size_t size, size_t guard) {
SFS_CHECK(size + guard >= size);
void *addr = Mmap(nullptr, size + guard, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON, -1, 0);
SFS_CHECK(MAP_FAILED != addr);
Mprotect(addr, guard, PROT_NONE);
return (char *)addr + guard;
}
inline void unsafe_stack_setup(void *start, size_t size, size_t guard) {
SFS_CHECK((char *)start + size >= (char *)start);
SFS_CHECK((char *)start + guard >= (char *)start);
void *stack_ptr = (char *)start + size;
SFS_CHECK((((size_t)stack_ptr) & (kStackAlign - 1)) == 0);
__safestack_unsafe_stack_ptr = stack_ptr;
unsafe_stack_start = start;
unsafe_stack_size = size;
unsafe_stack_guard = guard;
}
/// Thread data for the cleanup handler
pthread_key_t thread_cleanup_key;
/// Safe stack per-thread information passed to the thread_start function
struct tinfo {
void *(*start_routine)(void *);
void *start_routine_arg;
void *unsafe_stack_start;
size_t unsafe_stack_size;
size_t unsafe_stack_guard;
};
/// Wrap the thread function in order to deallocate the unsafe stack when the
/// thread terminates by returning from its main function.
void *thread_start(void *arg) {
struct tinfo *tinfo = (struct tinfo *)arg;
void *(*start_routine)(void *) = tinfo->start_routine;
void *start_routine_arg = tinfo->start_routine_arg;
// Setup the unsafe stack; this will destroy tinfo content
unsafe_stack_setup(tinfo->unsafe_stack_start, tinfo->unsafe_stack_size,
tinfo->unsafe_stack_guard);
// Make sure out thread-specific destructor will be called
pthread_setspecific(thread_cleanup_key, (void *)1);
return start_routine(start_routine_arg);
}
/// Linked list used to store exiting threads stack/thread information.
struct thread_stack_ll {
struct thread_stack_ll *next;
void *stack_base;
size_t size;
pid_t pid;
ThreadId tid;
};
/// Linked list of unsafe stacks for threads that are exiting. We delay
/// unmapping them until the thread exits.
thread_stack_ll *thread_stacks = nullptr;
pthread_mutex_t thread_stacks_mutex = PTHREAD_MUTEX_INITIALIZER;
/// Thread-specific data destructor. We want to free the unsafe stack only after
/// this thread is terminated. libc can call functions in safestack-instrumented
/// code (like free) after thread-specific data destructors have run.
void thread_cleanup_handler(void *_iter) {
SFS_CHECK(unsafe_stack_start != nullptr);
pthread_setspecific(thread_cleanup_key, NULL);
pthread_mutex_lock(&thread_stacks_mutex);
// Temporary list to hold the previous threads stacks so we don't hold the
// thread_stacks_mutex for long.
thread_stack_ll *temp_stacks = thread_stacks;
thread_stacks = nullptr;
pthread_mutex_unlock(&thread_stacks_mutex);
pid_t pid = getpid();
ThreadId tid = GetTid();
// Free stacks for dead threads
thread_stack_ll **stackp = &temp_stacks;
while (*stackp) {
thread_stack_ll *stack = *stackp;
if (stack->pid != pid ||
(-1 == TgKill(stack->pid, stack->tid, 0) && errno == ESRCH)) {
Munmap(stack->stack_base, stack->size);
*stackp = stack->next;
free(stack);
} else
stackp = &stack->next;
}
thread_stack_ll *cur_stack =
(thread_stack_ll *)malloc(sizeof(thread_stack_ll));
cur_stack->stack_base = (char *)unsafe_stack_start - unsafe_stack_guard;
cur_stack->size = unsafe_stack_size + unsafe_stack_guard;
cur_stack->pid = pid;
cur_stack->tid = tid;
pthread_mutex_lock(&thread_stacks_mutex);
// Merge thread_stacks with the current thread's stack and any remaining
// temp_stacks
*stackp = thread_stacks;
cur_stack->next = temp_stacks;
thread_stacks = cur_stack;
pthread_mutex_unlock(&thread_stacks_mutex);
unsafe_stack_start = nullptr;
}
void EnsureInterceptorsInitialized();
/// Intercept thread creation operation to allocate and setup the unsafe stack
INTERCEPTOR(int, pthread_create, pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg) {
EnsureInterceptorsInitialized();
size_t size = 0;
size_t guard = 0;
if (attr) {
pthread_attr_getstacksize(attr, &size);
pthread_attr_getguardsize(attr, &guard);
} else {
// get pthread default stack size
pthread_attr_t tmpattr;
pthread_attr_init(&tmpattr);
pthread_attr_getstacksize(&tmpattr, &size);
pthread_attr_getguardsize(&tmpattr, &guard);
pthread_attr_destroy(&tmpattr);
}
SFS_CHECK(size);
size = RoundUpTo(size, kStackAlign);
void *addr = unsafe_stack_alloc(size, guard);
// Put tinfo at the end of the buffer. guard may be not page aligned.
// If that is so then some bytes after addr can be mprotected.
struct tinfo *tinfo =
(struct tinfo *)(((char *)addr) + size - sizeof(struct tinfo));
tinfo->start_routine = start_routine;
tinfo->start_routine_arg = arg;
tinfo->unsafe_stack_start = addr;
tinfo->unsafe_stack_size = size;
tinfo->unsafe_stack_guard = guard;
return REAL(pthread_create)(thread, attr, thread_start, tinfo);
}
pthread_mutex_t interceptor_init_mutex = PTHREAD_MUTEX_INITIALIZER;
bool interceptors_inited = false;
void EnsureInterceptorsInitialized() {
MutexLock lock(interceptor_init_mutex);
if (interceptors_inited)
return;
// Initialize pthread interceptors for thread allocation
INTERCEPT_FUNCTION(pthread_create);
interceptors_inited = true;
}
} // namespace
extern "C" __attribute__((visibility("default")))
#if !SANITIZER_CAN_USE_PREINIT_ARRAY
// On ELF platforms, the constructor is invoked using .preinit_array (see below)
__attribute__((constructor(0)))
#endif
void __safestack_init() {
// Determine the stack size for the main thread.
size_t size = kDefaultUnsafeStackSize;
size_t guard = 4096;
struct rlimit limit;
if (getrlimit(RLIMIT_STACK, &limit) == 0 && limit.rlim_cur != RLIM_INFINITY)
size = limit.rlim_cur;
// Allocate unsafe stack for main thread
void *addr = unsafe_stack_alloc(size, guard);
unsafe_stack_setup(addr, size, guard);
// Setup the cleanup handler
pthread_key_create(&thread_cleanup_key, thread_cleanup_handler);
}
#if SANITIZER_CAN_USE_PREINIT_ARRAY
// On ELF platforms, run safestack initialization before any other constructors.
// On other platforms we use the constructor attribute to arrange to run our
// initialization early.
extern "C" {
__attribute__((section(".preinit_array"),
used)) void (*__safestack_preinit)(void) = __safestack_init;
}
#endif
extern "C"
__attribute__((visibility("default"))) void *__get_unsafe_stack_bottom() {
return unsafe_stack_start;
}
extern "C"
__attribute__((visibility("default"))) void *__get_unsafe_stack_top() {
return (char*)unsafe_stack_start + unsafe_stack_size;
}
extern "C"
__attribute__((visibility("default"))) void *__get_unsafe_stack_start() {
return unsafe_stack_start;
}
extern "C"
__attribute__((visibility("default"))) void *__get_unsafe_stack_ptr() {
return __safestack_unsafe_stack_ptr;
}