]> git.saurik.com Git - apple/xnu.git/blob - san/kasan-fakestack.c
xnu-4570.51.1.tar.gz
[apple/xnu.git] / san / kasan-fakestack.c
1 /*
2 * Copyright (c) 2016 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29 #include <stdint.h>
30 #include <stdbool.h>
31 #include <kern/assert.h>
32 #include <kern/zalloc.h>
33 #include <mach/mach_vm.h>
34 #include <mach/vm_param.h>
35 #include <libkern/libkern.h>
36 #include <libkern/OSAtomic.h>
37 #include <sys/queue.h>
38 #include <kern/thread.h>
39 #include <kern/debug.h>
40
41 #include <kasan.h>
42 #include <kasan_internal.h>
43
44 int __asan_option_detect_stack_use_after_return = 0;
45 int fakestack_enabled = 0;
46
47 #define FAKESTACK_HEADER_SZ 64
48 #define FAKESTACK_NUM_SZCLASS 7
49
50 #define FAKESTACK_FREED 0 /* forced by clang */
51 #define FAKESTACK_ALLOCATED 1
52
53 #if FAKESTACK
54
55 struct fakestack_header {
56 LIST_ENTRY(fakestack_header) list;
57 void *site; /* allocation site */
58 struct {
59 uint8_t flag;
60 vm_size_t realsz : 52;
61 vm_size_t sz_class : 4;
62 };
63 uint64_t __pad0;
64 };
65 _Static_assert(sizeof(struct fakestack_header) <= FAKESTACK_HEADER_SZ, "fakestack_header size mismatch");
66
67 static zone_t fakestack_zones[FAKESTACK_NUM_SZCLASS];
68 static char fakestack_names[FAKESTACK_NUM_SZCLASS][16];
69 static const unsigned long fakestack_min = 1 << 6;
70 static const unsigned long __unused fakestack_max = 1 << 16;
71
72 /*
73 * Enter a fakestack critical section in a reentrant-safe fashion. Returns true on
74 * success with the kasan lock held.
75 */
76 static bool
77 thread_enter_fakestack(boolean_t *flags)
78 {
79 thread_t cur = current_thread();
80 if (cur && kasan_lock_held(cur)) {
81 /* current thread is already in kasan - fail */
82 return false;
83 }
84 kasan_lock(flags);
85 return true;
86 }
87
88 static volatile long suspend_count;
89 static const long suspend_threshold = 20;
90
91 void
92 kasan_fakestack_suspend(void)
93 {
94 if (OSIncrementAtomicLong(&suspend_count) == suspend_threshold) {
95 __asan_option_detect_stack_use_after_return = 0;
96 }
97 }
98
99 void
100 kasan_fakestack_resume(void)
101 {
102 long orig = OSDecrementAtomicLong(&suspend_count);
103 assert(orig >= 0);
104
105 if (fakestack_enabled && orig == suspend_threshold) {
106 __asan_option_detect_stack_use_after_return = 1;
107 }
108 }
109
110 static bool
111 ptr_is_on_stack(uptr ptr)
112 {
113 vm_offset_t base = dtrace_get_kernel_stack(current_thread());
114
115 if (ptr >= base && ptr < (base + kernel_stack_size)) {
116 return true;
117 } else {
118 return false;
119 }
120 }
121
122 /* free all unused fakestack entries */
123 static void NOINLINE
124 kasan_fakestack_gc(thread_t thread)
125 {
126 struct fakestack_header *cur, *tmp;
127 LIST_HEAD(, fakestack_header) tofree = LIST_HEAD_INITIALIZER(tofree);
128
129 /* move all the freed elements off the main list */
130 struct fakestack_header_list *head = &kasan_get_thread_data(thread)->fakestack_head;
131 LIST_FOREACH_SAFE(cur, head, list, tmp) {
132 if (cur->flag == FAKESTACK_FREED) {
133 LIST_REMOVE(cur, list);
134 LIST_INSERT_HEAD(&tofree, cur, list);
135 }
136 }
137
138 /* ... then actually free them */
139 LIST_FOREACH_SAFE(cur, &tofree, list, tmp) {
140 zone_t zone = fakestack_zones[cur->sz_class];
141 size_t sz = (fakestack_min << cur->sz_class) + FAKESTACK_HEADER_SZ;
142 LIST_REMOVE(cur, list);
143
144 void *ptr = (void *)cur;
145 kasan_free_internal(&ptr, &sz, KASAN_HEAP_FAKESTACK, &zone, cur->realsz, 1, FAKESTACK_QUARANTINE);
146 if (ptr) {
147 zfree(zone, ptr);
148 }
149 }
150 }
151
152 static uint8_t **
153 fakestack_flag_ptr(vm_offset_t ptr, vm_size_t sz)
154 {
155 uint8_t **x = (uint8_t **)ptr;
156 size_t idx = sz / 8;
157 return &x[idx - 1];
158 }
159
160 static uptr ALWAYS_INLINE
161 kasan_fakestack_alloc(int sz_class, size_t realsz)
162 {
163 if (!__asan_option_detect_stack_use_after_return) {
164 return 0;
165 }
166
167 if (sz_class >= FAKESTACK_NUM_SZCLASS) {
168 return 0;
169 }
170
171 uptr ret = 0;
172 size_t sz = fakestack_min << sz_class;
173 assert(realsz <= sz);
174 assert(sz <= fakestack_max);
175 zone_t zone = fakestack_zones[sz_class];
176
177 boolean_t flags;
178 if (!thread_enter_fakestack(&flags)) {
179 return 0;
180 }
181
182 kasan_fakestack_gc(current_thread()); /* XXX: optimal? */
183
184 ret = (uptr)zget(zone);
185
186 if (ret) {
187 size_t leftrz = 32 + FAKESTACK_HEADER_SZ;
188 size_t validsz = realsz - 32 - 16; /* remove redzones */
189 size_t rightrz = sz - validsz - 32; /* 16 bytes, plus whatever is left over */
190 struct fakestack_header *hdr = (struct fakestack_header *)ret;
191
192 kasan_poison(ret, validsz, leftrz, rightrz, ASAN_STACK_RZ);
193
194 hdr->site = __builtin_return_address(0);
195 hdr->realsz = realsz;
196 hdr->sz_class = sz_class;
197 hdr->flag = FAKESTACK_ALLOCATED;
198 ret += FAKESTACK_HEADER_SZ;
199
200 *fakestack_flag_ptr(ret, sz) = &hdr->flag; /* back ptr to the slot */
201 struct fakestack_header_list *head = &kasan_get_thread_data(current_thread())->fakestack_head;
202 LIST_INSERT_HEAD(head, hdr, list);
203 }
204
205 kasan_unlock(flags);
206 return ret;
207 }
208
209 static void NOINLINE
210 kasan_fakestack_free(int sz_class, uptr dst, size_t realsz)
211 {
212 if (ptr_is_on_stack(dst)) {
213 return;
214 }
215
216 assert(realsz <= (fakestack_min << sz_class));
217
218 vm_size_t sz = fakestack_min << sz_class;
219 zone_t zone = fakestack_zones[sz_class];
220 assert(zone);
221
222 /* TODO: check the magic? */
223
224 dst -= FAKESTACK_HEADER_SZ;
225 sz += FAKESTACK_HEADER_SZ;
226
227 struct fakestack_header *hdr = (struct fakestack_header *)dst;
228 assert(hdr->sz_class == sz_class);
229
230 boolean_t flags;
231 kasan_lock(&flags);
232
233 LIST_REMOVE(hdr, list);
234
235 kasan_free_internal((void **)&dst, &sz, KASAN_HEAP_FAKESTACK, &zone, realsz, 1, FAKESTACK_QUARANTINE);
236 if (dst) {
237 zfree(zone, (void *)dst);
238 }
239
240 kasan_unlock(flags);
241 }
242
243 void NOINLINE
244 kasan_unpoison_fakestack(thread_t thread)
245 {
246 boolean_t flags;
247 if (!thread_enter_fakestack(&flags)) {
248 panic("expected success entering fakestack\n");
249 }
250
251 struct fakestack_header_list *head = &kasan_get_thread_data(thread)->fakestack_head;
252 struct fakestack_header *cur;
253 LIST_FOREACH(cur, head, list) {
254 if (cur->flag == FAKESTACK_ALLOCATED) {
255 cur->flag = FAKESTACK_FREED;
256 }
257 }
258
259 kasan_fakestack_gc(thread);
260 kasan_unlock(flags);
261 }
262
263 void NOINLINE
264 kasan_init_fakestack(void)
265 {
266 /* allocate the fakestack zones */
267 for (int i = 0; i < FAKESTACK_NUM_SZCLASS; i++) {
268 zone_t z;
269 unsigned long sz = (fakestack_min << i) + FAKESTACK_HEADER_SZ;
270 size_t maxsz = 256UL * 1024;
271
272 if (i <= 3) {
273 /* size classes 0..3 are much more common */
274 maxsz *= 4;
275 }
276
277 snprintf(fakestack_names[i], 16, "fakestack.%d", i);
278 z = zinit(sz, maxsz, sz, fakestack_names[i]);
279 assert(z);
280 zone_change(z, Z_NOCALLOUT, TRUE);
281 zone_change(z, Z_EXHAUST, TRUE);
282 zone_change(z, Z_EXPAND, FALSE);
283 zone_change(z, Z_COLLECT, FALSE);
284 zone_change(z, Z_KASAN_QUARANTINE, FALSE);
285 zfill(z, maxsz / sz);
286 fakestack_zones[i] = z;
287 }
288
289 /* globally enable */
290 if (fakestack_enabled) {
291 __asan_option_detect_stack_use_after_return = 1;
292 }
293 }
294
295 #else /* FAKESTACK */
296
297 void
298 kasan_init_fakestack(void)
299 {
300 assert(__asan_option_detect_stack_use_after_return == 0);
301 }
302
303 void
304 kasan_unpoison_fakestack(thread_t __unused thread)
305 {
306 assert(__asan_option_detect_stack_use_after_return == 0);
307 }
308
309 static uptr
310 kasan_fakestack_alloc(int __unused sz_class, size_t __unused realsz)
311 {
312 assert(__asan_option_detect_stack_use_after_return == 0);
313 return 0;
314 }
315
316 static void
317 kasan_fakestack_free(int __unused sz_class, uptr __unused dst, size_t __unused realsz)
318 {
319 assert(__asan_option_detect_stack_use_after_return == 0);
320 panic("fakestack_free called on non-FAKESTACK config\n");
321 }
322
323 #endif
324
325 void kasan_init_thread(struct kasan_thread_data *td)
326 {
327 LIST_INIT(&td->fakestack_head);
328 }
329
330 #define FAKESTACK_DECLARE(szclass) \
331 uptr __asan_stack_malloc_##szclass(size_t sz) { return kasan_fakestack_alloc(szclass, sz); } \
332 void __asan_stack_free_##szclass(uptr dst, size_t sz) { kasan_fakestack_free(szclass, dst, sz); }
333
334 FAKESTACK_DECLARE(0)
335 FAKESTACK_DECLARE(1)
336 FAKESTACK_DECLARE(2)
337 FAKESTACK_DECLARE(3)
338 FAKESTACK_DECLARE(4)
339 FAKESTACK_DECLARE(5)
340 FAKESTACK_DECLARE(6)
341 FAKESTACK_DECLARE(7)
342 FAKESTACK_DECLARE(8)
343 FAKESTACK_DECLARE(9)
344 FAKESTACK_DECLARE(10)